Mercurial Hosting > nabble
view src/nabble/model/DbSiteUpdater.java @ 66:3fbe9cb2e325 default tip
security
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Wed, 18 Sep 2024 03:51:47 -0600 |
parents | 7ecd1a4ef557 |
children |
line wrap: on
line source
package nabble.model; import fschmidt.db.DbDatabase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; final class DbSiteUpdater { private static final Logger logger = LoggerFactory.getLogger(DbSiteUpdater.class); // don't forget to update site.schema static final int version = 167; private static class Result { private static final long wait = 2000L; // 2 seconds private final long waitUntil = System.currentTimeMillis() + wait; private DbSiteUpdater updater; private SQLException sqlException = null; private RuntimeException runtimeException = null; private Error error = null; private final String user; private Result(DbDatabase db,String user) { updater = new DbSiteUpdater(db,this,user); this.user = user; updater.start(); } private synchronized void check() throws SQLException { if( !isDone() ) { long now = System.currentTimeMillis(); if( now < waitUntil ) { try { wait( waitUntil - now ); } catch(InterruptedException e) { throw new RuntimeException(e); } } if( !isDone() ) throw new UpdatingException(user); } if( sqlException != null ) throw sqlException; if( runtimeException != null ) throw runtimeException; if( error != null ) throw error; } synchronized boolean isDone() { return updater==null; } synchronized void done(Throwable e) { updater = null; if( e != null ) { if( e instanceof SQLException ) { sqlException = (SQLException)e; } else if( e instanceof RuntimeException ) { runtimeException = (RuntimeException)e; } else if( e instanceof Error ) { error = (Error)e; } else { runtimeException = new RuntimeException("invalid exception type",e); } } notifyAll(); } } private static final Map<String,Result> userResults = new HashMap<String,Result>(); private static final Object lock = new Object(); static void update(String user,DbDatabase db) throws SQLException { if( user.equals("global") ) return; // done from command line getResult(user,db).check(); } private static Result getResult(String user,DbDatabase db) throws SQLException { synchronized(lock) { Result result = userResults.get(user); if( result == null ) { result = new Result(db,user); userResults.put(user,result); } return result; } } // for shell public static void clearResult(long siteId) { String user = SiteKey.getInstance(siteId).schema(); synchronized(lock) { userResults.remove(user); } } private final Result result; private final DbUpdater up; private DbSiteUpdater(DbDatabase db,Result result,String schema) { this.up = new DbUpdater(db,schema); this.result = result; } private void start() { try { // needed for site creation because version is only up to date in current transaction if( up.dbVersion() == version ) { result.done(null); return; } } catch(RuntimeException e) { result.done(e); return; } catch(Error e) { result.done(e); return; } Executors.executeNow(new Runnable(){public void run(){ try { go(); result.done(null); } catch(SQLException e) { result.done(e); } catch(RuntimeException e) { result.done(e); } catch(Error e) { result.done(e); } finally { if( !result.isDone() ) logger.error("result isn't done"); } }}); } void go() throws SQLException { try { update(); if( up.databaseVersion() != version ) throw new RuntimeException(); } finally { up.close(); } } @Override public String toString() { return "DbSiteUpdater-" + up.schema; } private void execCatching(String sql,String exStr) throws SQLException { up.begin(); try { up.exec(sql); } catch(SQLException e) { if( !e.getMessage().contains(exStr) ) throw e; logger.info(e.toString()); up.exec("rollback"); } up.commit(); } void update() throws SQLException { switch( up.dbVersion() ) { case 0: up.execStep( "\r\n create index embed_key_site_idx on embed_key(site_id)\r\n " ); case 1: up.execStep( "\r\n create index file_avatar_site_idx on file_avatar(site_id)\r\n " ); case 2: up.execStep( "\r\n create index file_node_site_idx on file_node(site_id)\r\n " ); case 3: up.execStep( "\r\n create index file_temp_site_idx on file_temp(site_id)\r\n " ); case 4: up.execStep( "\r\n create index mailing_list_site_idx on mailing_list(site_id)\r\n " ); case 5: up.execStep( "\r\n create index node_msg_site_idx on node_msg(site_id)\r\n " ); case 6: up.execStep( "\r\n create index node_property_site_idx on node_property(site_id)\r\n " ); case 7: up.execStep( "\r\n create index paid_site_idx on paid(site_id)\r\n " ); case 8: up.execStep( "\r\n create index registration_site_idx on registration(site_id)\r\n " ); case 9: up.execStep( "\r\n create index subscription_site_idx on subscription(site_id)\r\n " ); case 10: up.execStep( "\r\n create index user_property_site_idx on user_property(site_id)\r\n " ); case 11: up.execStep( "\r\n create index view_count_site_idx on view_count(site_id)\r\n " ); case 12: up.execStep( "\r\n create index visited_site_idx on visited(site_id)\r\n " ); case 13: up.execStep( "\r\n create index tag_node_idx on tag(node_id)\r\n " ); case 14: up.execStep( "\r\n create index tag_user_idx on tag(user_id)\r\n " ); case 15: up.execStep( "\r\n create index embed_key_node_idx on embed_key(node_id)\r\n " ); case 16: up.execStep( "\r\n create index visited_node_idx on visited(node_id)\r\n " ); case 17: up.execStep( "\r\n create index visited_last_node_idx on visited(last_node_id)\r\n " ); case 18: up.execStep( "\r\n alter table node add column poll_option_count integer default null\r\n " ); case 19: up.execStep( "\r\n alter table configuration drop column site_id\r\n " ); case 20: up.execStep( "\r\n alter table configuration ADD PRIMARY KEY (name)\r\n " ); case 21: up.execStep( "\r\n alter table embed_key drop column site_id\r\n " ); case 22: up.execStep( "\r\n alter table file_avatar drop column site_id\r\n " ); case 23: up.execStep( "\r\n alter table file_node drop column site_id\r\n " ); case 24: up.execStep( "\r\n alter table file_temp drop column site_id\r\n " ); case 25: up.execStep( "\r\n alter table mailing_list drop column site_id\r\n " ); case 26: up.execStep( "\r\n alter table node_msg drop column site_id\r\n " ); case 27: up.execStep( "\r\n alter table node_property drop column site_id\r\n " ); case 28: up.execStep( "\r\n alter table paid drop column site_id\r\n " ); case 29: up.execStep( "\r\n alter table registration drop column site_id\r\n " ); case 30: up.execStep( "\r\n alter table subscription drop column site_id\r\n " ); case 31: up.execStep( "\r\n alter table user_property drop column site_id\r\n " ); case 32: up.execStep( "\r\n alter table view_count drop column site_id\r\n " ); case 33: up.execStep( "\r\n alter table visited drop column site_id\r\n " ); case 34: up.execStep( "\r\n alter table file_site drop column site_id\r\n " ); case 35: up.execStep( "\r\n alter table file_site ADD PRIMARY KEY (name)\r\n " ); case 36: up.execStep( "\r\n alter table module drop column site_id\r\n " ); case 37: up.execStep( "\r\n alter table module ADD PRIMARY KEY (module_name)\r\n " ); case 38: up.execStep( "\r\n alter table site_property drop column site_id\r\n " ); case 39: up.execStep( "\r\n alter table site_property ADD PRIMARY KEY (key)\r\n " ); case 40: up.execStep( "\r\n alter table tweak drop column site_id\r\n " ); case 41: up.execStep( "\r\n alter table tweak ADD PRIMARY KEY (tweak_name)\r\n " ); case 42: up.execStep( "\r\n alter table tag drop column site_id\r\n " ); case 43: up.execStep( "\r\n CREATE UNIQUE INDEX tag_idx\r\n ON tag (node_id, user_id, label);\r\n " ); case 44: up.execStep( "\r\n drop index tag_node_idx\r\n " ); case 45: up.execStep( "\r\n alter table user_ drop column site_id\r\n " ); case 46: up.execStep( "\r\n CREATE UNIQUE INDEX user_email_idx\r\n ON user_ (lower(email));\r\n " ); case 47: up.execStep( "\r\n CREATE UNIQUE INDEX user_name_idx\r\n ON user_ (lower(name));\r\n " ); case 48: up.execStep( "\r\n alter table node drop column site_id\r\n " ); case 49: up.execStep( "\r\n CREATE INDEX node_cookie_idx\r\n ON node (cookie)\r\n WHERE cookie IS NOT NULL;\r\n " ); case 50: up.execStep( "\r\n CREATE INDEX node_message_id_idx\r\n ON node (lower(message_id))\r\n WHERE message_id IS NOT NULL;\r\n " ); case 51: up.execStep( "\r\n drop table embed_key\r\n " ); case 52: up.execStep( "\r\n delete from site_property where key = '__embedding_warning'\r\n " ); case 53: up.execStep( "\r\n alter table site drop column site_id\r\n " ); case 54: up.execStep( "\r\n alter table site add column one integer default 1 not null\r\n " ); case 55: up.execStep( "\r\n alter table site ADD PRIMARY KEY (one)\r\n " ); case 56: up.execStep( "\r\n alter table site add column content_type character varying;\r\n " ); case 57: up.execStep( "\r\n update configuration set naml=E'<override_macro name=\"site_style\">\\n\\t<n.overridden/>\\n\\t#search-box { text-align:left; }\\n</override_macro>' where name='searchBoxAlignment';\r\n " ); case 58: up.execStep( "\r\n update site set ad_type=null where ad_type='ADBRITE' or ad_type='ADSENSE'\r\n " ); case 59: up.execStep( "\r\n alter table node\r\n add column unsafe character varying default 'new',\r\n add column safe_version integer\r\n " ); case 60: up.execStep( "\r\n alter table user_\r\n add column unsafe character varying default 'new',\r\n add column safe_version integer\r\n " ); case 61: up.execStep( "\r\n update site set ad_type='HOPELESS' where ad_type='COPYRIGHTED'\r\n " ); case 62: up.execStep( "\r\n update site set ad_type='HOPELESS' where ad_type='OURS'\r\n " ); case 63: up.execStep( "\r\n update site set ad_type=null, ad_free=" +(Integer.MAX_VALUE) +" where ad_type='NONE'\r\n " ); case 64: up.execStep( "\r\n alter table node\r\n add column is_safe boolean\r\n " ); case 65: up.execStep( "\r\n alter table user_\r\n add column is_safe boolean\r\n " ); case 66: up.execStep( null ); case 67: up.execStep( null ); case 68: up.execStep( "\r\n delete from tag where label='hide_ads'\r\n " ); case 69: up.execStep( "\r\n update site set ad_type=null where ad_type='SOME_ADSENSE'\r\n " ); case 70: up.execStep( "\r\n update site set ad_type='ADULT' where ad_type='HACK'\r\n " ); case 71: up.execStep( "\r\n update site set content_type=null where content_type='FAMILY'\r\n " ); case 72: up.execStep( "\r\n drop index if exists node_is_safe_idx\r\n " ); case 73: up.execStep( "\r\n drop index if exists user_is_safe_idx\r\n " ); case 74: up.execStep( "\r\n create index node_unsafe_idx on node(unsafe) where unsafe is not null and unsafe!='new'\r\n " ); case 75: up.execStep( "\r\n create index user_unsafe_idx on user_(unsafe) where unsafe is not null and unsafe!='new'\r\n " ); case 76: up.execStep( "\r\n update site set content_type=ad_type where content_type is null and ad_type is not null and ad_type!='HOPELESS'\r\n " ); case 77: up.execStep( "\r\n update site set ad_type=content_type where content_type is not null and ad_type is null\r\n " ); case 78: up.execStep( "\r\n update site set ad_type='ADULT' where content_type='ADULT' and ad_type='PORN'\r\n " ); case 79: up.execStep( "\r\n alter table user_\r\n drop column unsafe,\r\n drop column safe_version,\r\n drop column is_safe\r\n " ); case 80: up.execStep( null ); case 81: up.execStep( null ); case 82: up.begin(); up.exec( "\r\n alter table node\r\n drop column unsafe,\r\n drop column safe_version,\r\n drop column is_safe\r\n " ); up.exec( "\r\n alter table node\r\n add column unsafe character varying,\r\n add column safe_version integer,\r\n add column is_safe boolean\r\n " ); up.commit(); case 83: up.execStep( "\r\n create index node_unsafe_idx on node(unsafe) where safe_version is not null\r\n " ); case 84: up.execStep( "\r\n delete from module where module_name = 'topic_ads';\r\n " ); case 85: up.execStep( "\r\n alter table node\r\n drop column unsafe,\r\n drop column safe_version,\r\n drop column is_safe\r\n " ); case 86: up.execStep( "\r\n alter table site\r\n add column when_created timestamp with time zone NOT NULL DEFAULT now()\r\n " ); case 87: up.execStep( "\r\n update site\r\n set when_created = node.when_created\r\n from node\r\n where node.node_id = site.root_node_id\r\n " ); case 88: up.execStep( "\r\n alter table site\r\n add column is_safe boolean NOT NULL DEFAULT true\r\n " ); case 89: up.execStep( "\r\n update site set is_safe = false where ad_type is not null\r\n " ); case 90: up.execStep( "\r\n update site set when_created = now() where not is_safe\r\n " ); case 91: up.execStep( "\r\n alter table site rename column ad_type to old_ad_type\r\n " ); case 92: up.execStep( "\r\n alter table site rename column content_type to old_content_type\r\n " ); case 93: up.execStep( "\r\n alter table site\r\n add column ad_credits_for_users boolean NOT NULL DEFAULT false\r\n " ); case 94: up.execStep( "\r\n alter table site\r\n add column monthly_views integer NOT NULL DEFAULT 0\r\n " ); case 95: up.execStep( "\r\n delete from tag where label='group:Administrators' and node_id is null and user_id = (select user_id from user_ where email = 'pedro@nabble.com')\r\n " ); case 96: execCatching( "\r\n alter table tweak ADD PRIMARY KEY (tweak_name)\r\n " , "multiple primary keys for table" ); case 97: execCatching( "\r\n alter table mailing_list ADD PRIMARY KEY (node_id)\r\n " , "multiple primary keys for table" ); case 98: /* execCatching( "\r\n alter table mailing_list ADD CONSTRAINT mailing_list_node_id_fkey FOREIGN KEY (node_id) REFERENCES node (node_id) ON DELETE CASCADE\r\n " , "already exists" ); */ up.execStep( null ); case 99: execCatching( "\r\n CREATE INDEX forum_ml_idx ON mailing_list (lower(list_address))\r\n " , "already exists" ); case 100: execCatching( "\r\n alter table node ADD PRIMARY KEY (node_id)\r\n " , "multiple primary keys for table" ); case 101: execCatching( "\r\n alter table configuration ADD PRIMARY KEY (name)\r\n " , "multiple primary keys for table" ); case 102: execCatching( "\r\n alter table file_avatar ADD PRIMARY KEY (user_id, name)\r\n " , "multiple primary keys for table" ); case 103: execCatching( "\r\n alter table file_node ADD PRIMARY KEY (node_id, name)\r\n " , "multiple primary keys for table" ); case 104: execCatching( "\r\n alter table file_site ADD PRIMARY KEY (name)\r\n " , "multiple primary keys for table" ); case 105: execCatching( "\r\n alter table file_temp ADD PRIMARY KEY (user_id, name)\r\n " , "multiple primary keys for table" ); case 106: execCatching( "\r\n CREATE INDEX file_temp_date_idx ON file_temp (date_)\r\n " , "already exists" ); case 107: execCatching( "\r\n alter table module ADD PRIMARY KEY (module_name)\r\n " , "multiple primary keys for table" ); case 108: execCatching( "\r\n CREATE INDEX node_cookie_idx ON node (cookie) WHERE cookie IS NOT NULL\r\n " , "already exists" ); case 109: execCatching( "\r\n CREATE INDEX node_export ON node (export_permalink) WHERE export_permalink IS NOT NULL\r\n " , "already exists" ); case 110: execCatching( "\r\n CREATE INDEX node_message_id_idx ON node (lower(message_id)) WHERE message_id IS NOT NULL\r\n " , "already exists" ); case 111: execCatching( "\r\n CREATE INDEX node_ml2_idx ON node (lower(parent_message_id)) WHERE lower(parent_message_id) IS NOT NULL\r\n " , "already exists" ); case 112: execCatching( "\r\n CREATE INDEX node_owner_idx ON node (owner_id, when_created)\r\n " , "already exists" ); case 113: execCatching( "\r\n CREATE UNIQUE INDEX node_parent2_idx ON node (parent_id, when_created, node_id)\r\n " , "already exists" ); case 114: execCatching( "\r\n CREATE UNIQUE INDEX node_parent_idx ON node (parent_id, last_node_date, node_id)\r\n " , "already exists" ); case 115: execCatching( "\r\n CREATE INDEX node_sent_idx ON node (when_sent) WHERE when_sent IS NOT NULL\r\n " , "already exists" ); case 116: execCatching( "\r\n CREATE UNIQUE INDEX pinned_node_idx ON node (parent_id, pin) WHERE pin IS NOT NULL\r\n " , "already exists" ); case 117: execCatching( "\r\n CREATE INDEX post_date ON node (when_created)\r\n " , "already exists" ); case 118: execCatching( "\r\n alter table node_msg ADD PRIMARY KEY (node_id)\r\n " , "multiple primary keys for table" ); case 119: execCatching( "\r\n alter table node_property ADD PRIMARY KEY (node_id, key)\r\n " , "multiple primary keys for table" ); case 120: execCatching( "\r\n alter table paid ADD PRIMARY KEY (user_id)\r\n " , "multiple primary keys for table" ); case 121: execCatching( "\r\n alter table registration ADD PRIMARY KEY (key_)\r\n " , "multiple primary keys for table" ); case 122: execCatching( "\r\n CREATE INDEX registration_date_idx ON registration (date_)\r\n " , "already exists" ); case 123: execCatching( "\r\n alter table site ADD PRIMARY KEY (one)\r\n " , "multiple primary keys for table" ); case 124: execCatching( "\r\n alter table site_property ADD PRIMARY KEY (key)\r\n " , "multiple primary keys for table" ); case 125: execCatching( "\r\n alter table subscription ADD PRIMARY KEY (user_id, node_id)\r\n " , "multiple primary keys for table" ); case 126: execCatching( "\r\n CREATE INDEX subscription_node_idx ON subscription (node_id)\r\n " , "already exists" ); case 127: execCatching( "\r\n CREATE UNIQUE INDEX tag_idx ON tag (node_id, user_id, label)\r\n " , "already exists" ); case 128: execCatching( "\r\n CREATE INDEX tag_user_idx ON tag (user_id)\r\n " , "already exists" ); case 129: execCatching( "\r\n alter table user_ ADD PRIMARY KEY (user_id)\r\n " , "multiple primary keys for table" ); case 130: execCatching( "\r\n CREATE UNIQUE INDEX user_email_idx ON user_ (lower(email))\r\n " , "already exists" ); case 131: execCatching( "\r\n CREATE UNIQUE INDEX user_name_idx ON user_ (lower(name))\r\n " , "already exists" ); case 132: execCatching( "\r\n alter table user_property ADD PRIMARY KEY (user_id, key)\r\n " , "multiple primary keys for table" ); case 133: execCatching( "\r\n alter table view_count ADD PRIMARY KEY (node_id)\r\n " , "multiple primary keys for table" ); case 134: execCatching( "\r\n alter table visited ADD PRIMARY KEY (user_id, node_id)\r\n " , "multiple primary keys for table" ); case 135: execCatching( "\r\n CREATE INDEX visited_last_node_idx ON visited (last_node_id)\r\n " , "already exists" ); case 136: execCatching( "\r\n CREATE INDEX visited_node_idx ON visited (node_id)\r\n " , "already exists" ); case 137: execCatching( "\r\n ALTER TABLE ONLY file_avatar \r\n ADD CONSTRAINT file_avatar_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n " , "already exists" ); case 138: up.execStep( null ); case 139: execCatching( "\r\n ALTER TABLE ONLY file_temp \r\n ADD CONSTRAINT file_temp_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n " , "already exists" ); case 140: execCatching( "\r\n ALTER TABLE ONLY mailing_list \r\n ADD CONSTRAINT mailing_list_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n " , "already exists" ); case 141: up.execStep( null ); case 142: execCatching( "\r\n ALTER TABLE ONLY node \r\n ADD CONSTRAINT node_new_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES user_(user_id)\r\n " , "already exists" ); case 143: execCatching( "\r\n ALTER TABLE ONLY node \r\n ADD CONSTRAINT node_node_id_fkey FOREIGN KEY (node_id) REFERENCES node_msg(node_id) DEFERRABLE INITIALLY DEFERRED\r\n " , "already exists" ); case 144: up.execStep( null ); case 145: up.execStep( null ); case 146: execCatching( "\r\n ALTER TABLE ONLY paid \r\n ADD CONSTRAINT paid_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n " , "already exists" ); case 147: execCatching( "\r\n ALTER TABLE ONLY site \r\n ADD CONSTRAINT site_root_node_id_fkey FOREIGN KEY (root_node_id) REFERENCES node(node_id) DEFERRABLE INITIALLY DEFERRED\r\n " , "already exists" ); case 148: up.execStep( null ); case 149: execCatching( "\r\n ALTER TABLE ONLY subscription \r\n ADD CONSTRAINT subscription_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n " , "already exists" ); case 150: up.execStep( null ); case 151: execCatching( "\r\n ALTER TABLE ONLY tag \r\n ADD CONSTRAINT tag_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n " , "already exists" ); case 152: execCatching( "\r\n ALTER TABLE ONLY user_property \r\n ADD CONSTRAINT user_property_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n " , "already exists" ); case 153: up.execStep( null ); case 154: up.execStep( null ); case 155: up.execStep( null ); case 156: execCatching( "\r\n ALTER TABLE ONLY visited \r\n ADD CONSTRAINT visited_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n " , "already exists" ); case 157: up.execStep( null ); // problematic constraints case 158: execCatching( "\r\n ALTER TABLE ONLY file_node \r\n ADD CONSTRAINT file_node_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n " , "already exists" ); case 159: execCatching( "\r\n ALTER TABLE ONLY node_msg \r\n ADD CONSTRAINT node_msg_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n " , "already exists" ); case 160: execCatching( "\r\n ALTER TABLE ONLY node \r\n ADD CONSTRAINT node_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n " , "already exists" ); case 161: execCatching( "\r\n ALTER TABLE ONLY node_property \r\n ADD CONSTRAINT node_property_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n " , "already exists" ); case 162: execCatching( "\r\n ALTER TABLE ONLY subscription \r\n ADD CONSTRAINT subscription_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n " , "already exists" ); case 163: execCatching( "\r\n ALTER TABLE ONLY tag \r\n ADD CONSTRAINT tag_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n " , "already exists" ); case 164: execCatching( "\r\n ALTER TABLE ONLY view_count \r\n ADD CONSTRAINT view_count_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n " , "already exists" ); case 165: execCatching( "\r\n ALTER TABLE ONLY visited \r\n ADD CONSTRAINT visited_last_node_id_fkey FOREIGN KEY (last_node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n " , "already exists" ); case 166: execCatching( "\r\n ALTER TABLE ONLY visited \r\n ADD CONSTRAINT visited_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n " , "already exists" ); } } }