# Schema schemadoc

## Table: sl_apply_stats

Local SYNC apply statistics (running totals)

### Structure of sl_apply_stats

as_origin

  integer

  Origin of the SYNCs

as_num_insert

  bigint

  Number of INSERT operations performed

as_num_update

  bigint

  Number of UPDATE operations performed

as_num_delete

  bigint

  Number of DELETE operations performed

as_num_truncate

  bigint

  Number of TRUNCATE operations performed

as_num_script

  bigint

  Number of DDL operations performed

as_num_total

  bigint

  Total number of operations

as_duration

  interval

  Processing time

as_apply_first

  timestamp with time zone

  Timestamp of first recorded SYNC

as_apply_last

  timestamp with time zone

  Timestamp of most recent recorded SYNC

as_cache_prepare

  bigint

as_cache_hit

  bigint

as_cache_evict

  bigint

  Number of apply query cache evict operations

as_cache_prepare_max

  bigint

  Maximum number of apply queries prepared in one SYNC group

### Indexes on sl_apply_stats

sl_apply_stats_idx1

  as_origin

## Table: sl_archive_counter

Table used to generate the log shipping archive number.

### Structure of sl_archive_counter

ac_num

  bigint

  Counter of SYNC ID used in log shipping as the archive number

ac_timestamp

  timestamp with time zone

  Time at which the archive log was generated on the subscriber

## Table: sl_components

Table used to monitor what various slon/slonik components are doing

### Structure of sl_components

co_actor

  text

  `PRIMARY KEY`

  which component am I?

co_pid

  integer

  `NOT NULL`

  my process/thread PID on node where slon runs

co_node

  integer

  `NOT NULL`

  which node am I servicing?

co_connection_pid

  integer

  `NOT NULL`

  PID of database connection being used on database server

co_activity

  text

  activity that I am up to

co_starttime

  timestamp with time zone

  `NOT NULL`

  when did my activity begin? (timestamp reported as per slon process on server running slon)

co_event

  bigint

  which event have I started processing?

co_eventtype

  text

  what kind of event am I processing? (commonly n/a for event loop main threads)

## Table: sl_config_lock

This table exists solely to prevent overlapping execution of configuration change procedures and the resulting possible deadlocks.

### Structure of sl_config_lock

dummy

  integer

  No data ever goes in this table so the contents never matter. Indeed, this column does not really need to exist.

## Table: sl_confirm

Holds confirmation of replication events. After a period of time, Slony removes old confirmed events from both this table and the sl_event table.

### Structure of sl_confirm

con_origin

  integer

  The ID # (from sl_node.no_id) of the source node for this event

con_received

  integer

con_seqno

  bigint

  The ID # for the event

con_timestamp

  timestamp with time zone

  `DEFAULT (timeofday())::timestamp with time zone`

  When this event was confirmed

### Indexes on sl_confirm

sl_confirm_idx1

  con_origin, con_received, con_seqno

sl_confirm_idx2

  con_received, con_seqno

## Table: sl_event

Holds information about replication events. After a period of time, Slony removes old confirmed events from both this table and the sl_confirm table.

### Structure of sl_event

ev_origin

  integer

  `PRIMARY KEY`

  The ID # (from sl_node.no_id) of the source node for this event

ev_seqno

  bigint

  `PRIMARY KEY`

  The ID # for the event

ev_timestamp

  timestamp with time zone

  When this event record was created

ev_snapshot

  txid_snapshot

  TXID snapshot on provider node for this event

ev_type

  text

  The type of event this record is for. SYNC = Synchronise STORE_NODE = ENABLE_NODE = DROP_NODE = STORE_PATH = DROP_PATH = STORE_LISTEN = DROP_LISTEN = STORE_SET = DROP_SET = MERGE_SET = SET_ADD_TABLE = SET_ADD_SEQUENCE = STORE_TRIGGER = DROP_TRIGGER = MOVE_SET = ACCEPT_SET = SET_DROP_TABLE = SET_DROP_SEQUENCE = SET_MOVE_TABLE = SET_MOVE_SEQUENCE = FAILOVER_SET = SUBSCRIBE_SET = ENABLE_SUBSCRIPTION = UNSUBSCRIBE_SET = DDL_SCRIPT = ADJUST_SEQ = RESET_CONFIG =

ev_data1

  text

  Data field containing an argument needed to process the event

ev_data2

  text

  Data field containing an argument needed to process the event

ev_data3

  text

  Data field containing an argument needed to process the event

ev_data4

  text

  Data field containing an argument needed to process the event

ev_data5

  text

  Data field containing an argument needed to process the event

ev_data6

  text

  Data field containing an argument needed to process the event

ev_data7

  text

  Data field containing an argument needed to process the event

ev_data8

  text

  Data field containing an argument needed to process the event

## Table: sl_event_lock

This table exists solely to prevent multiple connections from concurrently creating new events and perhaps getting them out of order.

### Structure of sl_event_lock

dummy

  integer

  No data ever goes in this table so the contents never matter. Indeed, this column does not really need to exist.

## View: sl_failover_targets

### Structure of sl_failover_targets

set_id

  integer

set_origin

  integer

backup_id

  integer

### Definition of view sl_failover_targets

    SELECT set1.set_id
    ,
        set1.set_origin
    ,
        sub1.sub_receiver AS backup_id

    FROM sl_subscribe sub1
    ,
        sl_set set1

    WHERE (
         (sub1.sub_set = set1.set_id)
       AND (sub1.sub_forward = true)
       AND (NOT
               (sub1.sub_receiver IN
                     (
                      SELECT p1.pa_client

                        FROM (sl_path p1

                         LEFT JOIN sl_path p2
                                ON (
                                       (
                                             (p2.pa_client = p1.pa_client)
                                           AND (p2.pa_server = sub1.sub_receiver)
                                       )
                                 )
                           )

                       WHERE (
                                 (p2.pa_client IS NULL)
                               AND (p1.pa_server = set1.set_origin)
                               AND (p1.pa_client <> sub1.sub_receiver)
                           )
                     )
               )
         )
       AND (sub1.sub_provider = set1.set_origin)
       AND (NOT
               (sub1.sub_receiver IN
                     (
                      SELECT direct_recv.sub_receiver

                        FROM (
                                 (
                                       (
                                        SELECT subs2.sub_receiver

                                          FROM sl_subscribe subs2

                                         WHERE (
                                                   (subs2.sub_provider = set1.set_origin)
                                                 AND (subs2.sub_set = set1.set_id)
                                             )
                                       ) direct_recv

                                    JOIN (
                                        SELECT set2.set_id

                                          FROM sl_set set2

                                         WHERE (
                                                   (set2.set_origin = set1.set_origin)
                                                 AND (set2.set_id <> sub1.sub_set)
                                             )
                                       ) othersets
                                      ON (true)
                                 )

                         LEFT JOIN sl_subscribe subs3
                                ON (
                                       (
                                             (subs3.sub_set = othersets.set_id)
                                           AND (subs3.sub_forward = true)
                                           AND (subs3.sub_provider = set1.set_origin)
                                           AND (direct_recv.sub_receiver = subs3.sub_receiver)
                                       )
                                 )
                           )

                       WHERE (subs3.sub_receiver IS NULL)
                     )
               )
         )
    );

## Table: sl_listen

Indicates how nodes listen to events from other nodes in the Slony-I network.

### Structure of sl_listen

li_origin

  integer

  `PRIMARY KEY`

  `REFERENCES`

  The ID # (from sl_node.no_id) of the node this listener is operating on

li_provider

  integer

  `PRIMARY KEY`

  `REFERENCES`

  The ID # (from sl_node.no_id) of the source node for this listening event

li_receiver

  integer

  `PRIMARY KEY`

  `REFERENCES`

  The ID # (from sl_node.no_id) of the target node for this listening event

## Table: sl_log_1

Stores each change to be propagated to subscriber nodes

### Structure of sl_log_1

log_origin

  integer

  Origin node from which the change came

log_txid

  bigint

  Transaction ID on the origin node

log_tableid

  integer

  The table ID (from sl_table.tab_id) that this log entry is to affect

log_actionseq

  bigint

  The sequence number in which actions will be applied on replicas

log_tablenspname

  text

  The schema name of the table affected

log_tablerelname

  text

  The table name of the table affected

log_cmdtype

  "char"

  Replication action to take. U = Update, I = Insert, D = DELETE, T = TRUNCATE

log_cmdupdncols

  integer

  For cmdtype=U the number of updated columns in cmdargs

log_cmdargs

  text[]

  The data needed to perform the log action on the replica

### Indexes on sl_log_1

sl_log_1_idx1

  log_origin, log_txid, log_actionseq

## Table: sl_log_2

Stores each change to be propagated to subscriber nodes

### Structure of sl_log_2

log_origin

  integer

  Origin node from which the change came

log_txid

  bigint

  Transaction ID on the origin node

log_tableid

  integer

  The table ID (from sl_table.tab_id) that this log entry is to affect

log_actionseq

  bigint

  The sequence number in which actions will be applied on replicas

log_tablenspname

  text

  The schema name of the table affected

log_tablerelname

  text

  The table name of the table affected

log_cmdtype

  "char"

  Replication action to take. S = Script statement, s = Script complete

log_cmdupdncols

  integer

  For cmdtype=U the number of updated columns in cmdargs

log_cmdargs

  text[]

  The data needed to perform the log action on the replica

### Indexes on sl_log_2

sl_log_2_idx1

  log_origin, log_txid, log_actionseq

## Table: sl_log_script

Captures SQL script queries to be propagated to subscriber nodes

### Structure of sl_log_script

log_origin

  integer

  Origin name from which the change came

log_txid

  bigint

  Transaction ID on the origin node

log_actionseq

  bigint

  The sequence number in which actions will be applied on replicas

log_cmdtype

  "char"

log_cmdargs

  text[]

  The DDL statement, optionally followed by selected nodes to execute it on.

### Indexes on sl_log_script

sl_log_script_idx1

  log_origin, log_txid, log_actionseq

## Table: sl_node

Holds the list of nodes associated with this namespace.

### Structure of sl_node

no_id

  integer

  `PRIMARY KEY`

  The unique ID number for the node

no_active

  boolean

  Is the node active in replication yet?

no_comment

  text

  A human-oriented description of the node

no_failed

  boolean

### Tables referencing sl_listen via Foreign Key Constraints

## Table: sl_nodelock

Used to prevent multiple slon instances and to identify the backends to kill in terminateNodeConnections().

### Structure of sl_nodelock

nl_nodeid

  integer

  `PRIMARY KEY`

  Clients node_id

nl_conncnt

  serial

  `PRIMARY KEY`

  Clients connection number

nl_backendpid

  integer

  PID of database backend owning this lock

## Table: sl_path

Holds connection information for the paths between nodes, and the synchronisation delay

### Structure of sl_path

pa_server

  integer

  `PRIMARY KEY`

  `REFERENCES`

  The Node ID # (from sl_node.no_id) of the data source

pa_client

  integer

  `PRIMARY KEY`

  `REFERENCES`

  The Node ID # (from sl_node.no_id) of the data target

pa_conninfo

  text

  `NOT NULL`

  The PostgreSQL connection string used to connect to the source node.

pa_connretry

  integer

  The synchronisation delay, in seconds

### Tables referencing sl_listen via Foreign Key Constraints

## Table: sl_registry

Stores miscellaneous runtime data

### Structure of sl_registry

reg_key

  text

  `PRIMARY KEY`

  Unique key of the runtime option

reg_int4

  integer

  Option value if type int4

reg_text

  text

  Option value if type text

reg_timestamp

  timestamp with time zone

  Option value if type timestamp

## View: sl_seqlastvalue

### Structure of sl_seqlastvalue

seq_id

  integer

seq_set

  integer

seq_reloid

  oid

seq_origin

  integer

seq_last_value

  bigint

### Definition of view sl_seqlastvalue

    SELECT sq.seq_id
    ,
        sq.seq_set
    ,
        sq.seq_reloid
    ,
        s.set_origin AS seq_origin
    ,
        sequencelastvalue
    (
         (
               (quote_ident
                     (
                           (pgn.nspname)::text
                     ) || '.'::text
               ) || quote_ident
               (
                     (pgc.relname)::text
               )
         )
    ) AS seq_last_value

    FROM sl_sequence sq
    ,
        sl_set s
    ,
        pg_class pgc
    ,
        pg_namespace pgn

    WHERE (
         (s.set_id = sq.seq_set)
       AND (pgc.oid = sq.seq_reloid)
       AND (pgn.oid = pgc.relnamespace)
    );

## Table: sl_seqlog

Log of Sequence updates

### Structure of sl_seqlog

seql_seqid

  integer

  Sequence ID

seql_origin

  integer

  Publisher node at which the sequence originates

seql_ev_seqno

  bigint

  Slony-I Event with which this sequence update is associated

seql_last_value

  bigint

  Last value published for this sequence

### Indexes on sl_seqlog

sl_seqlog_idx

  seql_origin, seql_ev_seqno, seql_seqid

## Table: sl_sequence

Similar to sl_table, each entry identifies a sequence being replicated.

### Structure of sl_sequence

seq_id

  integer

  `PRIMARY KEY`

  An internally-used ID for Slony-I to use in its sequencing of updates

seq_reloid

  oid

  `UNIQUE`

  `NOT NULL`

  The OID of the sequence object

seq_relname

  name

  `NOT NULL`

  The name of the sequence in pg_catalog.pg_class.relname used to recover from a dump/restore cycle

seq_nspname

  name

  `NOT NULL`

  The name of the schema in pg_catalog.pg_namespace.nspname used to recover from a dump/restore cycle

seq_set

  integer

  `REFERENCES`

  Indicates which replication set the object is in

seq_comment

  text

  A human-oriented comment

## Table: sl_set

Holds definitions of replication sets.

### Structure of sl_set

set_id

  integer

  `PRIMARY KEY`

  A unique ID number for the set.

set_origin

  integer

  `REFERENCES`

  The ID number of the source node for the replication set.

set_locked

  bigint

  Transaction ID where the set was locked.

set_comment

  text

  A human-oriented description of the set.

### Tables referencing sl_sequence via Foreign Key Constraints

## Table: sl_setsync

SYNC information

### Structure of sl_setsync

ssy_setid

  integer

  `PRIMARY KEY`

  `REFERENCES`

  ID number of the replication set

ssy_origin

  integer

  `REFERENCES`

  ID number of the node

ssy_seqno

  bigint

  Slony-I sequence number

ssy_snapshot

  txid_snapshot

  TXID in provider system seen by the event

ssy_action_list

  text

  action list used during the subscription process. At the time a subscriber copies over data from the origin, it sees all tables in a state somewhere between two SYNC events. Therefore this list must contains all log_actionseqs that are visible at that time, whose operations have therefore already been included in the data copied at the time the initial data copy is done. Those actions may therefore be filtered out of the first SYNC done after subscribing.

## Table: sl_subscribe

Holds a list of subscriptions on sets

### Structure of sl_subscribe

sub_set

  integer

  `PRIMARY KEY`

  `REFERENCES`

  ID # (from sl_set) of the set being subscribed to

sub_provider

  integer

  `REFERENCES`

  ID# (from sl_node) of the node providing data

sub_receiver

  integer

  `PRIMARY KEY`

  `REFERENCES`

  ID# (from sl_node) of the node receiving data from the provider

sub_forward

  boolean

  Does this provider keep data in sl_log_1/sl_log_2 to allow it to be a provider for other nodes?

sub_active

  boolean

  Has this subscription been activated? This is not set on the subscriber until AFTER the subscriber has received COPY data from the provider

## Table: sl_table

Holds information about the tables being replicated.

### Structure of sl_table

tab_id

  integer

  `PRIMARY KEY`

  Unique key for Slony-I to use to identify the table

tab_reloid

  oid

  `UNIQUE`

  `NOT NULL`

  The OID of the table in pg_catalog.pg_class.oid

tab_relname

  name

  `NOT NULL`

  The name of the table in pg_catalog.pg_class.relname used to recover from a dump/restore cycle

tab_nspname

  name

  `NOT NULL`

  The name of the schema in pg_catalog.pg_namespace.nspname used to recover from a dump/restore cycle

tab_set

  integer

  `REFERENCES`

  ID of the replication set the table is in

tab_idxname

  name

  `NOT NULL`

  The name of the primary index of the table

tab_altered

  boolean

  `NOT NULL`

  Has the table been modified for replication?

tab_comment

  text

  Human-oriented description of the table

## add_empty_table_to_replication(p_comment integer, p_idxname integer, p_tabname text, p_nspname text, p_tab_id text, p_set_id text)

add_empty_table_to_replication(p_comment integer, p_idxname integer, p_tabname text, p_nspname text, p_tab_id text, p_set_id text)

### Function Properties

Language Return Type PLPGSQL bigint

Verify that a table is empty, and add it to replication. tab_idxname is optional - if NULL, then we use the primary key.

Note that this function is to be run within an EXECUTE SCRIPT script, so it runs at the right place in the transaction stream on all nodes.

    declare

      prec record;
      v_origin int4;
      v_isorigin boolean;
      v_fqname text;
      v_query text;
      v_rows integer;
      v_idxname text;

    begin
    -- Need to validate that the set exists; the set will tell us if this is the origin
      select set_origin into v_origin from sl_set where set_id = p_set_id;
      if not found then
    	raise exception 'add_empty_table_to_replication: set % not found!', p_set_id;
      end if;

    -- Need to be aware of whether or not this node is origin for the set
       v_isorigin := ( v_origin = getLocalNodeId('_schemadoc') );

       v_fqname := '"' || p_nspname || '"."' || p_tabname || '"';
    -- Take out a lock on the table
       v_query := 'lock ' || v_fqname || ';';
       execute v_query;

       if v_isorigin then
    	-- On the origin, verify that the table is empty, failing if it has any tuples
            v_query := 'select 1 as tuple from ' || v_fqname || ' limit 1;';
    	execute v_query into prec;
            GET DIAGNOSTICS v_rows = ROW_COUNT;
    	if v_rows = 0 then
    		raise notice 'add_empty_table_to_replication: table % empty on origin - OK', v_fqname;
    	else
    		raise exception 'add_empty_table_to_replication: table % contained tuples on origin node %', v_fqname, v_origin;
    	end if;
       else
    	-- On other nodes, TRUNCATE the table
            v_query := 'truncate ' || v_fqname || ';';
    	execute v_query;
       end if;
    -- If p_idxname is NULL, then look up the PK index, and RAISE EXCEPTION if one does not exist
       if p_idxname is NULL then
    	select c2.relname into prec from pg_catalog.pg_index i, pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_namespace n where i.indrelid = c1.oid and i.indexrelid = c2.oid and c1.relname = p_tabname and i.indisprimary and n.nspname = p_nspname and n.oid = c1.relnamespace;
    	if not found then
    		raise exception 'add_empty_table_to_replication: table % has no primary key and no candidate specified!', v_fqname;
    	else
    		v_idxname := prec.relname;
    	end if;
       else
    	v_idxname := p_idxname;
       end if;
       return setAddTable_int(p_set_id, p_tab_id, v_fqname, v_idxname, p_comment);
    end

## add_missing_table_field(p_type text, p_field text, p_table text, p_namespace text)

add_missing_table_field(p_type text, p_field text, p_table text, p_namespace text)

### Function Properties

Language Return Type PLPGSQL boolean

Add a column of a given type to a table if it is missing

    DECLARE
      v_row       record;
      v_query     text;
    BEGIN
      if not check_table_field_exists(p_namespace, p_table, p_field) then
        raise notice 'Upgrade table %.% - add field %', p_namespace, p_table, p_field;
        v_query := 'alter table ' || p_namespace || '.' || p_table || ' add column ';
        v_query := v_query || p_field || ' ' || p_type || ';';
        execute v_query;
        return 't';
      else
        return 'f';
      end if;
    END;

## addpartiallogindices()

addpartiallogindices()

### Function Properties

Language Return Type PLPGSQL integer

Add partial indexes, if possible, to the unused sl_log_? table for all origin nodes, and drop any that are no longer needed.

This function presently gets run any time set origins are manipulated (FAILOVER, STORE SET, MOVE SET, DROP SET), as well as each time the system switches between sl_log_1 and sl_log_2.

    DECLARE
    	v_current_status	int4;
    	v_log			int4;
    	v_dummy		record;
    	v_dummy2	record;
    	idef 		text;
    	v_count		int4;
            v_iname         text;
    	v_ilen int4;
    	v_maxlen int4;
    BEGIN
    	v_count := 0;
    	select last_value into v_current_status from sl_log_status;

    	-- If status is 2 or 3 --> in process of cleanup --> unsafe to create indices
    	if v_current_status in (2, 3) then
    		return 0;
    	end if;

    	if v_current_status = 0 then   -- Which log should get indices?
    		v_log := 2;
    	else
    		v_log := 1;
    	end if;
    --                                       PartInd_test_db_sl_log_2-node-1
    	-- Add missing indices...
    	for v_dummy in select distinct set_origin from sl_set loop
                v_iname := 'PartInd_schemadoc_sl_log_' || v_log::text || '-node-'
    			|| v_dummy.set_origin::text;
    	   -- raise notice 'Consider adding partial index % on sl_log_%', v_iname, v_log;
    	   -- raise notice 'schema: [_schemadoc] tablename:[sl_log_%]', v_log;
                select * into v_dummy2 from pg_catalog.pg_indexes where tablename = 'sl_log_' || v_log::text and  indexname = v_iname;
                if not found then
    		-- raise notice 'index was not found - add it!';
            v_iname := 'PartInd_schemadoc_sl_log_' || v_log::text || '-node-' || v_dummy.set_origin::text;
    		v_ilen := pg_catalog.length(v_iname);
    		v_maxlen := pg_catalog.current_setting('max_identifier_length'::text)::int4;
                    if v_ilen > v_maxlen then
    		   raise exception 'Length of proposed index name [%] > max_identifier_length [%] - cluster name probably too long', v_ilen, v_maxlen;
    		end if;

    		idef := 'create index "' || v_iname ||
                            '" on sl_log_' || v_log::text || ' USING btree(log_txid) where (log_origin = ' || v_dummy.set_origin::text || ');';
    		execute idef;
    		v_count := v_count + 1;
                else
                    -- raise notice 'Index % already present - skipping', v_iname;
                end if;
    	end loop;

    	-- Remove unneeded indices...
    	for v_dummy in select indexname from pg_catalog.pg_indexes i where i.tablename = 'sl_log_' || v_log::text and
                           i.indexname like ('PartInd_schemadoc_sl_log_' || v_log::text || '-node-%') and
                           not exists (select 1 from sl_set where
    				i.indexname = 'PartInd_schemadoc_sl_log_' || v_log::text || '-node-' || set_origin::text)
    	loop
    		-- raise notice 'Dropping obsolete index %d', v_dummy.indexname;
    		idef := 'drop index "' || v_dummy.indexname || '";';
    		execute idef;
    		v_count := v_count - 1;
    	end loop;
    	return v_count;
    END

## agg_text_sum(txt_new text, txt_before text)

agg_text_sum(txt_new text, txt_before text)

### Function Properties

Language Return Type PLPGSQL text

An accumulator function used by the slony string_agg function to aggregate rows into a string

    DECLARE
      c_delim text;
    BEGIN
        c_delim = ',';
    	IF (txt_before IS NULL or txt_before='') THEN
    	   RETURN txt_new;
    	END IF;
    	RETURN txt_before || c_delim || txt_new;
    END;

## altertableaddtriggers(p_tab_id integer)

altertableaddtriggers(p_tab_id integer)

### Function Properties

Language Return Type PLPGSQL integer

alterTableAddTriggers(tab_id)

Adds the log and deny access triggers to a replicated table.

    declare
    	v_no_id				int4;
    	v_tab_row			record;
    	v_tab_fqname		text;
    	v_tab_attkind		text;
    	v_n					int4;
    	v_trec	record;
    	v_tgbad	boolean;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Get our local node ID
    	-- ----
    	v_no_id := getLocalNodeId('_schemadoc');

    	-- ----
    	-- Get the sl_table row and the current origin of the table.
    	-- ----
    	select T.tab_reloid, T.tab_set, T.tab_idxname,
    			S.set_origin, PGX.indexrelid,
    			slon_quote_brute(PGN.nspname) || '.' ||
    			slon_quote_brute(PGC.relname) as tab_fqname
    			into v_tab_row
    			from sl_table T, sl_set S,
    				"pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN,
    				"pg_catalog".pg_index PGX, "pg_catalog".pg_class PGXC
    			where T.tab_id = p_tab_id
    				and T.tab_set = S.set_id
    				and T.tab_reloid = PGC.oid
    				and PGC.relnamespace = PGN.oid
    				and PGX.indrelid = T.tab_reloid
    				and PGX.indexrelid = PGXC.oid
    				and PGXC.relname = T.tab_idxname
    				for update;
    	if not found then
    		raise exception 'Slony-I: alterTableAddTriggers(): Table with id % not found', p_tab_id;
    	end if;
    	v_tab_fqname = v_tab_row.tab_fqname;

    	v_tab_attkind := determineAttKindUnique(v_tab_row.tab_fqname,
    						v_tab_row.tab_idxname);

    	execute 'lock table ' || v_tab_fqname || ' in access exclusive mode';

    	-- ----
    	-- Create the log and the deny access triggers
    	-- ----
    	execute 'create trigger "_schemadoc_logtrigger"' ||
    			' after insert or update or delete on ' ||
    			v_tab_fqname || ' for each row execute procedure logTrigger (' ||
                                   pg_catalog.quote_literal('_schemadoc') || ',' ||
    				pg_catalog.quote_literal(p_tab_id::text) || ',' ||
    				pg_catalog.quote_literal(v_tab_attkind) || ');';

    	execute 'create trigger "_schemadoc_denyaccess" ' ||
    			'before insert or update or delete on ' ||
    			v_tab_fqname || ' for each row execute procedure ' ||
    			'denyAccess (' || pg_catalog.quote_literal('_schemadoc') || ');';

    	perform alterTableAddTruncateTrigger(v_tab_fqname, p_tab_id);

    	perform alterTableConfigureTriggers (p_tab_id);
    	return p_tab_id;
    end;

## altertableconfiguretriggers(p_tab_id integer)

altertableconfiguretriggers(p_tab_id integer)

### Function Properties

Language Return Type PLPGSQL integer

alterTableConfigureTriggers (tab_id)

Set the enable/disable configuration for the replication triggers according to the origin of the set.

    declare
    	v_no_id				int4;
    	v_tab_row			record;
    	v_tab_fqname		text;
    	v_n					int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Get our local node ID
    	-- ----
    	v_no_id := getLocalNodeId('_schemadoc');

    	-- ----
    	-- Get the sl_table row and the current tables origin.
    	-- ----
    	select T.tab_reloid, T.tab_set,
    			S.set_origin, PGX.indexrelid,
    			slon_quote_brute(PGN.nspname) || '.' ||
    			slon_quote_brute(PGC.relname) as tab_fqname
    			into v_tab_row
    			from sl_table T, sl_set S,
    				"pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN,
    				"pg_catalog".pg_index PGX, "pg_catalog".pg_class PGXC
    			where T.tab_id = p_tab_id
    				and T.tab_set = S.set_id
    				and T.tab_reloid = PGC.oid
    				and PGC.relnamespace = PGN.oid
    				and PGX.indrelid = T.tab_reloid
    				and PGX.indexrelid = PGXC.oid
    				and PGXC.relname = T.tab_idxname
    				for update;
    	if not found then
    		raise exception 'Slony-I: alterTableConfigureTriggers(): Table with id % not found', p_tab_id;
    	end if;
    	v_tab_fqname = v_tab_row.tab_fqname;

    	-- ----
    	-- Configuration depends on the origin of the table
    	-- ----
    	if v_tab_row.set_origin = v_no_id then
    		-- ----
    		-- On the origin the log trigger is configured like a default
    		-- user trigger and the deny access trigger is disabled.
    		-- ----
    		execute 'alter table ' || v_tab_fqname ||
    				' enable trigger "_schemadoc_logtrigger"';
    		execute 'alter table ' || v_tab_fqname ||
    				' disable trigger "_schemadoc_denyaccess"';
            perform alterTableConfigureTruncateTrigger(v_tab_fqname,
    				'enable', 'disable');
    	else
    		-- ----
    		-- On a replica the log trigger is disabled and the
    		-- deny access trigger fires in origin session role.
    		-- ----
    		execute 'alter table ' || v_tab_fqname ||
    				' disable trigger "_schemadoc_logtrigger"';
    		execute 'alter table ' || v_tab_fqname ||
    				' enable trigger "_schemadoc_denyaccess"';
            perform alterTableConfigureTruncateTrigger(v_tab_fqname,
    				'disable', 'enable');

    	end if;

    	return p_tab_id;
    end;

## altertabledroptriggers(p_tab_id integer)

altertabledroptriggers(p_tab_id integer)

### Function Properties

Language Return Type PLPGSQL integer

alterTableDropTriggers (tab_id)

Remove the log and deny access triggers from a table.

    declare
    	v_no_id				int4;
    	v_tab_row			record;
    	v_tab_fqname		text;
    	v_n					int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Get our local node ID
    	-- ----
    	v_no_id := getLocalNodeId('_schemadoc');

    	-- ----
    	-- Get the sl_table row and the current tables origin.
    	-- ----
    	select T.tab_reloid, T.tab_set,
    			S.set_origin, PGX.indexrelid,
    			slon_quote_brute(PGN.nspname) || '.' ||
    			slon_quote_brute(PGC.relname) as tab_fqname
    			into v_tab_row
    			from sl_table T, sl_set S,
    				"pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN,
    				"pg_catalog".pg_index PGX, "pg_catalog".pg_class PGXC
    			where T.tab_id = p_tab_id
    				and T.tab_set = S.set_id
    				and T.tab_reloid = PGC.oid
    				and PGC.relnamespace = PGN.oid
    				and PGX.indrelid = T.tab_reloid
    				and PGX.indexrelid = PGXC.oid
    				and PGXC.relname = T.tab_idxname
    				for update;
    	if not found then
    		raise exception 'Slony-I: alterTableDropTriggers(): Table with id % not found', p_tab_id;
    	end if;
    	v_tab_fqname = v_tab_row.tab_fqname;

    	execute 'lock table ' || v_tab_fqname || ' in access exclusive mode';

    	-- ----
    	-- Drop both triggers
    	-- ----
    	execute 'drop trigger "_schemadoc_logtrigger" on ' ||
    			v_tab_fqname;

    	execute 'drop trigger "_schemadoc_denyaccess" on ' ||
    			v_tab_fqname;

    	perform alterTableDropTruncateTrigger(v_tab_fqname, p_tab_id);

    	return p_tab_id;
    end;

## check_table_field_exists(p_field text, p_table text, p_namespace text)

check_table_field_exists(p_field text, p_table text, p_namespace text)

### Function Properties

Language Return Type PLPGSQL boolean

Check if a table has a specific attribute

    BEGIN
    	return exists (
    			select 1 from "information_schema".columns
    				where table_schema = p_namespace
    				and table_name = p_table
    				and column_name = p_field
    		);
    END;

## check_unconfirmed_log()

check_unconfirmed_log()

### Function Properties

Language Return Type PLPGSQL boolean

    declare
    	v_rc		bool = false;
    	v_error		bool = false;
    	v_origin	integer;
    	v_allconf	bigint;
    	v_allsnap	txid_snapshot;
    	v_count		bigint;
    begin
    	--
    	-- Loop over all nodes that are the origin of at least one set
    	--
    	for v_origin in select distinct set_origin as no_id
    			from sl_set loop
    		--
    		-- Per origin determine which is the highest event seqno
    		-- that is confirmed by all subscribers to any of the
    		-- origins sets.
    		--
    		select into v_allconf min(max_seqno) from (
    				select con_received, max(con_seqno) as max_seqno
    					from sl_confirm
    					where con_origin = v_origin
    					and con_received in (
    						select distinct sub_receiver
    							from sl_set as SET,
    								sl_subscribe as SUB
    							where SET.set_id = SUB.sub_set
    							and SET.set_origin = v_origin
    						)
    					group by con_received
    			) as maxconfirmed;
    		if not found then
    			raise NOTICE 'check_unconfirmed_log(): cannot determine highest ev_seqno for node % confirmed by all subscribers', v_origin;
    			v_error = true;
    			continue;
    		end if;

    		--
    		-- Get the txid snapshot that corresponds with that event
    		--
    		select into v_allsnap ev_snapshot
    			from sl_event
    			where ev_origin = v_origin
    			and ev_seqno = v_allconf;
    		if not found then
    			raise NOTICE 'check_unconfirmed_log(): cannot find event %,% in sl_event', v_origin, v_allconf;
    			v_error = true;
    			continue;
    		end if;

    		--
    		-- Count the number of log rows that appeard after that event.
    		--
    		select into v_count count(*) from (
    			select 1 from sl_log_1
    				where log_origin = v_origin
    				and log_txid >= "pg_catalog".txid_snapshot_xmax(v_allsnap)
    			union all
    			select 1 from sl_log_1
    				where log_origin = v_origin
    				and log_txid in (
    					select * from "pg_catalog".txid_snapshot_xip(v_allsnap)
    				)
    			union all
    			select 1 from sl_log_2
    				where log_origin = v_origin
    				and log_txid >= "pg_catalog".txid_snapshot_xmax(v_allsnap)
    			union all
    			select 1 from sl_log_2
    				where log_origin = v_origin
    				and log_txid in (
    					select * from "pg_catalog".txid_snapshot_xip(v_allsnap)
    				)
    		) as cnt;

    		if v_count > 0 then
    			raise NOTICE 'check_unconfirmed_log(): origin % has % log rows that have not propagated to all subscribers yet', v_origin, v_count;
    			v_rc = true;
    		end if;
    	end loop;

    	if v_error then
    		raise EXCEPTION 'check_unconfirmed_log(): aborting due to previous inconsistency';
    	end if;

    	return v_rc;
    end;

## checkmoduleversion()

checkmoduleversion()

### Function Properties

Language Return Type PLPGSQL text

Inline test function that verifies that slonik request for STORE NODE/INIT CLUSTER is being run against a conformant set of schema/functions.

    declare
      moduleversion	text;
    begin
      select into moduleversion getModuleVersion();
      if moduleversion <> '@MODULEVERSION@' then
          raise exception 'Slonik version: @MODULEVERSION@ != Slony-I version in PG build %',
                 moduleversion;
      end if;
      return null;
    end;

## cleanupevent(p_interval interval)

cleanupevent(p_interval interval)

### Function Properties

Language Return Type PLPGSQL integer

cleaning old data out of sl_confirm, sl_event. Removes all but the last sl_confirm row per (origin,receiver), and then removes all events that are confirmed by all nodes in the whole cluster up to the last SYNC.

    declare
    	v_max_row	record;
    	v_min_row	record;
    	v_max_sync	int8;
    	v_origin	int8;
    	v_seqno		int8;
    	v_xmin		bigint;
    	v_rc            int8;
    begin
    	-- ----
    	-- First remove all confirmations where origin/receiver no longer exist
    	-- ----
    	delete from sl_confirm
    				where con_origin not in (select no_id from sl_node);
    	delete from sl_confirm
    				where con_received not in (select no_id from sl_node);
    	-- ----
    	-- Next remove all but the oldest confirm row per origin,receiver pair.
    	-- Ignore confirmations that are younger than 10 minutes. We currently
    	-- have an not confirmed suspicion that a possibly lost transaction due
    	-- to a server crash might have been visible to another session, and
    	-- that this led to log data that is needed again got removed.
    	-- ----
    	for v_max_row in select con_origin, con_received, max(con_seqno) as con_seqno
    				from sl_confirm
    				where con_timestamp < (CURRENT_TIMESTAMP - p_interval)
    				group by con_origin, con_received
    	loop
    		delete from sl_confirm
    				where con_origin = v_max_row.con_origin
    				and con_received = v_max_row.con_received
    				and con_seqno < v_max_row.con_seqno;
    	end loop;

    	-- ----
    	-- Then remove all events that are confirmed by all nodes in the
    	-- whole cluster up to the last SYNC
    	-- ----
    	for v_min_row in select con_origin, min(con_seqno) as con_seqno
    				from sl_confirm
    				group by con_origin
    	loop
    		select coalesce(max(ev_seqno), 0) into v_max_sync
    				from sl_event
    				where ev_origin = v_min_row.con_origin
    				and ev_seqno <= v_min_row.con_seqno
    				and ev_type = 'SYNC';
    		if v_max_sync > 0 then
    			delete from sl_event
    					where ev_origin = v_min_row.con_origin
    					and ev_seqno < v_max_sync;
    		end if;
    	end loop;

    	-- ----
    	-- If cluster has only one node, then remove all events up to
    	-- the last SYNC - Bug #1538
            -- http://gborg.postgresql.org/project/slony1/bugs/bugupdate.php?1538
    	-- ----

    	select * into v_min_row from sl_node where
    			no_id <> getLocalNodeId('_schemadoc') limit 1;
    	if not found then
    		select ev_origin, ev_seqno into v_min_row from sl_event
    		where ev_origin = getLocalNodeId('_schemadoc')
    		order by ev_origin desc, ev_seqno desc limit 1;
    		raise notice 'Slony-I: cleanupEvent(): Single node - deleting events < %', v_min_row.ev_seqno;
    			delete from sl_event
    			where
    				ev_origin = v_min_row.ev_origin and
    				ev_seqno < v_min_row.ev_seqno;

            end if;

    	if exists (select * from "pg_catalog".pg_class c, "pg_catalog".pg_namespace n, "pg_catalog".pg_attribute a where c.relname = 'sl_seqlog' and n.oid = c.relnamespace and a.attrelid = c.oid and a.attname = 'oid') then
                    execute 'alter table sl_seqlog set without oids;';
    	end if;
    	-- ----
    	-- Also remove stale entries from the nodelock table.
    	-- ----
    	perform cleanupNodelock();

    	-- ----
    	-- Find the eldest event left, for each origin
    	-- ----
        for v_origin, v_seqno, v_xmin in
    	select ev_origin, ev_seqno, "pg_catalog".txid_snapshot_xmin(ev_snapshot) from sl_event
              where (ev_origin, ev_seqno) in (select ev_origin, min(ev_seqno) from sl_event where ev_type = 'SYNC' group by ev_origin)
    	loop
    		delete from sl_seqlog where seql_origin = v_origin and seql_ev_seqno < v_seqno;
    		delete from sl_log_script where log_origin = v_origin and log_txid < v_xmin;
        end loop;

    	v_rc := logswitch_finish();
    	if v_rc = 0 then   -- no switch in progress
    		perform logswitch_start();
    	end if;

    	return 0;
    end;

## cleanupnodelock()

cleanupnodelock()

### Function Properties

Language Return Type PLPGSQL integer

Clean up stale entries when restarting slon

    declare
    	v_row		record;
    begin
    	for v_row in select nl_nodeid, nl_conncnt, nl_backendpid
    			from sl_nodelock
    			for update
    	loop
    		if killBackend(v_row.nl_backendpid, 'NULL') < 0 then
    			raise notice 'Slony-I: cleanup stale sl_nodelock entry for pid=%',
    					v_row.nl_backendpid;
    			delete from sl_nodelock where
    					nl_nodeid = v_row.nl_nodeid and
    					nl_conncnt = v_row.nl_conncnt;
    		end if;
    	end loop;

    	return 0;
    end;

## clonenodefinish(p_no_provider integer, p_no_id integer)

clonenodefinish(p_no_provider integer, p_no_id integer)

### Function Properties

Language Return Type PLPGSQL integer

Internal part of cloneNodePrepare().

    declare
    	v_row			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	perform "pg_catalog".setval('sl_local_node_id', p_no_id);
    	perform resetSession();
    	for v_row in select sub_set from sl_subscribe
    			where sub_receiver = p_no_id
    	loop
    		perform updateReloid(v_row.sub_set, p_no_id);
    	end loop;

    	perform RebuildListenEntries();

    	delete from sl_confirm
    		where con_received = p_no_id;
    	insert into sl_confirm
    		(con_origin, con_received, con_seqno, con_timestamp)
    		select con_origin, p_no_id, con_seqno, con_timestamp
    		from sl_confirm
    		where con_received = p_no_provider;
    	insert into sl_confirm
    		(con_origin, con_received, con_seqno, con_timestamp)
    		select p_no_provider, p_no_id,
    				(select max(ev_seqno) from sl_event
    					where ev_origin = p_no_provider), current_timestamp;

    	return 0;
    end;

## clonenodeprepare(p_no_comment integer, p_no_provider integer, p_no_id text)

clonenodeprepare(p_no_comment integer, p_no_provider integer, p_no_id text)

### Function Properties

Language Return Type PLPGSQL bigint

Prepare for cloning a node.

    begin
    	perform cloneNodePrepare_int (p_no_id, p_no_provider, p_no_comment);
    	return  createEvent('_schemadoc', 'CLONE_NODE',
    									p_no_id::text, p_no_provider::text,
    									p_no_comment::text);
    end;

## clonenodeprepare_int(p_no_comment integer, p_no_provider integer, p_no_id text)

clonenodeprepare_int(p_no_comment integer, p_no_provider integer, p_no_id text)

### Function Properties

Language Return Type PLPGSQL integer

Internal part of cloneNodePrepare().

    declare
       v_dummy int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	update sl_node set
    	       no_active = np.no_active,
    	       no_comment = np.no_comment,
    	       no_failed = np.no_failed
    	       from sl_node np
    	       where np.no_id = p_no_provider
    	       and sl_node.no_id = p_no_id;
    	if not found then
    	   insert into sl_node
    		(no_id, no_active, no_comment,no_failed)
    		select p_no_id, no_active, p_no_comment, no_failed
    		from sl_node
    		where no_id = p_no_provider;
    	end if;

           insert into sl_path
    	    (pa_server, pa_client, pa_conninfo, pa_connretry)
    	    select pa_server, p_no_id, '<event pending>', pa_connretry
    	    from sl_path
    	    where pa_client = p_no_provider
    	    and (pa_server, p_no_id) not in (select pa_server, pa_client
    	    	    from sl_path);

           insert into sl_path
    	    (pa_server, pa_client, pa_conninfo, pa_connretry)
    	    select p_no_id, pa_client, '<event pending>', pa_connretry
    	    from sl_path
    	    where pa_server = p_no_provider
    	    and (p_no_id, pa_client) not in (select pa_server, pa_client
    	    	    from sl_path);

    	insert into sl_subscribe
    		(sub_set, sub_provider, sub_receiver, sub_forward, sub_active)
    		select sub_set, sub_provider, p_no_id, sub_forward, sub_active
    		from sl_subscribe
    		where sub_receiver = p_no_provider;

    	insert into sl_confirm
    		(con_origin, con_received, con_seqno, con_timestamp)
    		select con_origin, p_no_id, con_seqno, con_timestamp
    		from sl_confirm
    		where con_received = p_no_provider;

    	perform RebuildListenEntries();

    	return 0;
    end;

## component_state(i_eventtype text, i_event integer, i_starttime integer, i_activity integer, i_conn_pid text, i_node timestamp with time zone, i_pid bigint, i_actor text)

component_state(i_eventtype text, i_event integer, i_starttime integer, i_activity integer, i_conn_pid text, i_node timestamp with time zone, i_pid bigint, i_actor text)

### Function Properties

Language Return Type PLPGSQL integer

Store state of a Slony component. Useful for monitoring

    begin
    	-- Trim out old state for this component
    	if not exists (select 1 from sl_components where co_actor = i_actor) then
    	   insert into sl_components
                 (co_actor, co_pid, co_node, co_connection_pid, co_activity, co_starttime, co_event, co_eventtype)
    	   values
                  (i_actor, i_pid, i_node, i_conn_pid, i_activity, i_starttime, i_event, i_eventtype);
    	else
    	   update sl_components
                  set
                     co_connection_pid = i_conn_pid, co_activity = i_activity, co_starttime = i_starttime, co_event = i_event,
                     co_eventtype = i_eventtype
                  where co_actor = i_actor
    	      	    and co_starttime < i_starttime;
    	end if;
    	return 1;
    end

## copyfields(p_tab_id integer)

copyfields(p_tab_id integer)

### Function Properties

Language Return Type PLPGSQL text

Return a string consisting of what should be appended to a COPY statement to specify fields for the passed-in tab_id.

In PG versions > 7.3, this looks like (field1,field2,...fieldn)

    declare
    	result text;
    	prefix text;
    	prec record;
    begin
    	result := '';
    	prefix := '(';   -- Initially, prefix is the opening paren

    	for prec in select slon_quote_input(a.attname) as column from sl_table t, pg_catalog.pg_attribute a where t.tab_id = p_tab_id and t.tab_reloid = a.attrelid and a.attnum > 0 and a.attisdropped = false order by attnum
    	loop
    		result := result || prefix || prec.column;
    		prefix := ',';   -- Subsequently, prepend columns with commas
    	end loop;
    	result := result || ')';
    	return result;
    end;

## ddlcapture(p_nodes text, p_statement text)

ddlcapture(p_nodes text, p_statement text)

### Function Properties

Language Return Type PLPGSQL bigint

Capture an SQL statement (usually DDL) that is to be literally replayed on subscribers

    declare
    	c_local_node	integer;
    	c_found_origin	boolean;
    	c_node			text;
    	c_cmdargs		text[];
    	c_nodeargs      text;
    	c_delim         text;
    begin
    	c_local_node := getLocalNodeId('_schemadoc');

    	c_cmdargs = array_append('{}'::text[], p_statement);
    	c_nodeargs = '';
    	if p_nodes is not null then
    		c_found_origin := 'f';
    		-- p_nodes list needs to consist of a list of nodes that exist
    		-- and that include the current node ID
    		for c_node in select trim(node) from
    				pg_catalog.regexp_split_to_table(p_nodes, ',') as node loop
    			if not exists
    					(select 1 from sl_node
    					where no_id = (c_node::integer)) then
    				raise exception 'ddlcapture(%,%) - node % does not exist!',
    					p_statement, p_nodes, c_node;
    		   end if;

    		   if c_local_node = (c_node::integer) then
    		   	  c_found_origin := 't';
    		   end if;
    		   if length(c_nodeargs)>0 then
    		   	  c_nodeargs = c_nodeargs ||','|| c_node;
    		   else
    				c_nodeargs=c_node;
    			end if;
    	   end loop;

    		if not c_found_origin then
    			raise exception
    				'ddlcapture(%,%) - origin node % not included in ONLY ON list!',
    				p_statement, p_nodes, c_local_node;
           end if;
        end if;
    	c_cmdargs = array_append(c_cmdargs,c_nodeargs);
    	c_delim=',';
    	c_cmdargs = array_append(c_cmdargs,

               (select string_agg( seq_id::text || c_delim
    		   || c_local_node ||
    		    c_delim || seq_last_value)
    		    FROM (
    		       select seq_id,
               	   seq_last_value from sl_seqlastvalue
               	   where seq_origin = c_local_node) as FOO
    			where NOT seqtrack(seq_id,seq_last_value) is NULL));
    	insert into sl_log_script
    			(log_origin, log_txid, log_actionseq, log_cmdtype, log_cmdargs)
    		values
    			(c_local_node, pg_catalog.txid_current(),
    			nextval('sl_action_seq'), 'S', c_cmdargs);
    	execute p_statement;
    	return currval('sl_action_seq');
    end;

## ddlscript_complete(p_nodes text)

ddlscript_complete(p_nodes text)

### Function Properties

Language Return Type PLPGSQL bigint

ddlScript_complete(set_id, script, only_on_node)

After script has run on origin, this fixes up relnames and log trigger arguments and inserts the "fire ddlScript_complete_int() log row into sl_log_script.

    declare
    	c_local_node	integer;
    	c_found_origin	boolean;
    	c_node			text;
    	c_cmdargs		text[];
    begin
    	c_local_node := getLocalNodeId('_schemadoc');

    	c_cmdargs = '{}'::text[];
    	if p_nodes is not null then
    		c_found_origin := 'f';
    		-- p_nodes list needs to consist o a list of nodes that exist
    		-- and that include the current node ID
    		for c_node in select trim(node) from
    				pg_catalog.regexp_split_to_table(p_nodes, ',') as node loop
    			if not exists
    					(select 1 from sl_node
    					where no_id = (c_node::integer)) then
    				raise exception 'ddlcapture(%,%) - node % does not exist!',
    					p_statement, p_nodes, c_node;
    		   end if;

    		   if c_local_node = (c_node::integer) then
    		   	  c_found_origin := 't';
    		   end if;

    		   c_cmdargs = array_append(c_cmdargs, c_node);
    	   end loop;

    		if not c_found_origin then
    			raise exception
    				'ddlScript_complete(%) - origin node % not included in ONLY ON list!',
    				p_nodes, c_local_node;
           end if;
        end if;

    	perform ddlScript_complete_int();

    	insert into sl_log_script
    			(log_origin, log_txid, log_actionseq, log_cmdtype, log_cmdargs)
    		values
    			(c_local_node, pg_catalog.txid_current(),
    			nextval('sl_action_seq'), 's', c_cmdargs);

    	return currval('sl_action_seq');
    end;

## ddlscript_complete_int()

ddlscript_complete_int()

### Function Properties

Language Return Type PLPGSQL integer

ddlScript_complete_int()

Complete processing the DDL_SCRIPT event.

    begin
    	perform updateRelname();
    	perform repair_log_triggers(true);
    	return 0;
    end;

## deny_truncate()

deny_truncate()

### Function Properties

Language Return Type PLPGSQL trigger

trigger function run when a replicated table receives a TRUNCATE request

    	declare
    		r_role text;
    	begin
    		-- Ignore this call if session_replication_role = 'local'
    		select into r_role setting
    			from pg_catalog.pg_settings where name = 'session_replication_role';
    		if r_role = 'local' then
    			return NULL;
    		end if;

    		raise exception 'truncation of replicated table forbidden on subscriber node';
        end

## determineattkindunique(p_idx_name text, p_tab_fqname name)

determineattkindunique(p_idx_name text, p_tab_fqname name)

### Function Properties

Language Return Type PLPGSQL text

determineAttKindUnique (tab_fqname, indexname)

Given a tablename, return the Slony-I specific attkind (used for the log trigger) of the table. Use the specified unique index or the primary key (if indexname is NULL).

    declare
    	v_tab_fqname_quoted	text default '';
    	v_idx_name_quoted	text;
    	v_idxrow		record;
    	v_attrow		record;
    	v_i				integer;
    	v_attno			int2;
    	v_attkind		text default '';
    	v_attfound		bool;
    begin
    	v_tab_fqname_quoted := slon_quote_input(p_tab_fqname);
    	v_idx_name_quoted := slon_quote_brute(p_idx_name);
    	--
    	-- Ensure that the table exists
    	--
    	if (select PGC.relname
    				from "pg_catalog".pg_class PGC,
    					"pg_catalog".pg_namespace PGN
    				where slon_quote_brute(PGN.nspname) || '.' ||
    					slon_quote_brute(PGC.relname) = v_tab_fqname_quoted
    					and PGN.oid = PGC.relnamespace) is null then
    		raise exception 'Slony-I: table % not found', v_tab_fqname_quoted;
    	end if;

    	--
    	-- Lookup the tables primary key or the specified unique index
    	--
    	if p_idx_name isnull then
    		raise exception 'Slony-I: index name must be specified';
    	else
    		select PGXC.relname, PGX.indexrelid, PGX.indkey
    				into v_idxrow
    				from "pg_catalog".pg_class PGC,
    					"pg_catalog".pg_namespace PGN,
    					"pg_catalog".pg_index PGX,
    					"pg_catalog".pg_class PGXC
    				where slon_quote_brute(PGN.nspname) || '.' ||
    					slon_quote_brute(PGC.relname) = v_tab_fqname_quoted
    					and PGN.oid = PGC.relnamespace
    					and PGX.indrelid = PGC.oid
    					and PGX.indexrelid = PGXC.oid
    					and PGX.indisunique
    					and slon_quote_brute(PGXC.relname) = v_idx_name_quoted;
    		if not found then
    			raise exception 'Slony-I: table % has no unique index %',
    					v_tab_fqname_quoted, v_idx_name_quoted;
    		end if;
    	end if;

    	--
    	-- Loop over the tables attributes and check if they are
    	-- index attributes. If so, add a "k" to the return value,
    	-- otherwise add a "v".
    	--
    	for v_attrow in select PGA.attnum, PGA.attname
    			from "pg_catalog".pg_class PGC,
    			    "pg_catalog".pg_namespace PGN,
    				"pg_catalog".pg_attribute PGA
    			where slon_quote_brute(PGN.nspname) || '.' ||
    			    slon_quote_brute(PGC.relname) = v_tab_fqname_quoted
    				and PGN.oid = PGC.relnamespace
    				and PGA.attrelid = PGC.oid
    				and not PGA.attisdropped
    				and PGA.attnum > 0
    			order by attnum
    	loop
    		v_attfound = 'f';

    		v_i := 0;
    		loop
    			select indkey[v_i] into v_attno from "pg_catalog".pg_index
    					where indexrelid = v_idxrow.indexrelid;
    			if v_attno isnull or v_attno = 0 then
    				exit;
    			end if;
    			if v_attrow.attnum = v_attno then
    				v_attfound = 't';
    				exit;
    			end if;
    			v_i := v_i + 1;
    		end loop;

    		if v_attfound then
    			v_attkind := v_attkind || 'k';
    		else
    			v_attkind := v_attkind || 'v';
    		end if;
    	end loop;

    	-- Strip off trailing v characters as they are not needed by the logtrigger
    	v_attkind := pg_catalog.rtrim(v_attkind, 'v');

    	--
    	-- Return the resulting attkind
    	--
    	return v_attkind;
    end;

## determineidxnameunique(p_idx_name text, p_tab_fqname name)

determineidxnameunique(p_idx_name text, p_tab_fqname name)

### Function Properties

Language Return Type PLPGSQL name

FUNCTION determineIdxnameUnique (tab_fqname, indexname)

Given a tablename, tab_fqname, check that the unique index, indexname, exists or return the primary key index name for the table. If there is no unique index, it raises an exception.

    declare
    	v_tab_fqname_quoted	text default '';
    	v_idxrow		record;
    begin
    	v_tab_fqname_quoted := slon_quote_input(p_tab_fqname);
    	--
    	-- Ensure that the table exists
    	--
    	if (select PGC.relname
    				from "pg_catalog".pg_class PGC,
    					"pg_catalog".pg_namespace PGN
    				where slon_quote_brute(PGN.nspname) || '.' ||
    					slon_quote_brute(PGC.relname) = v_tab_fqname_quoted
    					and PGN.oid = PGC.relnamespace) is null then
    		raise exception 'Slony-I: determineIdxnameUnique(): table % not found', v_tab_fqname_quoted;
    	end if;

    	--
    	-- Lookup the tables primary key or the specified unique index
    	--
    	if p_idx_name isnull then
    		select PGXC.relname
    				into v_idxrow
    				from "pg_catalog".pg_class PGC,
    					"pg_catalog".pg_namespace PGN,
    					"pg_catalog".pg_index PGX,
    					"pg_catalog".pg_class PGXC
    				where slon_quote_brute(PGN.nspname) || '.' ||
    					slon_quote_brute(PGC.relname) = v_tab_fqname_quoted
    					and PGN.oid = PGC.relnamespace
    					and PGX.indrelid = PGC.oid
    					and PGX.indexrelid = PGXC.oid
    					and PGX.indisprimary;
    		if not found then
    			raise exception 'Slony-I: table % has no primary key',
    					v_tab_fqname_quoted;
    		end if;
    	else
    		select PGXC.relname
    				into v_idxrow
    				from "pg_catalog".pg_class PGC,
    					"pg_catalog".pg_namespace PGN,
    					"pg_catalog".pg_index PGX,
    					"pg_catalog".pg_class PGXC
    				where slon_quote_brute(PGN.nspname) || '.' ||
    					slon_quote_brute(PGC.relname) = v_tab_fqname_quoted
    					and PGN.oid = PGC.relnamespace
    					and PGX.indrelid = PGC.oid
    					and PGX.indexrelid = PGXC.oid
    					and PGX.indisunique
    					and slon_quote_brute(PGXC.relname) = slon_quote_input(p_idx_name);
    		if not found then
    			raise exception 'Slony-I: table % has no unique index %',
    					v_tab_fqname_quoted, p_idx_name;
    		end if;
    	end if;

    	--
    	-- Return the found index name
    	--
    	return v_idxrow.relname;
    end;

## disable_indexes_on_table(i_oid oid)

disable_indexes_on_table(i_oid oid)

### Function Properties

Language Return Type PLPGSQL integer

disable indexes on the specified table. Used during subscription process to suppress indexes, which allows COPY to go much faster.

This may be set as a SECURITY DEFINER in order to eliminate the need for superuser access by Slony-I.

    begin
    	-- Setting pg_class.relhasindex to false will cause copy not to
    	-- maintain any indexes. At the end of the copy we will reenable
    	-- them and reindex the table. This bulk creating of indexes is
    	-- faster.

    	update pg_catalog.pg_class set relhasindex ='f' where oid = i_oid;
    	return 1;
    end

## disablenode(p_no_id integer)

disablenode(p_no_id integer)

### Function Properties

Language Return Type PLPGSQL bigint

process DISABLE_NODE event for node no_id

NOTE: This is not yet implemented!

    begin
    	-- **** TODO ****
    	raise exception 'Slony-I: disableNode() not implemented';
    end;

## disablenode_int(p_no_id integer)

disablenode_int(p_no_id integer)

### Function Properties

Language Return Type PLPGSQL integer

    begin
    	-- **** TODO ****
    	raise exception 'Slony-I: disableNode_int() not implemented';
    end;

## droplisten(p_li_receiver integer, p_li_provider integer, p_li_origin integer)

droplisten(p_li_receiver integer, p_li_provider integer, p_li_origin integer)

### Function Properties

Language Return Type PLPGSQL bigint

dropListen (li_origin, li_provider, li_receiver)

Generate the DROP_LISTEN event.

    begin
    	perform dropListen_int(p_li_origin,
    			p_li_provider, p_li_receiver);

    	return  createEvent ('_schemadoc', 'DROP_LISTEN',
    			p_li_origin::text, p_li_provider::text, p_li_receiver::text);
    end;

## droplisten_int(p_li_receiver integer, p_li_provider integer, p_li_origin integer)

droplisten_int(p_li_receiver integer, p_li_provider integer, p_li_origin integer)

### Function Properties

Language Return Type PLPGSQL integer

dropListen (li_origin, li_provider, li_receiver)

Process the DROP_LISTEN event, deleting the sl_listen entry for the indicated (origin,provider,receiver) combination.

    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	delete from sl_listen
    			where li_origin = p_li_origin
    			and li_provider = p_li_provider
    			and li_receiver = p_li_receiver;
    	if found then
    		return 1;
    	else
    		return 0;
    	end if;
    end;

## dropnode(p_no_ids integer[])

dropnode(p_no_ids integer[])

### Function Properties

Language Return Type PLPGSQL bigint

generate DROP_NODE event to drop node node_id from replication

    declare
    	v_node_row		record;
    	v_idx         integer;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that this got called on a different node
    	-- ----
    	if  getLocalNodeId('_schemadoc') = ANY (p_no_ids) then
    		raise exception 'Slony-I: DROP_NODE cannot initiate on the dropped node';
    	end if;

    	--
    	-- if any of the deleted nodes are receivers we drop the sl_subscribe line
    	--
    	delete from sl_subscribe where sub_receiver = ANY (p_no_ids);

    	v_idx:=1;
    	LOOP
    	  EXIT WHEN v_idx>array_upper(p_no_ids,1) ;
    	  select * into v_node_row from sl_node
    			where no_id = p_no_ids[v_idx]
    			for update;
    	  if not found then
    		 raise exception 'Slony-I: unknown node ID % %', p_no_ids[v_idx],v_idx;
    	  end if;
    	  -- ----
    	  -- Make sure we do not break other nodes subscriptions with this
    	  -- ----
    	  if exists (select true from sl_subscribe
    			where sub_provider = p_no_ids[v_idx])
    	  then
    		raise exception 'Slony-I: Node % is still configured as a data provider',
    				p_no_ids[v_idx];
    	  end if;

    	  -- ----
    	  -- Make sure no set originates there any more
    	  -- ----
    	  if exists (select true from sl_set
    			where set_origin = p_no_ids[v_idx])
    	  then
    	  	  raise exception 'Slony-I: Node % is still origin of one or more sets',
    				p_no_ids[v_idx];
    	  end if;

    	  -- ----
    	  -- Call the internal drop functionality and generate the event
    	  -- ----
    	  perform dropNode_int(p_no_ids[v_idx]);
    	  v_idx:=v_idx+1;
    	END LOOP;
    	return  createEvent('_schemadoc', 'DROP_NODE',
    									array_to_string(p_no_ids,','));
    end;

## dropnode_int(p_no_id integer)

dropnode_int(p_no_id integer)

### Function Properties

Language Return Type PLPGSQL integer

internal function to process DROP_NODE event to drop node node_id from replication

    declare
    	v_tab_row		record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- If the dropped node is a remote node, clean the configuration
    	-- from all traces for it.
    	-- ----
    	if p_no_id <> getLocalNodeId('_schemadoc') then
    		delete from sl_subscribe
    				where sub_receiver = p_no_id;
    		delete from sl_listen
    				where li_origin = p_no_id
    					or li_provider = p_no_id
    					or li_receiver = p_no_id;
    		delete from sl_path
    				where pa_server = p_no_id
    					or pa_client = p_no_id;
    		delete from sl_confirm
    				where con_origin = p_no_id
    					or con_received = p_no_id;
    		delete from sl_event
    				where ev_origin = p_no_id;
    		delete from sl_node
    				where no_id = p_no_id;

    		return p_no_id;
    	end if;

    	-- ----
    	-- This is us ... deactivate the node for now, the daemon
    	-- will call uninstallNode() in a separate transaction.
    	-- ----
    	update sl_node
    			set no_active = false
    			where no_id = p_no_id;

    	-- Rewrite sl_listen table
    	perform RebuildListenEntries();

    	return p_no_id;
    end;

## droppath(p_pa_client integer, p_pa_server integer)

droppath(p_pa_client integer, p_pa_server integer)

### Function Properties

Language Return Type PLPGSQL bigint

Generate DROP_PATH event to drop path from pa_server to pa_client

    declare
    	v_row			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- There should be no existing subscriptions. Auto unsubscribing
    	-- is considered too dangerous.
    	-- ----
    	for v_row in select sub_set, sub_provider, sub_receiver
    			from sl_subscribe
    			where sub_provider = p_pa_server
    			and sub_receiver = p_pa_client
    	loop
    		raise exception
    			'Slony-I: Path cannot be dropped, subscription of set % needs it',
    			v_row.sub_set;
    	end loop;

    	-- ----
    	-- Drop all sl_listen entries that depend on this path
    	-- ----
    	for v_row in select li_origin, li_provider, li_receiver
    			from sl_listen
    			where li_provider = p_pa_server
    			and li_receiver = p_pa_client
    	loop
    		perform dropListen(
    				v_row.li_origin, v_row.li_provider, v_row.li_receiver);
    	end loop;

    	-- ----
    	-- Now drop the path and create the event
    	-- ----
    	perform dropPath_int(p_pa_server, p_pa_client);

    	-- Rewrite sl_listen table
    	perform RebuildListenEntries();

    	return  createEvent ('_schemadoc', 'DROP_PATH',
    			p_pa_server::text, p_pa_client::text);
    end;

## droppath_int(p_pa_client integer, p_pa_server integer)

droppath_int(p_pa_client integer, p_pa_server integer)

### Function Properties

Language Return Type PLPGSQL integer

Process DROP_PATH event to drop path from pa_server to pa_client

    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Remove any dangling sl_listen entries with the server
    	-- as provider and the client as receiver. This must have
    	-- been cleared out before, but obviously was not.
    	-- ----
    	delete from sl_listen
    			where li_provider = p_pa_server
    			and li_receiver = p_pa_client;

    	delete from sl_path
    			where pa_server = p_pa_server
    			and pa_client = p_pa_client;

    	if found then
    		-- Rewrite sl_listen table
    		perform RebuildListenEntries();

    		return 1;
    	else
    		-- Rewrite sl_listen table
    		perform RebuildListenEntries();

    		return 0;
    	end if;
    end;

## dropset(p_set_id integer)

dropset(p_set_id integer)

### Function Properties

Language Return Type PLPGSQL bigint

Process DROP_SET event to drop replication of set set_id. This involves: - Removing log and deny access triggers - Removing all traces of the set configuration, including sequences, tables, subscribers, syncs, and the set itself

    declare
    	v_origin			int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that the set exists and originates here
    	-- ----
    	select set_origin into v_origin from sl_set
    			where set_id = p_set_id;
    	if not found then
    		raise exception 'Slony-I: set % not found', p_set_id;
    	end if;
    	if v_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: set % does not originate on local node',
    				p_set_id;
    	end if;

    	-- ----
    	-- Call the internal drop set functionality and generate the event
    	-- ----
    	perform dropSet_int(p_set_id);
    	return  createEvent('_schemadoc', 'DROP_SET',
    			p_set_id::text);
    end;

## dropset_int(p_set_id integer)

dropset_int(p_set_id integer)

### Function Properties

Language Return Type PLPGSQL integer

    declare
    	v_tab_row			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Restore all tables original triggers and rules and remove
    	-- our replication stuff.
    	-- ----
    	for v_tab_row in select tab_id from sl_table
    			where tab_set = p_set_id
    			order by tab_id
    	loop
    		perform alterTableDropTriggers(v_tab_row.tab_id);
    	end loop;

    	-- ----
    	-- Remove all traces of the set configuration
    	-- ----
    	delete from sl_sequence
    			where seq_set = p_set_id;
    	delete from sl_table
    			where tab_set = p_set_id;
    	delete from sl_subscribe
    			where sub_set = p_set_id;
    	delete from sl_setsync
    			where ssy_setid = p_set_id;
    	delete from sl_set
    			where set_id = p_set_id;

    	-- Regenerate sl_listen since we revised the subscriptions
    	perform RebuildListenEntries();

    	-- Run addPartialLogIndices() to try to add indices to unused sl_log_? table
    	perform addPartialLogIndices();

    	return p_set_id;
    end;

## enable_indexes_on_table(i_oid oid)

enable_indexes_on_table(i_oid oid)

### Function Properties

Language Return Type PLPGSQL integer

re-enable indexes on the specified table.

This may be set as a SECURITY DEFINER in order to eliminate the need for superuser access by Slony-I.

    begin
    	update pg_catalog.pg_class set relhasindex ='t' where oid = i_oid;
    	return 1;
    end

## enablenode(p_no_id integer)

enablenode(p_no_id integer)

### Function Properties

Language Return Type PLPGSQL bigint

no_id - Node ID #

Generate the ENABLE_NODE event for node no_id

    declare
    	v_local_node_id	int4;
    	v_node_row		record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that we are the node to activate and that we are
    	-- currently disabled.
    	-- ----
    	v_local_node_id := getLocalNodeId('_schemadoc');
    	select * into v_node_row
    			from sl_node
    			where no_id = p_no_id
    			for update;
    	if not found then
    		raise exception 'Slony-I: node % not found', p_no_id;
    	end if;
    	if v_node_row.no_active then
    		raise exception 'Slony-I: node % is already active', p_no_id;
    	end if;

    	-- ----
    	-- Activate this node and generate the ENABLE_NODE event
    	-- ----
    	perform enableNode_int (p_no_id);
    	return  createEvent('_schemadoc', 'ENABLE_NODE',
    									p_no_id::text);
    end;

## enablenode_int(p_no_id integer)

enablenode_int(p_no_id integer)

### Function Properties

Language Return Type PLPGSQL integer

no_id - Node ID #

Internal function to process the ENABLE_NODE event for node no_id

    declare
    	v_local_node_id	int4;
    	v_node_row		record;
    	v_sub_row		record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that the node is inactive
    	-- ----
    	select * into v_node_row
    			from sl_node
    			where no_id = p_no_id
    			for update;
    	if not found then
    		raise exception 'Slony-I: node % not found', p_no_id;
    	end if;
    	if v_node_row.no_active then
    		return p_no_id;
    	end if;

    	-- ----
    	-- Activate the node and generate sl_confirm status rows for it.
    	-- ----
    	update sl_node
    			set no_active = 't'
    			where no_id = p_no_id;
    	insert into sl_confirm
    			(con_origin, con_received, con_seqno)
    			select no_id, p_no_id, 0 from sl_node
    				where no_id != p_no_id
    				and no_active;
    	insert into sl_confirm
    			(con_origin, con_received, con_seqno)
    			select p_no_id, no_id, 0 from sl_node
    				where no_id != p_no_id
    				and no_active;

    	-- ----
    	-- Generate ENABLE_SUBSCRIPTION events for all sets that
    	-- origin here and are subscribed by the just enabled node.
    	-- ----
    	v_local_node_id := getLocalNodeId('_schemadoc');
    	for v_sub_row in select SUB.sub_set, SUB.sub_provider from
    			sl_set S,
    			sl_subscribe SUB
    			where S.set_origin = v_local_node_id
    			and S.set_id = SUB.sub_set
    			and SUB.sub_receiver = p_no_id
    			for update of S
    	loop
    		perform enableSubscription (v_sub_row.sub_set,
    				v_sub_row.sub_provider, p_no_id);
    	end loop;

    	return p_no_id;
    end;

## enablesubscription(p_sub_receiver integer, p_sub_provider integer, p_sub_set integer)

enablesubscription(p_sub_receiver integer, p_sub_provider integer, p_sub_set integer)

### Function Properties

Language Return Type PLPGSQL integer

enableSubscription (sub_set, sub_provider, sub_receiver)

Indicates that sub_receiver intends subscribing to set sub_set from sub_provider. Work is all done by the internal function enableSubscription_int (sub_set, sub_provider, sub_receiver).

    begin
    	return  enableSubscription_int (p_sub_set,
    			p_sub_provider, p_sub_receiver);
    end;

## enablesubscription_int(p_sub_receiver integer, p_sub_provider integer, p_sub_set integer)

enablesubscription_int(p_sub_receiver integer, p_sub_provider integer, p_sub_set integer)

### Function Properties

Language Return Type PLPGSQL integer

enableSubscription_int (sub_set, sub_provider, sub_receiver)

Internal function to enable subscription of node sub_receiver to set sub_set via node sub_provider.

slon does most of the work; all we need do here is to remember that it happened. The function updates sl_subscribe, indicating that the subscription has become active.

    declare
    	v_n					int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- The real work is done in the replication engine. All
    	-- we have to do here is remembering that it happened.
    	-- ----

    	-- ----
    	-- Well, not only ... we might be missing an important event here
    	-- ----
    	if not exists (select true from sl_path
    			where pa_server = p_sub_provider
    			and pa_client = p_sub_receiver)
    	then
    		insert into sl_path
    				(pa_server, pa_client, pa_conninfo, pa_connretry)
    				values
    				(p_sub_provider, p_sub_receiver,
    				'<event pending>', 10);
    	end if;

    	update sl_subscribe
    			set sub_active = 't'
    			where sub_set = p_sub_set
    			and sub_receiver = p_sub_receiver;
    	get diagnostics v_n = row_count;
    	if v_n = 0 then
    		insert into sl_subscribe
    				(sub_set, sub_provider, sub_receiver,
    				sub_forward, sub_active)
    				values
    				(p_sub_set, p_sub_provider, p_sub_receiver,
    				false, true);
    	end if;

    	-- Rewrite sl_listen table
    	perform RebuildListenEntries();

    	return p_sub_set;
    end;

## failednode(p_failed_nodes integer, p_backup_node integer, p_failed_node integer[])

failednode(p_failed_nodes integer, p_backup_node integer, p_failed_node integer[])

### Function Properties

Language Return Type PLPGSQL integer

Initiate failover from failed_node to backup_node. This function must be called on all nodes, and then waited for the restart of all node daemons.

    declare
    	v_row				record;
    	v_row2				record;
    	v_failed					boolean;
        v_restart_required          boolean;
    begin

    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	v_restart_required:=false;
    	--
    	-- any nodes other than the backup receiving
    	-- ANY subscription from a failed node
    	-- will now get that data from the backup node.
    	update sl_subscribe set
    		   sub_provider=p_backup_node
    		   where sub_provider=p_failed_node
    		   and sub_receiver<>p_backup_node
    		   and sub_receiver <> ALL (p_failed_nodes);
    	if found then
    	   v_restart_required:=true;
    	end if;
    	--
    	-- if this node is receiving a subscription from the backup node
    	-- with a failed node as the provider we need to fix this.
    	update sl_subscribe set
    	        sub_provider=p_backup_node
    		from sl_set
    		where set_id = sub_set
    		and set_origin=p_failed_node
    		and sub_provider = ANY(p_failed_nodes)
    		and sub_receiver=getLocalNodeId('_schemadoc');

    	-- ----
    	-- Terminate all connections of the failed node the hard way
    	-- ----
    	perform terminateNodeConnections(p_failed_node);

    	-- Clear out the paths for the failed node.
    	-- This ensures that *this* node won't be pulling data from
    	-- the failed node even if it *does* become accessible

    	update sl_path set pa_conninfo='<event pending>' WHERE
    	   		  pa_server=p_failed_node
    			  and pa_conninfo<>'<event pending>';

    	if found then
    	   v_restart_required:=true;
    	end if;

    	v_failed := exists (select 1 from sl_node
    		   where no_failed=true and no_id=p_failed_node);

        if not v_failed then

    		update sl_node set no_failed=true where no_id = ANY (p_failed_nodes)
    			   and no_failed=false;
    		if found then
    	   	   v_restart_required:=true;
    		end if;
    	end if;

    	if v_restart_required then
    	  -- Rewrite sl_listen table
    	  perform RebuildListenEntries();

    	  -- ----
    	  -- Make sure the node daemon will restart
     	  -- ----
    	  notify "_schemadoc_Restart";
        end if;

    	-- ----
    	-- That is it - so far.
    	-- ----
    	return p_failed_node;
    end;

## failednode2(p_failed_nodes integer, p_ev_seqno integer, p_backup_node bigint, p_failed_node integer[])

failednode2(p_failed_nodes integer, p_ev_seqno integer, p_backup_node bigint, p_failed_node integer[])

### Function Properties

Language Return Type PLPGSQL bigint

FUNCTION failedNode2 (failed_node, backup_node, set_id, ev_seqno, ev_seqfake,p_failed_nodes)

On the node that has the highest sequence number of the failed node, fake the FAILOVER_SET event.

    declare
    	v_row				record;
    	v_new_event			bigint;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	select * into v_row
    			from sl_event
    			where ev_origin = p_failed_node
    			and ev_seqno = p_ev_seqno;
    	if not found then
    		raise exception 'Slony-I: event %,% not found',
    				p_failed_node, p_ev_seqno;
    	end if;

    	update sl_node set no_failed=true  where no_id = ANY
    	(p_failed_nodes) and no_failed=false;
    	-- Rewrite sl_listen table
    	perform RebuildListenEntries();
    	-- ----
    	-- Make sure the node daemon will restart
    	-- ----
    	raise notice 'calling restart node %',p_failed_node;

    	notify "_schemadoc_Restart";

    	select createEvent('_schemadoc','FAILOVER_NODE',
    								p_failed_node::text,p_ev_seqno::text,
    								array_to_string(p_failed_nodes,','))
    			into v_new_event;

    	return v_new_event;
    end;

## failednode3(p_seq_no integer, p_backup_node integer, p_failed_node bigint)

failednode3(p_seq_no integer, p_backup_node integer, p_failed_node bigint)

### Function Properties

Language Return Type PLPGSQL integer

    declare

    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	perform failoverSet_int(p_failed_node,
    		p_backup_node,p_seq_no);

    	notify "_schemadoc_Restart";
        return 0;
    end;

## failoverset_int(p_last_seqno integer, p_backup_node integer, p_failed_node bigint)

failoverset_int(p_last_seqno integer, p_backup_node integer, p_failed_node bigint)

### Function Properties

Language Return Type PLPGSQL integer

FUNCTION failoverSet_int (failed_node, backup_node, set_id, wait_seqno)

Finish failover for one set.

    declare
    	v_row				record;
    	v_last_sync			int8;
    	v_set				int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	SELECT max(ev_seqno) into v_last_sync FROM sl_event where
    		   ev_origin=p_failed_node;
    	if v_last_sync > p_last_seqno then
    	   -- this node is ahead of the last sequence number from the
    	   -- failed node that the backup node has.
    	   -- this node must unsubscribe from all sets from the origin.
    	   for v_set in select set_id from sl_set where
    		set_origin=p_failed_node
    		loop
    			raise warning 'Slony is dropping the subscription of set % found sync %s bigger than %s '
    			, v_set, v_last_sync::text, p_last_seqno::text;
    			perform unsubscribeSet(v_set,
    				   getLocalNodeId('_schemadoc'),
    				   true);
    		end loop;
    		delete from sl_event where ev_origin=p_failed_node
    			   and ev_seqno > p_last_seqno;
    	end if;
    	-- ----
    	-- Change the origin of the set now to the backup node.
    	-- On the backup node this includes changing all the
    	-- trigger and protection stuff
    	for v_set in select set_id from sl_set where
    		set_origin=p_failed_node
    	loop
    	-- ----
    	   if p_backup_node = getLocalNodeId('_schemadoc') then
    	   	  	delete from sl_setsync
    				where ssy_setid = v_set;
    			delete from sl_subscribe
    				where sub_set = v_set
    					and sub_receiver = p_backup_node;
    			update sl_set
    				set set_origin = p_backup_node
    				where set_id = v_set;
    			 update sl_subscribe
    						set sub_provider=p_backup_node
    					  	FROM sl_node receive_node
    					   where sub_set = v_set
    					   and sub_provider=p_failed_node
    					   and sub_receiver=receive_node.no_id
    					   and receive_node.no_failed=false;

    			for v_row in select * from sl_table
    				where tab_set = v_set
    				order by tab_id
    			loop
    				perform alterTableConfigureTriggers(v_row.tab_id);
    			end loop;
    	else
    		raise notice 'deleting from sl_subscribe all rows with receiver %',
    		p_backup_node;

    			delete from sl_subscribe
    					  where sub_set = v_set
    					  and sub_receiver = p_backup_node;

    			update sl_subscribe
    				 		set sub_provider=p_backup_node
    						FROM sl_node receive_node
    					   where sub_set = v_set
    					    and sub_provider=p_failed_node
    						 and sub_provider=p_failed_node
    					   and sub_receiver=receive_node.no_id
    					   and receive_node.no_failed=false;
    			update sl_set
    					   set set_origin = p_backup_node
    				where set_id = v_set;
    			-- ----
    			-- If we are a subscriber of the set ourself, change our
    			-- setsync status to reflect the new set origin.
    			-- ----
    			if exists (select true from sl_subscribe
    			   where sub_set = v_set
    			   	and sub_receiver = getLocalNodeId(
    						'_schemadoc'))
    			then
    				delete from sl_setsync
    					   where ssy_setid = v_set;

    				select coalesce(max(ev_seqno), 0) into v_last_sync
    					   from sl_event
    					   where ev_origin = p_backup_node
    					   and ev_type = 'SYNC';
    				if v_last_sync > 0 then
    				   insert into sl_setsync
    					(ssy_setid, ssy_origin, ssy_seqno,
    					ssy_snapshot, ssy_action_list)
    					select v_set, p_backup_node, v_last_sync,
    					ev_snapshot, NULL
    					from sl_event
    					where ev_origin = p_backup_node
    						and ev_seqno = v_last_sync;
    				else
    					insert into sl_setsync
    					(ssy_setid, ssy_origin, ssy_seqno,
    					ssy_snapshot, ssy_action_list)
    					values (v_set, p_backup_node, '0',
    					'1:1:', NULL);
    				end if;
    			end if;
    		end if;
    	end loop;

    	--If there are any subscriptions with
    	--the failed_node being the provider then
    	--we want to redirect those subscriptions
    	--to come from the backup node.
    	--
    	-- The backup node should be a valid
    	-- provider for all subscriptions served
    	-- by the failed node. (otherwise it
    	-- wouldn't be a allowable backup node).
    --	delete from sl_subscribe
    --		   where sub_receiver=p_backup_node;

    	update sl_subscribe
    	       set sub_provider=p_backup_node
    	       from sl_node
    	       where sub_provider=p_failed_node
    	       and sl_node.no_id=sub_receiver
    	       and sl_node.no_failed=false
    		   and sub_receiver<>p_backup_node;

    	update sl_subscribe
    	       set sub_provider=(select set_origin from
    		   	   sl_set where set_id=
    			   sub_set)
    			where sub_provider=p_failed_node
    			and sub_receiver=p_backup_node;

    	update sl_node
    		   set no_active=false WHERE
    		   no_id=p_failed_node;

    	-- Rewrite sl_listen table
    	perform RebuildListenEntries();

    	return p_failed_node;
    end;

## finishtableaftercopy(p_tab_id integer)

finishtableaftercopy(p_tab_id integer)

### Function Properties

Language Return Type PLPGSQL integer

Reenable index maintenance and reindex the table

    declare
    	v_tab_oid		oid;
    	v_tab_fqname	text;
    begin
    	-- ----
    	-- Get the tables OID and fully qualified name
    	-- ---
    	select	PGC.oid,
    			slon_quote_brute(PGN.nspname) || '.' ||
    			slon_quote_brute(PGC.relname) as tab_fqname
    		into v_tab_oid, v_tab_fqname
    			from sl_table T,
    				"pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN
    				where T.tab_id = p_tab_id
    				and T.tab_reloid = PGC.oid
    				and PGC.relnamespace = PGN.oid;
    	if not found then
    		raise exception 'Table with ID % not found in sl_table', p_tab_id;
    	end if;

    	-- ----
    	-- Reenable indexes and reindex the table.
    	-- ----
    	perform enable_indexes_on_table(v_tab_oid);
    	execute 'reindex table ' || slon_quote_input(v_tab_fqname);

    	return 1;
    end;

## forwardconfirm(p_con_timestamp integer, p_con_seqno integer, p_con_received bigint, p_con_origin timestamp without time zone)

forwardconfirm(p_con_timestamp integer, p_con_seqno integer, p_con_received bigint, p_con_origin timestamp without time zone)

### Function Properties

Language Return Type PLPGSQL bigint

forwardConfirm (p_con_origin, p_con_received, p_con_seqno, p_con_timestamp)

Confirms (recorded in sl_confirm) that items from p_con_origin up to p_con_seqno have been received by node p_con_received as of p_con_timestamp, and raises an event to forward this confirmation.

    declare
    	v_max_seqno		bigint;
    begin
    	select into v_max_seqno coalesce(max(con_seqno), 0)
    			from sl_confirm
    			where con_origin = p_con_origin
    			and con_received = p_con_received;
    	if v_max_seqno < p_con_seqno then
    		insert into sl_confirm
    				(con_origin, con_received, con_seqno, con_timestamp)
    				values (p_con_origin, p_con_received, p_con_seqno,
    					p_con_timestamp);
    		v_max_seqno = p_con_seqno;
    	end if;

    	return v_max_seqno;
    end;

## generate_sync_event(p_interval interval)

generate_sync_event(p_interval interval)

### Function Properties

Language Return Type PLPGSQL integer

Generate a sync event if there has not been one in the requested interval, and this is a provider node.

    declare
    	v_node_row     record;

    BEGIN
    	select 1 into v_node_row from sl_event
           	  where ev_type = 'SYNC' and ev_origin = getLocalNodeId('_schemadoc')
              and ev_timestamp > now() - p_interval limit 1;
    	if not found then
    		-- If there has been no SYNC in the last interval, then push one
    		perform createEvent('_schemadoc', 'SYNC', NULL);
    		return 1;
    	else
    		return 0;
    	end if;
    end;

## initializelocalnode(p_comment integer, p_local_node_id text)

initializelocalnode(p_comment integer, p_local_node_id text)

### Function Properties

Language Return Type PLPGSQL integer

no_id - Node ID # no_comment - Human-oriented comment

Initializes the new node, no_id

    declare
    	v_old_node_id		int4;
    	v_first_log_no		int4;
    	v_event_seq			int8;
    begin
    	-- ----
    	-- Make sure this node is uninitialized or got reset
    	-- ----
    	select last_value::int4 into v_old_node_id from sl_local_node_id;
    	if v_old_node_id != -1 then
    		raise exception 'Slony-I: This node is already initialized';
    	end if;

    	-- ----
    	-- Set sl_local_node_id to the requested value and add our
    	-- own system to sl_node.
    	-- ----
    	perform setval('sl_local_node_id', p_local_node_id);
    	perform storeNode_int (p_local_node_id, p_comment);

    	if (pg_catalog.current_setting('max_identifier_length')::integer - pg_catalog.length('schemadoc')) < 5 then
    		raise notice 'Slony-I: Cluster name length [%] versus system max_identifier_length [%] ', pg_catalog.length('schemadoc'), pg_catalog.current_setting('max_identifier_length');
    		raise notice 'leaves narrow/no room for some Slony-I-generated objects (such as indexes).';
    		raise notice 'You may run into problems later!';
    	end if;

    	--
    	-- Put the apply trigger onto sl_log_1 and sl_log_2
    	--
    	create trigger apply_trigger
    		before INSERT on sl_log_1
    		for each row execute procedure logApply('_schemadoc');
    	alter table sl_log_1
    	  enable replica trigger apply_trigger;
    	create trigger apply_trigger
    		before INSERT on sl_log_2
    		for each row execute procedure logApply('_schemadoc');
    	alter table sl_log_2
    			enable replica trigger apply_trigger;

    	return p_local_node_id;
    end;

## is_node_reachable(receiver_node_id integer, origin_node_id integer)

is_node_reachable(receiver_node_id integer, origin_node_id integer)

### Function Properties

Language Return Type PLPGSQL boolean

Is the receiver node reachable from the origin, via any of the listen paths?

    declare
    		listen_row record;
    		reachable boolean;
    begin
    	reachable:=false;
    	select * into listen_row from sl_listen where
    		   li_origin=origin_node_id and li_receiver=receiver_node_id;
    	if found then
    	   reachable:=true;
    	end if;
      return reachable;
    end

## issubscriptioninprogress(p_add_id integer)

issubscriptioninprogress(p_add_id integer)

### Function Properties

Language Return Type PLPGSQL boolean

Checks to see if a subscription for the indicated set is in progress. Returns true if a subscription is in progress. Otherwise false

    begin
    	if exists (select true from sl_event
    			where ev_type = 'ENABLE_SUBSCRIPTION'
    			and ev_data1 = p_add_id::text
    			and ev_seqno > (select max(con_seqno) from sl_confirm
    					where con_origin = ev_origin
    					and con_received::text = ev_data3))
    	then
    		return true;
    	else
    		return false;
    	end if;
    end;

## lockset(p_set_id integer)

lockset(p_set_id integer)

### Function Properties

Language Return Type PLPGSQL integer

lockSet(set_id)

Add a special trigger to all tables of a set that disables access to it.

    declare
    	v_local_node_id		int4;
    	v_set_row			record;
    	v_tab_row			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that the set exists and that we are the origin
    	-- and that it is not already locked.
    	-- ----
    	v_local_node_id := getLocalNodeId('_schemadoc');
    	select * into v_set_row from sl_set
    			where set_id = p_set_id
    			for update;
    	if not found then
    		raise exception 'Slony-I: set % not found', p_set_id;
    	end if;
    	if v_set_row.set_origin <> v_local_node_id then
    		raise exception 'Slony-I: set % does not originate on local node',
    				p_set_id;
    	end if;
    	if v_set_row.set_locked notnull then
    		raise exception 'Slony-I: set % is already locked', p_set_id;
    	end if;

    	-- ----
    	-- Place the lockedSet trigger on all tables in the set.
    	-- ----
    	for v_tab_row in select T.tab_id,
    			slon_quote_brute(PGN.nspname) || '.' ||
    			slon_quote_brute(PGC.relname) as tab_fqname
    			from sl_table T,
    				"pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN
    			where T.tab_set = p_set_id
    				and T.tab_reloid = PGC.oid
    				and PGC.relnamespace = PGN.oid
    			order by tab_id
    	loop
    		execute 'create trigger "_schemadoc_lockedset" ' ||
    				'before insert or update or delete on ' ||
    				v_tab_row.tab_fqname || ' for each row execute procedure
    				lockedSet (''_schemadoc'');';
    	end loop;

    	-- ----
    	-- Remember our snapshots xmax as for the set locking
    	-- ----
    	update sl_set
    			set set_locked = "pg_catalog".txid_snapshot_xmax("pg_catalog".txid_current_snapshot())
    			where set_id = p_set_id;

    	return p_set_id;
    end;

## log_truncate()

log_truncate()

### Function Properties

Language Return Type PLPGSQL trigger

trigger function run when a replicated table receives a TRUNCATE request

    	declare
    		r_role text;
    		c_nspname text;
    		c_relname text;
    		c_log integer;
    		c_node integer;
    		c_tabid integer;
    	begin
    		-- Ignore this call if session_replication_role = 'local'
    		select into r_role setting
    			from pg_catalog.pg_settings where name = 'session_replication_role';
    		if r_role = 'local' then
    			return NULL;
    		end if;

            c_tabid := tg_argv[0];
    	    c_node := getLocalNodeId('_schemadoc');
    		select tab_nspname, tab_relname into c_nspname, c_relname
    				  from sl_table where tab_id = c_tabid;
    		select last_value into c_log from sl_log_status;
    		if c_log in (0, 2) then
    			insert into sl_log_1 (
    					log_origin, log_txid, log_tableid,
    					log_actionseq, log_tablenspname,
    					log_tablerelname, log_cmdtype,
    					log_cmdupdncols, log_cmdargs
    				) values (
    					c_node, pg_catalog.txid_current(), c_tabid,
    					nextval('sl_action_seq'), c_nspname,
    					c_relname, 'T', 0, '{}'::text[]);
    		else   -- (1, 3)
    			insert into sl_log_2 (
    					log_origin, log_txid, log_tableid,
    					log_actionseq, log_tablenspname,
    					log_tablerelname, log_cmdtype,
    					log_cmdupdncols, log_cmdargs
    				) values (
    					c_node, pg_catalog.txid_current(), c_tabid,
    					nextval('sl_action_seq'), c_nspname,
    					c_relname, 'T', 0, '{}'::text[]);
    		end if;
    		return NULL;
        end

## logswitch_finish()

logswitch_finish()

### Function Properties

Language Return Type PLPGSQL integer

logswitch_finish()

Attempt to finalize a log table switch in progress return values: -1 if switch in progress, but not complete 0 if no switch in progress 1 if performed truncate on sl_log_2 2 if performed truncate on sl_log_1

    DECLARE
    	v_current_status	int4;
    	v_dummy				record;
    	v_origin	int8;
    	v_seqno		int8;
    	v_xmin		bigint;
    	v_purgeable boolean;
    BEGIN
    	-- ----
    	-- Get the current log status.
    	-- ----
    	select last_value into v_current_status from sl_log_status;

    	-- ----
    	-- status value 0 or 1 means that there is no log switch in progress
    	-- ----
    	if v_current_status = 0 or v_current_status = 1 then
    		return 0;
    	end if;

    	-- ----
    	-- status = 2: sl_log_1 active, cleanup sl_log_2
    	-- ----
    	if v_current_status = 2 then
    		v_purgeable := 'true';

    		-- ----
    		-- Attempt to lock sl_log_2 in order to make sure there are no other transactions
    		-- currently writing to it. Exit if it is still in use. This prevents TRUNCATE from
    		-- blocking writers to sl_log_2 while it is waiting for a lock. It also prevents it
    		-- immediately truncating log data generated inside the transaction which was active
    		-- when logswitch_finish() was called (and was blocking TRUNCATE) as soon as that
    		-- transaction is committed.
    		-- ----
    		begin
    			lock table sl_log_2 in access exclusive mode nowait;
    		exception when lock_not_available then
    			raise notice 'Slony-I: could not lock sl_log_2 - sl_log_2 not truncated';
    			return -1;
    		end;

    		-- ----
    		-- The cleanup thread calls us after it did the delete and
    		-- vacuum of both log tables. If sl_log_2 is empty now, we
    		-- can truncate it and the log switch is done.
    		-- ----
    	        for v_origin, v_seqno, v_xmin in
    		  select ev_origin, ev_seqno, "pg_catalog".txid_snapshot_xmin(ev_snapshot) from sl_event
    	          where (ev_origin, ev_seqno) in (select ev_origin, min(ev_seqno) from sl_event where ev_type = 'SYNC' group by ev_origin)
    		loop
    			if exists (select 1 from sl_log_2 where log_origin = v_origin and log_txid >= v_xmin limit 1) then
    				v_purgeable := 'false';
    			end if;
    	        end loop;
    		if not v_purgeable then
    			-- ----
    			-- Found a row ... log switch is still in progress.
    			-- ----
    			raise notice 'Slony-I: log switch to sl_log_1 still in progress - sl_log_2 not truncated';
    			return -1;
    		end if;

    		raise notice 'Slony-I: log switch to sl_log_1 complete - truncate sl_log_2';
    		truncate sl_log_2;
    		if exists (select * from "pg_catalog".pg_class c, "pg_catalog".pg_namespace n, "pg_catalog".pg_attribute a where c.relname = 'sl_log_2' and n.oid = c.relnamespace and a.attrelid = c.oid and a.attname = 'oid') then
    	                execute 'alter table sl_log_2 set without oids;';
    		end if;
    		perform "pg_catalog".setval('sl_log_status', 0);
    		-- Run addPartialLogIndices() to try to add indices to unused sl_log_? table
    		perform addPartialLogIndices();

    		return 1;
    	end if;

    	-- ----
    	-- status = 3: sl_log_2 active, cleanup sl_log_1
    	-- ----
    	if v_current_status = 3 then
    		v_purgeable := 'true';

    		-- ----
    		-- Attempt to lock sl_log_1 in order to make sure there are no other transactions
    		-- currently writing to it. Exit if it is still in use. This prevents TRUNCATE from
    		-- blocking writes to sl_log_1 while it is waiting for a lock. It also prevents it
    		-- immediately truncating log data generated inside the transaction which was active
    		-- when logswitch_finish() was called (and was blocking TRUNCATE) as soon as that
    		-- transaction is committed.
    		-- ----
    		begin
    			lock table sl_log_1 in access exclusive mode nowait;
    		exception when lock_not_available then
    			raise notice 'Slony-I: could not lock sl_log_1 - sl_log_1 not truncated';
    			return -1;
    		end;

    		-- ----
    		-- The cleanup thread calls us after it did the delete and
    		-- vacuum of both log tables. If sl_log_2 is empty now, we
    		-- can truncate it and the log switch is done.
    		-- ----
    	        for v_origin, v_seqno, v_xmin in
    		  select ev_origin, ev_seqno, "pg_catalog".txid_snapshot_xmin(ev_snapshot) from sl_event
    	          where (ev_origin, ev_seqno) in (select ev_origin, min(ev_seqno) from sl_event where ev_type = 'SYNC' group by ev_origin)
    		loop
    			if (exists (select 1 from sl_log_1 where log_origin = v_origin and log_txid >= v_xmin limit 1)) then
    				v_purgeable := 'false';
    			end if;
    	        end loop;
    		if not v_purgeable then
    			-- ----
    			-- Found a row ... log switch is still in progress.
    			-- ----
    			raise notice 'Slony-I: log switch to sl_log_2 still in progress - sl_log_1 not truncated';
    			return -1;
    		end if;

    		raise notice 'Slony-I: log switch to sl_log_2 complete - truncate sl_log_1';
    		truncate sl_log_1;
    		if exists (select * from "pg_catalog".pg_class c, "pg_catalog".pg_namespace n, "pg_catalog".pg_attribute a where c.relname = 'sl_log_1' and n.oid = c.relnamespace and a.attrelid = c.oid and a.attname = 'oid') then
    	                execute 'alter table sl_log_1 set without oids;';
    		end if;
    		perform "pg_catalog".setval('sl_log_status', 1);
    		-- Run addPartialLogIndices() to try to add indices to unused sl_log_? table
    		perform addPartialLogIndices();
    		return 2;
    	end if;
    END;

## logswitch_start()

logswitch_start()

### Function Properties

Language Return Type PLPGSQL integer

logswitch_start()

Initiate a log table switch if none is in progress

    DECLARE
    	v_current_status	int4;
    BEGIN
    	-- ----
    	-- Get the current log status.
    	-- ----
    	select last_value into v_current_status from sl_log_status;

    	-- ----
    	-- status = 0: sl_log_1 active, sl_log_2 clean
    	-- Initiate a switch to sl_log_2.
    	-- ----
    	if v_current_status = 0 then
    		perform "pg_catalog".setval('sl_log_status', 3);
    		perform registry_set_timestamp(
    				'logswitch.laststart', now());
    		raise notice 'Slony-I: Logswitch to sl_log_2 initiated';
    		return 2;
    	end if;

    	-- ----
    	-- status = 1: sl_log_2 active, sl_log_1 clean
    	-- Initiate a switch to sl_log_1.
    	-- ----
    	if v_current_status = 1 then
    		perform "pg_catalog".setval('sl_log_status', 2);
    		perform registry_set_timestamp(
    				'logswitch.laststart', now());
    		raise notice 'Slony-I: Logswitch to sl_log_1 initiated';
    		return 1;
    	end if;

    	raise exception 'Previous logswitch still in progress';
    END;

## mergeset(p_add_id integer, p_set_id integer)

mergeset(p_add_id integer, p_set_id integer)

### Function Properties

Language Return Type PLPGSQL bigint

Generate MERGE_SET event to request that sets be merged together.

Both sets must exist, and originate on the same node. They must be subscribed by the same set of nodes.

    declare
    	v_origin			int4;
    	in_progress			boolean;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that both sets exist and originate here
    	-- ----
    	if p_set_id = p_add_id then
    		raise exception 'Slony-I: merged set ids cannot be identical';
    	end if;
    	select set_origin into v_origin from sl_set
    			where set_id = p_set_id;
    	if not found then
    		raise exception 'Slony-I: set % not found', p_set_id;
    	end if;
    	if v_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: set % does not originate on local node',
    				p_set_id;
    	end if;

    	select set_origin into v_origin from sl_set
    			where set_id = p_add_id;
    	if not found then
    		raise exception 'Slony-I: set % not found', p_add_id;
    	end if;
    	if v_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: set % does not originate on local node',
    				p_add_id;
    	end if;

    	-- ----
    	-- Check that both sets are subscribed by the same set of nodes
    	-- ----
    	if exists (select true from sl_subscribe SUB1
    				where SUB1.sub_set = p_set_id
    				and SUB1.sub_receiver not in (select SUB2.sub_receiver
    						from sl_subscribe SUB2
    						where SUB2.sub_set = p_add_id))
    	then
    		raise exception 'Slony-I: subscriber lists of set % and % are different',
    				p_set_id, p_add_id;
    	end if;

    	if exists (select true from sl_subscribe SUB1
    				where SUB1.sub_set = p_add_id
    				and SUB1.sub_receiver not in (select SUB2.sub_receiver
    						from sl_subscribe SUB2
    						where SUB2.sub_set = p_set_id))
    	then
    		raise exception 'Slony-I: subscriber lists of set % and % are different',
    				p_add_id, p_set_id;
    	end if;

    	-- ----
    	-- Check that all ENABLE_SUBSCRIPTION events for the set are confirmed
    	-- ----
    	select isSubscriptionInProgress(p_add_id) into in_progress ;

    	if in_progress then
    		raise exception 'Slony-I: set % has subscriptions in progress - cannot merge',
    				p_add_id;
    	end if;

    	-- ----
    	-- Create a SYNC event, merge the sets, create a MERGE_SET event
    	-- ----
    	perform createEvent('_schemadoc', 'SYNC', NULL);
    	perform mergeSet_int(p_set_id, p_add_id);
    	return  createEvent('_schemadoc', 'MERGE_SET',
    			p_set_id::text, p_add_id::text);
    end;

## mergeset_int(p_add_id integer, p_set_id integer)

mergeset_int(p_add_id integer, p_set_id integer)

### Function Properties

Language Return Type PLPGSQL integer

mergeSet_int(set_id, add_id) - Perform MERGE_SET event, merging all objects from set add_id into set set_id.

    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	update sl_sequence
    			set seq_set = p_set_id
    			where seq_set = p_add_id;
    	update sl_table
    			set tab_set = p_set_id
    			where tab_set = p_add_id;
    	delete from sl_subscribe
    			where sub_set = p_add_id;
    	delete from sl_setsync
    			where ssy_setid = p_add_id;
    	delete from sl_set
    			where set_id = p_add_id;

    	return p_set_id;
    end;

## moveset(p_new_origin integer, p_set_id integer)

moveset(p_new_origin integer, p_set_id integer)

### Function Properties

Language Return Type PLPGSQL bigint

moveSet(set_id, new_origin)

Generate MOVE_SET event to request that the origin for set set_id be moved to node new_origin

    declare
    	v_local_node_id		int4;
    	v_set_row			record;
    	v_sub_row			record;
    	v_sync_seqno		int8;
    	v_lv_row			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that the set is locked and that this locking
    	-- happened long enough ago.
    	-- ----
    	v_local_node_id := getLocalNodeId('_schemadoc');
    	select * into v_set_row from sl_set
    			where set_id = p_set_id
    			for update;
    	if not found then
    		raise exception 'Slony-I: set % not found', p_set_id;
    	end if;
    	if v_set_row.set_origin <> v_local_node_id then
    		raise exception 'Slony-I: set % does not originate on local node',
    				p_set_id;
    	end if;
    	if v_set_row.set_locked isnull then
    		raise exception 'Slony-I: set % is not locked', p_set_id;
    	end if;
    	if v_set_row.set_locked > "pg_catalog".txid_snapshot_xmin("pg_catalog".txid_current_snapshot()) then
    		raise exception 'Slony-I: cannot move set % yet, transactions < % are still in progress',
    				p_set_id, v_set_row.set_locked;
    	end if;

    	-- ----
    	-- Unlock the set
    	-- ----
    	perform unlockSet(p_set_id);

    	-- ----
    	-- Check that the new_origin is an active subscriber of the set
    	-- ----
    	select * into v_sub_row from sl_subscribe
    			where sub_set = p_set_id
    			and sub_receiver = p_new_origin;
    	if not found then
    		raise exception 'Slony-I: set % is not subscribed by node %',
    				p_set_id, p_new_origin;
    	end if;
    	if not v_sub_row.sub_active then
    		raise exception 'Slony-I: subsctiption of node % for set % is inactive',
    				p_new_origin, p_set_id;
    	end if;

    	-- ----
    	-- Reconfigure everything
    	-- ----
    	perform moveSet_int(p_set_id, v_local_node_id,
    			p_new_origin, 0);

    	perform RebuildListenEntries();

    	-- ----
    	-- At this time we hold access exclusive locks for every table
    	-- in the set. But we did move the set to the new origin, so the
    	-- createEvent() we are doing now will not record the sequences.
    	-- ----
    	v_sync_seqno := createEvent('_schemadoc', 'SYNC');
    	insert into sl_seqlog
    			(seql_seqid, seql_origin, seql_ev_seqno, seql_last_value)
    			select seq_id, v_local_node_id, v_sync_seqno, seq_last_value
    			from sl_seqlastvalue
    			where seq_set = p_set_id;

    	-- ----
    	-- Finally we generate the real event
    	-- ----
    	return createEvent('_schemadoc', 'MOVE_SET',
    			p_set_id::text, v_local_node_id::text, p_new_origin::text);
    end;

## moveset_int(p_wait_seqno integer, p_new_origin integer, p_old_origin integer, p_set_id bigint)

moveset_int(p_wait_seqno integer, p_new_origin integer, p_old_origin integer, p_set_id bigint)

### Function Properties

Language Return Type PLPGSQL integer

moveSet(set_id, old_origin, new_origin, wait_seqno)

Process MOVE_SET event to request that the origin for set set_id be moved from old_origin to node new_origin

    declare
    	v_local_node_id		int4;
    	v_tab_row			record;
    	v_sub_row			record;
    	v_sub_node			int4;
    	v_sub_last			int4;
    	v_sub_next			int4;
    	v_last_sync			int8;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Get our local node ID
    	-- ----
    	v_local_node_id := getLocalNodeId('_schemadoc');

    	-- On the new origin, raise an event - ACCEPT_SET
    	if v_local_node_id = p_new_origin then
    		-- Create a SYNC event as well so that the ACCEPT_SET has
    		-- the same snapshot as the last SYNC generated by the new
    		-- origin. This snapshot will be used by other nodes to
    		-- finalize the setsync status.
    		perform createEvent('_schemadoc', 'SYNC', NULL);
    		perform createEvent('_schemadoc', 'ACCEPT_SET',
    			p_set_id::text, p_old_origin::text,
    			p_new_origin::text, p_wait_seqno::text);
    	end if;

    	-- ----
    	-- Next we have to reverse the subscription path
    	-- ----
    	v_sub_last = p_new_origin;
    	select sub_provider into v_sub_node
    			from sl_subscribe
    			where sub_set = p_set_id
    			and sub_receiver = p_new_origin;
    	if not found then
    		raise exception 'Slony-I: subscription path broken in moveSet_int';
    	end if;
    	while v_sub_node <> p_old_origin loop
    		-- ----
    		-- Tracing node by node, the old receiver is now in
    		-- v_sub_last and the old provider is in v_sub_node.
    		-- ----

    		-- ----
    		-- Get the current provider of this node as next
    		-- and change the provider to the previous one in
    		-- the reverse chain.
    		-- ----
    		select sub_provider into v_sub_next
    				from sl_subscribe
    				where sub_set = p_set_id
    					and sub_receiver = v_sub_node
    				for update;
    		if not found then
    			raise exception 'Slony-I: subscription path broken in moveSet_int';
    		end if;
    		update sl_subscribe
    				set sub_provider = v_sub_last
    				where sub_set = p_set_id
    					and sub_receiver = v_sub_node
    					and sub_receiver <> v_sub_last;

    		v_sub_last = v_sub_node;
    		v_sub_node = v_sub_next;
    	end loop;

    	-- ----
    	-- This includes creating a subscription for the old origin
    	-- ----
    	insert into sl_subscribe
    			(sub_set, sub_provider, sub_receiver,
    			sub_forward, sub_active)
    			values (p_set_id, v_sub_last, p_old_origin, true, true);
    	if v_local_node_id = p_old_origin then
    		select coalesce(max(ev_seqno), 0) into v_last_sync
    				from sl_event
    				where ev_origin = p_new_origin
    					and ev_type = 'SYNC';
    		if v_last_sync > 0 then
    			insert into sl_setsync
    					(ssy_setid, ssy_origin, ssy_seqno,
    					ssy_snapshot, ssy_action_list)
    					select p_set_id, p_new_origin, v_last_sync,
    					ev_snapshot, NULL
    					from sl_event
    					where ev_origin = p_new_origin
    						and ev_seqno = v_last_sync;
    		else
    			insert into sl_setsync
    					(ssy_setid, ssy_origin, ssy_seqno,
    					ssy_snapshot, ssy_action_list)
    					values (p_set_id, p_new_origin, '0',
    					'1:1:', NULL);
    		end if;
    	end if;

    	-- ----
    	-- Now change the ownership of the set.
    	-- ----
    	update sl_set
    			set set_origin = p_new_origin
    			where set_id = p_set_id;

    	-- ----
    	-- On the new origin, delete the obsolete setsync information
    	-- and the subscription.
    	-- ----
    	if v_local_node_id = p_new_origin then
    		delete from sl_setsync
    				where ssy_setid = p_set_id;
    	else
    		if v_local_node_id <> p_old_origin then
    			--
    			-- On every other node, change the setsync so that it will
    			-- pick up from the new origins last known sync.
    			--
    			delete from sl_setsync
    					where ssy_setid = p_set_id;
    			select coalesce(max(ev_seqno), 0) into v_last_sync
    					from sl_event
    					where ev_origin = p_new_origin
    						and ev_type = 'SYNC';
    			if v_last_sync > 0 then
    				insert into sl_setsync
    						(ssy_setid, ssy_origin, ssy_seqno,
    						ssy_snapshot, ssy_action_list)
    						select p_set_id, p_new_origin, v_last_sync,
    						ev_snapshot, NULL
    						from sl_event
    						where ev_origin = p_new_origin
    							and ev_seqno = v_last_sync;
    			else
    				insert into sl_setsync
    						(ssy_setid, ssy_origin, ssy_seqno,
    						ssy_snapshot, ssy_action_list)
    						values (p_set_id, p_new_origin,
    						'0', '1:1:', NULL);
    			end if;
    		end if;
    	end if;
    	delete from sl_subscribe
    			where sub_set = p_set_id
    			and sub_receiver = p_new_origin;

    	-- Regenerate sl_listen since we revised the subscriptions
    	perform RebuildListenEntries();

    	-- Run addPartialLogIndices() to try to add indices to unused sl_log_? table
    	perform addPartialLogIndices();

    	-- ----
    	-- If we are the new or old origin, we have to
    	-- adjust the log and deny access trigger configuration.
    	-- ----
    	if v_local_node_id = p_old_origin or v_local_node_id = p_new_origin then
    		for v_tab_row in select tab_id from sl_table
    				where tab_set = p_set_id
    				order by tab_id
    		loop
    			perform alterTableConfigureTriggers(v_tab_row.tab_id);
    		end loop;
    	end if;

    	return p_set_id;
    end;

## prefailover(p_is_candidate integer, p_failed_node boolean)

prefailover(p_is_candidate integer, p_failed_node boolean)

### Function Properties

Language Return Type PLPGSQL integer

Prepare for a failover. This function is called on all candidate nodes. It blanks the paths to the failed node and then restart of all node daemons.

    declare
    	v_row				record;
    	v_row2				record;
    	v_n					int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- All consistency checks first

    	if p_is_candidate then
    	   -- ----
    	   -- Check all sets originating on the failed node
    	   -- ----
    	   for v_row in select set_id
    			from sl_set
    			where set_origin = p_failed_node
    		loop
    				-- ----
    				-- Check that the backup node is subscribed to all sets
    				-- that originate on the failed node
    				-- ----
    				select into v_row2 sub_forward, sub_active
    					   from sl_subscribe
    					   where sub_set = v_row.set_id
    					and sub_receiver = getLocalNodeId('_schemadoc');
    				if not found then
    				   raise exception 'Slony-I: cannot failover - node % is not subscribed to set %',
    					getLocalNodeId('_schemadoc'), v_row.set_id;
    				end if;

    				-- ----
    				-- Check that the subscription is active
    				-- ----
    				if not v_row2.sub_active then
    				   raise exception 'Slony-I: cannot failover - subscription for set % is not active',
    					v_row.set_id;
    				end if;

    				-- ----
    				-- If there are other subscribers, the backup node needs to
    				-- be a forwarder too.
    				-- ----
    				select into v_n count(*)
    					   from sl_subscribe
    					   where sub_set = v_row.set_id
    					   and sub_receiver <> getLocalNodeId('_schemadoc');
    				if v_n > 0 and not v_row2.sub_forward then
    				raise exception 'Slony-I: cannot failover - node % is not a forwarder of set %',
    					 getLocalNodeId('_schemadoc'), v_row.set_id;
    				end if;
    			end loop;
    	end if;

    	-- ----
    	-- Terminate all connections of the failed node the hard way
    	-- ----
    	perform terminateNodeConnections(p_failed_node);

    	update sl_path set pa_conninfo='<event pending>' WHERE
    	   		  pa_server=p_failed_node;
    	notify "_schemadoc_Restart";
    	-- ----
    	-- That is it - so far.
    	-- ----
    	return p_failed_node;
    end;

## preparetableforcopy(p_tab_id integer)

preparetableforcopy(p_tab_id integer)

### Function Properties

Language Return Type PLPGSQL integer

Delete all data and suppress index maintenance

    declare
    	v_tab_oid		oid;
    	v_tab_fqname	text;
    begin
    	-- ----
    	-- Get the OID and fully qualified name for the table
    	-- ---
    	select	PGC.oid,
    			slon_quote_brute(PGN.nspname) || '.' ||
    			slon_quote_brute(PGC.relname) as tab_fqname
    		into v_tab_oid, v_tab_fqname
    			from sl_table T,
    				"pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN
    				where T.tab_id = p_tab_id
    				and T.tab_reloid = PGC.oid
    				and PGC.relnamespace = PGN.oid;
    	if not found then
    		raise exception 'Table with ID % not found in sl_table', p_tab_id;
    	end if;

    	-- ----
    	-- Try using truncate to empty the table and fallback to
    	-- delete on error.
    	-- ----
    	perform TruncateOnlyTable(v_tab_fqname);
    	raise notice 'truncate of % succeeded', v_tab_fqname;

    	-- suppress index activity
            perform disable_indexes_on_table(v_tab_oid);

    	return 1;
    	exception when others then
    		raise notice 'truncate of % failed - doing delete', v_tab_fqname;
    		perform disable_indexes_on_table(v_tab_oid);
    		execute 'delete from only ' || slon_quote_input(v_tab_fqname);
    		return 0;
    end;

## rebuildlistenentries()

rebuildlistenentries()

### Function Properties

Language Return Type PLPGSQL integer

RebuildListenEntries()

Invoked by various subscription and path modifying functions, this rewrites the sl_listen entries, adding in all the ones required to allow communications between nodes in the Slony-I cluster.

    declare
    	v_row	record;
            v_cnt  integer;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- First remove the entire configuration
    	delete from sl_listen;

    	-- Second populate the sl_listen configuration with a full
    	-- network of all possible paths.
    	insert into sl_listen
    				(li_origin, li_provider, li_receiver)
    			select pa_server, pa_server, pa_client from sl_path;
    	while true loop
    		insert into sl_listen
    					(li_origin, li_provider, li_receiver)
    			select distinct li_origin, pa_server, pa_client
    				from sl_listen, sl_path
    				where li_receiver = pa_server
    				  and li_origin <> pa_client
    				  and pa_conninfo<>'<event pending>'
    			except
    			select li_origin, li_provider, li_receiver
    				from sl_listen;

    		if not found then
    			exit;
    		end if;
    	end loop;

    	-- We now replace specific event-origin,receiver combinations
    	-- with a configuration that tries to avoid events arriving at
    	-- a node before the data provider actually has the data ready.

    	-- Loop over every possible pair of receiver and event origin
    	for v_row in select N1.no_id as receiver, N2.no_id as origin,
    			  N2.no_failed as failed
    			from sl_node as N1, sl_node as N2
    			where N1.no_id <> N2.no_id
    	loop
    		-- 1st choice:
    		-- If we use the event origin as a data provider for any
    		-- set that originates on that very node, we are a direct
    		-- subscriber to that origin and listen there only.
    		if exists (select true from sl_set, sl_subscribe				, sl_node p
    				where set_origin = v_row.origin
    				  and sub_set = set_id
    				  and sub_provider = v_row.origin
    				  and sub_receiver = v_row.receiver
    				  and sub_active
    				  and p.no_active
    				  and p.no_id=sub_provider
    				  )
    		then
    			delete from sl_listen
    				where li_origin = v_row.origin
    				  and li_receiver = v_row.receiver;
    			insert into sl_listen (li_origin, li_provider, li_receiver)
    				values (v_row.origin, v_row.origin, v_row.receiver);

    		-- 2nd choice:
    		-- If we are subscribed to any set originating on this
    		-- event origin, we want to listen on all data providers
    		-- we use for this origin. We are a cascaded subscriber
    		-- for sets from this node.
    		else
    				if exists (select true from sl_set, sl_subscribe,
    				   	  	       sl_node provider
    						where set_origin = v_row.origin
    						  and sub_set = set_id
    						  and sub_provider=provider.no_id
    						  and provider.no_failed = false
    						  and sub_receiver = v_row.receiver
    						  and sub_active)
    				then
    						delete from sl_listen
    							   where li_origin = v_row.origin
    					  		   and li_receiver = v_row.receiver;
    						insert into sl_listen (li_origin, li_provider, li_receiver)
    						select distinct set_origin, sub_provider, v_row.receiver
    							   from sl_set, sl_subscribe
    						where set_origin = v_row.origin
    						  and sub_set = set_id
    						  and sub_receiver = v_row.receiver
    						  and sub_active;
    				end if;
    		end if;

    		if v_row.failed then

    		--for every failed node we delete all sl_listen entries
    		--except via providers (listed in sl_subscribe)
    		--or failover candidates (sl_failover_targets)
    		--we do this to prevent a non-failover candidate
    		--that is more ahead of the failover candidate from
    		--sending events to the failover candidate that
    		--are 'too far ahead'

    		--if the failed node is not an origin for any
                    --node then we don't delete all listen paths
    		--for events from it.  Instead we leave
                    --the listen network alone.

    		select count(*) into v_cnt from sl_subscribe sub,
    		       sl_set s
                           where s.set_origin=v_row.origin and s.set_id=sub.sub_set;
                    if v_cnt > 0 then
    		    delete from sl_listen where
    			   li_origin=v_row.origin and
    			   li_receiver=v_row.receiver
    			   and li_provider not in
    			       (select sub_provider from
    			       sl_subscribe,
    			       sl_set where
    			       sub_set=set_id
    			       and set_origin=v_row.origin);
    		    end if;
                   end if;
    --		   insert into sl_listen
    --		   		  (li_origin,li_provider,li_receiver)
    --				  SELECT v_row.origin, pa_server
    --				  ,v_row.receiver
    --				  FROM sl_path where
    --				  	   pa_client=v_row.receiver
    --				  and (v_row.origin,pa_server,v_row.receiver) not in
    --				  	  		(select li_origin,li_provider,li_receiver
    --					  		from sl_listen);
    --		end if;
    	end loop ;

    	return null ;
    end ;

## recreate_log_trigger(p_tab_attkind text, p_tab_id oid, p_fq_table_name text)

recreate_log_trigger(p_tab_attkind text, p_tab_id oid, p_fq_table_name text)

### Function Properties

Language Return Type PLPGSQL integer

A function that drops and recreates the log trigger on the specified table. It is intended to be used after the primary_key/unique index has changed.

    begin
    	execute 'drop trigger "_schemadoc_logtrigger" on ' ||
    		p_fq_table_name	;
    		-- ----
    	execute 'create trigger "_schemadoc_logtrigger"' ||
    			' after insert or update or delete on ' ||
    			p_fq_table_name
    			|| ' for each row execute procedure logTrigger (' ||
                                   pg_catalog.quote_literal('_schemadoc') || ',' ||
    				pg_catalog.quote_literal(p_tab_id::text) || ',' ||
    				pg_catalog.quote_literal(p_tab_attkind) || ');';
    	return 0;
    end

## registernodeconnection(p_nodeid integer)

registernodeconnection(p_nodeid integer)

### Function Properties

Language Return Type PLPGSQL integer

Register (uniquely) the node connection so that only one slon can service the node

    begin
    	insert into sl_nodelock
    		(nl_nodeid, nl_backendpid)
    		values
    		(p_nodeid, pg_backend_pid());

    	return 0;
    end;

## registry_get_int4(p_default text, p_key integer)

registry_get_int4(p_default text, p_key integer)

### Function Properties

Language Return Type PLPGSQL integer

registry_get_int4(key, value)

Get a registry value. If not present, set and return the default.

    DECLARE
    	v_value		int4;
    BEGIN
    	select reg_int4 into v_value from sl_registry
    			where reg_key = p_key;
    	if not found then
    		v_value = p_default;
    		if p_default notnull then
    			perform registry_set_int4(p_key, p_default);
    		end if;
    	else
    		if v_value is null then
    			raise exception 'Slony-I: registry key % is not an int4 value',
    					p_key;
    		end if;
    	end if;
    	return v_value;
    END;

## registry_get_text(p_default text, p_key text)

registry_get_text(p_default text, p_key text)

### Function Properties

Language Return Type PLPGSQL text

registry_get_text(key, value)

Get a registry value. If not present, set and return the default.

    DECLARE
    	v_value		text;
    BEGIN
    	select reg_text into v_value from sl_registry
    			where reg_key = p_key;
    	if not found then
    		v_value = p_default;
    		if p_default notnull then
    			perform registry_set_text(p_key, p_default);
    		end if;
    	else
    		if v_value is null then
    			raise exception 'Slony-I: registry key % is not a text value',
    					p_key;
    		end if;
    	end if;
    	return v_value;
    END;

## registry_get_timestamp(p_default text, p_key timestamp with time zone)

registry_get_timestamp(p_default text, p_key timestamp with time zone)

### Function Properties

Language Return Type PLPGSQL timestamp without time zone

registry_get_timestamp(key, value)

Get a registry value. If not present, set and return the default.

    DECLARE
    	v_value		timestamp;
    BEGIN
    	select reg_timestamp into v_value from sl_registry
    			where reg_key = p_key;
    	if not found then
    		v_value = p_default;
    		if p_default notnull then
    			perform registry_set_timestamp(p_key, p_default);
    		end if;
    	else
    		if v_value is null then
    			raise exception 'Slony-I: registry key % is not an timestamp value',
    					p_key;
    		end if;
    	end if;
    	return v_value;
    END;

## registry_set_int4(p_value text, p_key integer)

registry_set_int4(p_value text, p_key integer)

### Function Properties

Language Return Type PLPGSQL integer

registry_set_int4(key, value)

Set or delete a registry value

    BEGIN
    	if p_value is null then
    		delete from sl_registry
    				where reg_key = p_key;
    	else
    		lock table sl_registry;
    		update sl_registry
    				set reg_int4 = p_value
    				where reg_key = p_key;
    		if not found then
    			insert into sl_registry (reg_key, reg_int4)
    					values (p_key, p_value);
    		end if;
    	end if;
    	return p_value;
    END;

## registry_set_text(p_value text, p_key text)

registry_set_text(p_value text, p_key text)

### Function Properties

Language Return Type PLPGSQL text

registry_set_text(key, value)

Set or delete a registry value

    BEGIN
    	if p_value is null then
    		delete from sl_registry
    				where reg_key = p_key;
    	else
    		lock table sl_registry;
    		update sl_registry
    				set reg_text = p_value
    				where reg_key = p_key;
    		if not found then
    			insert into sl_registry (reg_key, reg_text)
    					values (p_key, p_value);
    		end if;
    	end if;
    	return p_value;
    END;

## registry_set_timestamp(p_value text, p_key timestamp with time zone)

registry_set_timestamp(p_value text, p_key timestamp with time zone)

### Function Properties

Language Return Type PLPGSQL timestamp without time zone

registry_set_timestamp(key, value)

Set or delete a registry value

    BEGIN
    	if p_value is null then
    		delete from sl_registry
    				where reg_key = p_key;
    	else
    		lock table sl_registry;
    		update sl_registry
    				set reg_timestamp = p_value
    				where reg_key = p_key;
    		if not found then
    			insert into sl_registry (reg_key, reg_timestamp)
    					values (p_key, p_value);
    		end if;
    	end if;
    	return p_value;
    END;

## repair_log_triggers(only_locked boolean)

repair_log_triggers(only_locked boolean)

### Function Properties

Language Return Type PLPGSQL integer

repair the log triggers as required. If only_locked is true then only tables that are already exclusively locked by the current transaction are repaired. Otherwise all replicated tables with outdated trigger arguments are recreated.

    declare
    	retval integer;
    	table_row record;
    begin
    	retval=0;
    	for table_row in
    		select  tab_nspname,tab_relname,
    				tab_idxname, tab_id, mode,
    				determineAttKindUnique(tab_nspname||
    					'.'||tab_relname,tab_idxname) as attkind
    		from
    				sl_table
    				left join
    				pg_locks on (relation=tab_reloid and pid=pg_backend_pid()
    				and mode='AccessExclusiveLock')
    				,pg_trigger
    		where tab_reloid=tgrelid and
    		determineAttKindUnique(tab_nspname||'.'
    						||tab_relname,tab_idxname)
    			!=(decode_tgargs(tgargs))[2]
    			and tgname =  '_schemadoc'
    			|| '_logtrigger'
    		LOOP
    				if (only_locked=false) or table_row.mode='AccessExclusiveLock' then
    					 perform recreate_log_trigger
    					 		 (table_row.tab_nspname||'.'||table_row.tab_relname,
    							 table_row.tab_id,table_row.attkind);
    					retval=retval+1;
    				else
    					 raise notice '%.% has an invalid configuration on the log trigger. This was not corrected because only_lock is true and the table is not locked.',
    					 table_row.tab_nspname,table_row.tab_relname;

    				end if;
    		end loop;
    	return retval;
    end

## replicate_partition(p_comment integer, p_idxname text, p_tabname text, p_nspname text, p_tab_id text)

replicate_partition(p_comment integer, p_idxname text, p_tabname text, p_nspname text, p_tab_id text)

### Function Properties

Language Return Type PLPGSQL bigint

Add a partition table to replication. tab_idxname is optional - if NULL, then we use the primary key. This function looks up replication configuration via the parent table.

Note that this function is to be run within an EXECUTE SCRIPT script, so it runs at the right place in the transaction stream on all nodes.

    declare
      prec record;
      prec2 record;
      v_set_id int4;

    begin
    -- Look up the parent table; fail if it does not exist
       select c1.oid into prec from pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_inherits i, pg_catalog.pg_namespace n where c1.oid = i.inhparent  and c2.oid = i.inhrelid and n.oid = c2.relnamespace and n.nspname = p_nspname and c2.relname = p_tabname;
       if not found then
    	raise exception 'replicate_partition: No parent table found for %.%!', p_nspname, p_tabname;
       end if;

    -- The parent table tells us what replication set to use
       select tab_set into prec2 from sl_table where tab_reloid = prec.oid;
       if not found then
    	raise exception 'replicate_partition: Parent table % for new partition %.% is not replicated!', prec.oid, p_nspname, p_tabname;
       end if;

       v_set_id := prec2.tab_set;

    -- Now, we have all the parameters necessary to run add_empty_table_to_replication...
       return add_empty_table_to_replication(v_set_id, p_tab_id, p_nspname, p_tabname, p_idxname, p_comment);
    end

## reshapesubscription(p_sub_receiver integer, p_sub_provider integer, p_sub_origin integer)

reshapesubscription(p_sub_receiver integer, p_sub_provider integer, p_sub_origin integer)

### Function Properties

Language Return Type PLPGSQL integer

Run on a receiver/subscriber node when the provider for that subscription is being changed. Slonik will invoke this method before the SUBSCRIBE_SET event propogates to the receiver so listen paths can be updated.

    begin
    	update sl_subscribe
    		   set sub_provider=p_sub_provider
    		   from sl_set
    		   WHERE sub_set=sl_set.set_id
    		   and sl_set.set_origin=p_sub_origin and sub_receiver=p_sub_receiver;
    	if found then
    	   perform RebuildListenEntries();
    	   notify "_schemadoc_Restart";
    	end if;
    	return 0;
    end

## resubscribenode(p_receiver integer, p_provider integer, p_origin integer)

resubscribenode(p_receiver integer, p_provider integer, p_origin integer)

### Function Properties

Language Return Type PLPGSQL bigint

    declare
    	v_record record;
    	v_missing_sets text;
    	v_ev_seqno bigint;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	--
    	-- Check that the receiver exists
    	--
    	if not exists (select no_id from sl_node where no_id=
    	       	      p_receiver) then
    		      raise exception 'Slony-I: subscribeSet() receiver % does not exist' , p_receiver;
    	end if;

    	--
    	-- Check that the provider exists
    	--
    	if not exists (select no_id from sl_node where no_id=
    	       	      p_provider) then
    		      raise exception 'Slony-I: subscribeSet() provider % does not exist' , p_provider;
    	end if;

    	-- ----
    	-- Check that this is called on the origin node
    	-- ----
    	if p_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: subscribeSet() must be called on origin';
    	end if;

    	-- ---
    	-- Verify that the provider is either the origin or an active subscriber
    	-- Bug report #1362
    	-- ---
    	if p_origin <> p_provider then
    	   for v_record in select sub1.sub_set from
    		    sl_subscribe sub1
    		    left outer join  (sl_subscribe sub2
    				 inner join
    				 sl_set  on (
    								sl_set.set_id=sub2.sub_set
    								and sub2.sub_set=p_origin)
    								)
    			ON (  sub1.sub_set = sub2.sub_set and
                      sub1.sub_receiver = p_provider and
    			      sub1.sub_forward and sub1.sub_active
    				  and sub2.sub_receiver=p_receiver)

    			where sub2.sub_set is null
    		loop
    				v_missing_sets=v_missing_sets || ' ' || v_record.sub_set;
    		end loop;
    		if v_missing_sets is not null then
    			raise exception 'Slony-I: subscribeSet(): provider % is not an active forwarding node for replication set %', p_sub_provider, v_missing_sets;
    		end if;
    	end if;

    	for v_record in select *  from
    		sl_subscribe, sl_set where
    		sub_set=set_id and
    		sub_receiver=p_receiver
    		and set_origin=p_origin
    	loop
    	-- ----
    	-- Create the SUBSCRIBE_SET event
    	-- ----
    	   v_ev_seqno :=  createEvent('_schemadoc', 'SUBSCRIBE_SET',
    				  v_record.sub_set::text, p_provider::text, p_receiver::text,
    				  case v_record.sub_forward when true then 't' else 'f' end,
    				  	   'f' );

    		-- ----
    		-- Call the internal procedure to store the subscription
    		-- ----
    		perform subscribeSet_int(v_record.sub_set,
    				p_provider,
    				p_receiver, v_record.sub_forward, false);
    	end loop;

    	return v_ev_seqno;
    end;

## sequencelastvalue(p_seqname text)

sequencelastvalue(p_seqname text)

### Function Properties

Language Return Type PLPGSQL bigint

sequenceLastValue(p_seqname)

Utility function used in sl_seqlastvalue view to compactly get the last value from the requested sequence.

    declare
    	v_seq_row	record;
    begin
    	for v_seq_row in execute 'select last_value from ' || slon_quote_input(p_seqname)
    	loop
    		return v_seq_row.last_value;
    	end loop;

    	-- not reached
    end;

## sequencesetvalue(p_ignore_missing integer, p_last_value integer, p_ev_seqno bigint, p_seq_origin bigint, p_seq_id boolean)

sequencesetvalue(p_ignore_missing integer, p_last_value integer, p_ev_seqno bigint, p_seq_origin bigint, p_seq_id boolean)

### Function Properties

Language Return Type PLPGSQL integer

sequenceSetValue (seq_id, seq_origin, ev_seqno, last_value,ignore_missing) Set sequence seq_id to have new value last_value.

    declare
    	v_fqname			text;
    	v_found                         integer;
    begin
    	-- ----
    	-- Get the sequences fully qualified name
    	-- ----
    	select slon_quote_brute(PGN.nspname) || '.' ||
    			slon_quote_brute(PGC.relname) into v_fqname
    		from sl_sequence SQ,
    			"pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN
    		where SQ.seq_id = p_seq_id
    			and SQ.seq_reloid = PGC.oid
    			and PGC.relnamespace = PGN.oid;
    	if not found then
    	        if p_ignore_missing then
                           return null;
                    end if;
    		raise exception 'Slony-I: sequenceSetValue(): sequence % not found', p_seq_id;
    	end if;

    	-- ----
    	-- Update it to the new value
    	-- ----
    	execute 'select setval(''' || v_fqname ||
    			''', ' || p_last_value::text || ')';

    	if p_ev_seqno is not null then
    	   insert into sl_seqlog
    			(seql_seqid, seql_origin, seql_ev_seqno, seql_last_value)
    			values (p_seq_id, p_seq_origin, p_ev_seqno, p_last_value);
    	end if;
    	return p_seq_id;
    end;

## setaddsequence(p_seq_comment integer, p_fqname integer, p_seq_id text, p_set_id text)

setaddsequence(p_seq_comment integer, p_fqname integer, p_seq_id text, p_set_id text)

### Function Properties

Language Return Type PLPGSQL bigint

setAddSequence (set_id, seq_id, seq_fqname, seq_comment)

On the origin node for set set_id, add sequence seq_fqname to the replication set, and raise SET_ADD_SEQUENCE to cause this to replicate to subscriber nodes.

    declare
    	v_set_origin		int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that we are the origin of the set
    	-- ----
    	select set_origin into v_set_origin
    			from sl_set
    			where set_id = p_set_id;
    	if not found then
    		raise exception 'Slony-I: setAddSequence(): set % not found', p_set_id;
    	end if;
    	if v_set_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: setAddSequence(): set % has remote origin - submit to origin node', p_set_id;
    	end if;

    	if exists (select true from sl_subscribe
    			where sub_set = p_set_id)
    	then
    		raise exception 'Slony-I: cannot add sequence to currently subscribed set %',
    				p_set_id;
    	end if;

    	-- ----
    	-- Add the sequence to the set and generate the SET_ADD_SEQUENCE event
    	-- ----
    	perform setAddSequence_int(p_set_id, p_seq_id, p_fqname,
    			p_seq_comment);
    	return  createEvent('_schemadoc', 'SET_ADD_SEQUENCE',
    						p_set_id::text, p_seq_id::text,
    						p_fqname::text, p_seq_comment::text);
    end;

## setaddsequence_int(p_seq_comment integer, p_fqname integer, p_seq_id text, p_set_id text)

setaddsequence_int(p_seq_comment integer, p_fqname integer, p_seq_id text, p_set_id text)

### Function Properties

Language Return Type PLPGSQL integer

setAddSequence_int (set_id, seq_id, seq_fqname, seq_comment)

This processes the SET_ADD_SEQUENCE event. On remote nodes that subscribe to set_id, add the sequence to the replication set.

    declare
    	v_local_node_id		int4;
    	v_set_origin		int4;
    	v_sub_provider		int4;
    	v_relkind			char;
    	v_seq_reloid		oid;
    	v_seq_relname		name;
    	v_seq_nspname		name;
    	v_sync_row			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- For sets with a remote origin, check that we are subscribed
    	-- to that set. Otherwise we ignore the sequence because it might
    	-- not even exist in our database.
    	-- ----
    	v_local_node_id := getLocalNodeId('_schemadoc');
    	select set_origin into v_set_origin
    			from sl_set
    			where set_id = p_set_id;
    	if not found then
    		raise exception 'Slony-I: setAddSequence_int(): set % not found',
    				p_set_id;
    	end if;
    	if v_set_origin != v_local_node_id then
    		select sub_provider into v_sub_provider
    				from sl_subscribe
    				where sub_set = p_set_id
    				and sub_receiver = getLocalNodeId('_schemadoc');
    		if not found then
    			return 0;
    		end if;
    	end if;

    	-- ----
    	-- Get the sequences OID and check that it is a sequence
    	-- ----
    	select PGC.oid, PGC.relkind, PGC.relname, PGN.nspname
    		into v_seq_reloid, v_relkind, v_seq_relname, v_seq_nspname
    			from "pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN
    			where PGC.relnamespace = PGN.oid
    			and slon_quote_input(p_fqname) = slon_quote_brute(PGN.nspname) ||
    					'.' || slon_quote_brute(PGC.relname);
    	if not found then
    		raise exception 'Slony-I: setAddSequence_int(): sequence % not found',
    				p_fqname;
    	end if;
    	if v_relkind != 'S' then
    		raise exception 'Slony-I: setAddSequence_int(): % is not a sequence',
    				p_fqname;
    	end if;

            select 1 into v_sync_row from sl_sequence where seq_id = p_seq_id;
    	if not found then
                   v_relkind := 'o';   -- all is OK
            else
                    raise exception 'Slony-I: setAddSequence_int(): sequence ID % has already been assigned', p_seq_id;
            end if;

    	-- ----
    	-- Add the sequence to sl_sequence
    	-- ----
    	insert into sl_sequence
    		(seq_id, seq_reloid, seq_relname, seq_nspname, seq_set, seq_comment)
    		values
    		(p_seq_id, v_seq_reloid, v_seq_relname, v_seq_nspname,  p_set_id, p_seq_comment);

    	-- ----
    	-- On the set origin, fake a sl_seqlog row for the last sync event
    	-- ----
    	if v_set_origin = v_local_node_id then
    		for v_sync_row in select coalesce (max(ev_seqno), 0) as ev_seqno
    				from sl_event
    				where ev_origin = v_local_node_id
    					and ev_type = 'SYNC'
    		loop
    			insert into sl_seqlog
    					(seql_seqid, seql_origin, seql_ev_seqno,
    					seql_last_value) values
    					(p_seq_id, v_local_node_id, v_sync_row.ev_seqno,
    					sequenceLastValue(p_fqname));
    		end loop;
    	end if;

    	return p_seq_id;
    end;

## setaddtable(p_tab_comment integer, p_tab_idxname integer, p_fqname text, p_tab_id name, p_set_id text)

setaddtable(p_tab_comment integer, p_tab_idxname integer, p_fqname text, p_tab_id name, p_set_id text)

### Function Properties

Language Return Type PLPGSQL bigint

setAddTable (set_id, tab_id, tab_fqname, tab_idxname, tab_comment)

Add table tab_fqname to replication set on origin node, and generate SET_ADD_TABLE event to allow this to propagate to other nodes.

Note that the table id, tab_id, must be unique ACROSS ALL SETS.

    declare
    	v_set_origin		int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that we are the origin of the set
    	-- ----
    	select set_origin into v_set_origin
    			from sl_set
    			where set_id = p_set_id;
    	if not found then
    		raise exception 'Slony-I: setAddTable(): set % not found', p_set_id;
    	end if;
    	if v_set_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: setAddTable(): set % has remote origin', p_set_id;
    	end if;

    	if exists (select true from sl_subscribe
    			where sub_set = p_set_id)
    	then
    		raise exception 'Slony-I: cannot add table to currently subscribed set % - must attach to an unsubscribed set',
    				p_set_id;
    	end if;

    	-- ----
    	-- Add the table to the set and generate the SET_ADD_TABLE event
    	-- ----
    	perform setAddTable_int(p_set_id, p_tab_id, p_fqname,
    			p_tab_idxname, p_tab_comment);
    	return  createEvent('_schemadoc', 'SET_ADD_TABLE',
    			p_set_id::text, p_tab_id::text, p_fqname::text,
    			p_tab_idxname::text, p_tab_comment::text);
    end;

## setaddtable_int(p_tab_comment integer, p_tab_idxname integer, p_fqname text, p_tab_id name, p_set_id text)

setaddtable_int(p_tab_comment integer, p_tab_idxname integer, p_fqname text, p_tab_id name, p_set_id text)

### Function Properties

Language Return Type PLPGSQL integer

setAddTable_int (set_id, tab_id, tab_fqname, tab_idxname, tab_comment)

This function processes the SET_ADD_TABLE event on remote nodes, adding a table to replication if the remote node is subscribing to its replication set.

    declare
    	v_tab_relname		name;
    	v_tab_nspname		name;
    	v_local_node_id		int4;
    	v_set_origin		int4;
    	v_sub_provider		int4;
    	v_relkind		char;
    	v_tab_reloid		oid;
    	v_pkcand_nn		boolean;
    	v_prec			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- For sets with a remote origin, check that we are subscribed
    	-- to that set. Otherwise we ignore the table because it might
    	-- not even exist in our database.
    	-- ----
    	v_local_node_id := getLocalNodeId('_schemadoc');
    	select set_origin into v_set_origin
    			from sl_set
    			where set_id = p_set_id;
    	if not found then
    		raise exception 'Slony-I: setAddTable_int(): set % not found',
    				p_set_id;
    	end if;
    	if v_set_origin != v_local_node_id then
    		select sub_provider into v_sub_provider
    				from sl_subscribe
    				where sub_set = p_set_id
    				and sub_receiver = getLocalNodeId('_schemadoc');
    		if not found then
    			return 0;
    		end if;
    	end if;

    	-- ----
    	-- Get the tables OID and check that it is a real table
    	-- ----
    	select PGC.oid, PGC.relkind, PGC.relname, PGN.nspname into v_tab_reloid, v_relkind, v_tab_relname, v_tab_nspname
    			from "pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN
    			where PGC.relnamespace = PGN.oid
    			and slon_quote_input(p_fqname) = slon_quote_brute(PGN.nspname) ||
    					'.' || slon_quote_brute(PGC.relname);
    	if not found then
    		raise exception 'Slony-I: setAddTable_int(): table % not found',
    				p_fqname;
    	end if;
    	if v_relkind != 'r' then
    		raise exception 'Slony-I: setAddTable_int(): % is not a regular table',
    				p_fqname;
    	end if;

    	if not exists (select indexrelid
    			from "pg_catalog".pg_index PGX, "pg_catalog".pg_class PGC
    			where PGX.indrelid = v_tab_reloid
    				and PGX.indexrelid = PGC.oid
    				and PGC.relname = p_tab_idxname)
    	then
    		raise exception 'Slony-I: setAddTable_int(): table % has no index %',
    				p_fqname, p_tab_idxname;
    	end if;

    	-- ----
    	-- Verify that the columns in the PK (or candidate) are not NULLABLE
    	-- ----

    	v_pkcand_nn := 'f';
    	for v_prec in select attname from "pg_catalog".pg_attribute where attrelid =
                            (select oid from "pg_catalog".pg_class where oid = v_tab_reloid)
                        and attname in (select attname from "pg_catalog".pg_attribute where
                                        attrelid = (select oid from "pg_catalog".pg_class PGC,
                                        "pg_catalog".pg_index PGX where
                                        PGC.relname = p_tab_idxname and PGX.indexrelid=PGC.oid and
                                        PGX.indrelid = v_tab_reloid)) and attnotnull <> 't'
    	loop
    		raise notice 'Slony-I: setAddTable_int: table % PK column % nullable', p_fqname, v_prec.attname;
    		v_pkcand_nn := 't';
    	end loop;
    	if v_pkcand_nn then
    		raise exception 'Slony-I: setAddTable_int: table % not replicable!', p_fqname;
    	end if;

    	select * into v_prec from sl_table where tab_id = p_tab_id;
    	if not found then
    		v_pkcand_nn := 't';  -- No-op -- All is well
    	else
    		raise exception 'Slony-I: setAddTable_int: table id % has already been assigned!', p_tab_id;
    	end if;

    	-- ----
    	-- Add the table to sl_table and create the trigger on it.
    	-- ----
    	insert into sl_table
    			(tab_id, tab_reloid, tab_relname, tab_nspname,
    			tab_set, tab_idxname, tab_altered, tab_comment)
    			values
    			(p_tab_id, v_tab_reloid, v_tab_relname, v_tab_nspname,
    			p_set_id, p_tab_idxname, false, p_tab_comment);
    	perform alterTableAddTriggers(p_tab_id);

    	return p_tab_id;
    end;

## setdropsequence(p_seq_id integer)

setdropsequence(p_seq_id integer)

### Function Properties

Language Return Type PLPGSQL bigint

setDropSequence (seq_id)

On the origin node for the set, drop sequence seq_id from replication set, and raise SET_DROP_SEQUENCE to cause this to replicate to subscriber nodes.

    declare
    	v_set_id		int4;
    	v_set_origin		int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Determine set id for this sequence
    	-- ----
    	select seq_set into v_set_id from sl_sequence where seq_id = p_seq_id;

    	-- ----
    	-- Ensure sequence exists
    	-- ----
    	if not found then
    		raise exception 'Slony-I: setDropSequence_int(): sequence % not found',
    			p_seq_id;
    	end if;

    	-- ----
    	-- Check that we are the origin of the set
    	-- ----
    	select set_origin into v_set_origin
    			from sl_set
    			where set_id = v_set_id;
    	if not found then
    		raise exception 'Slony-I: setDropSequence(): set % not found', v_set_id;
    	end if;
    	if v_set_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: setDropSequence(): set % has origin at another node - submit this to that node', v_set_id;
    	end if;

    	-- ----
    	-- Add the sequence to the set and generate the SET_ADD_SEQUENCE event
    	-- ----
    	perform setDropSequence_int(p_seq_id);
    	return  createEvent('_schemadoc', 'SET_DROP_SEQUENCE',
    					p_seq_id::text);
    end;

## setdropsequence_int(p_seq_id integer)

setdropsequence_int(p_seq_id integer)

### Function Properties

Language Return Type PLPGSQL integer

setDropSequence_int (seq_id)

This processes the SET_DROP_SEQUENCE event. On remote nodes that subscribe to the set containing sequence seq_id, drop the sequence from the replication set.

    declare
    	v_set_id		int4;
    	v_local_node_id		int4;
    	v_set_origin		int4;
    	v_sub_provider		int4;
    	v_relkind			char;
    	v_sync_row			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Determine set id for this sequence
    	-- ----
    	select seq_set into v_set_id from sl_sequence where seq_id = p_seq_id;

    	-- ----
    	-- Ensure sequence exists
    	-- ----
    	if not found then
    		return 0;
    	end if;

    	-- ----
    	-- For sets with a remote origin, check that we are subscribed
    	-- to that set. Otherwise we ignore the sequence because it might
    	-- not even exist in our database.
    	-- ----
    	v_local_node_id := getLocalNodeId('_schemadoc');
    	select set_origin into v_set_origin
    			from sl_set
    			where set_id = v_set_id;
    	if not found then
    		raise exception 'Slony-I: setDropSequence_int(): set % not found',
    				v_set_id;
    	end if;
    	if v_set_origin != v_local_node_id then
    		select sub_provider into v_sub_provider
    				from sl_subscribe
    				where sub_set = v_set_id
    				and sub_receiver = getLocalNodeId('_schemadoc');
    		if not found then
    			return 0;
    		end if;
    	end if;

    	-- ----
    	-- drop the sequence from sl_sequence, sl_seqlog
    	-- ----
    	delete from sl_seqlog where seql_seqid = p_seq_id;
    	delete from sl_sequence where seq_id = p_seq_id;

    	return p_seq_id;
    end;

## setdroptable(p_tab_id integer)

setdroptable(p_tab_id integer)

### Function Properties

Language Return Type PLPGSQL bigint

setDropTable (tab_id)

Drop table tab_id from set on origin node, and generate SET_DROP_TABLE event to allow this to propagate to other nodes.

    declare
    	v_set_id		int4;
    	v_set_origin		int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

        -- ----
    	-- Determine the set_id
        -- ----
    	select tab_set into v_set_id from sl_table where tab_id = p_tab_id;

    	-- ----
    	-- Ensure table exists
    	-- ----
    	if not found then
    		raise exception 'Slony-I: setDropTable_int(): table % not found',
    			p_tab_id;
    	end if;

    	-- ----
    	-- Check that we are the origin of the set
    	-- ----
    	select set_origin into v_set_origin
    			from sl_set
    			where set_id = v_set_id;
    	if not found then
    		raise exception 'Slony-I: setDropTable(): set % not found', v_set_id;
    	end if;
    	if v_set_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: setDropTable(): set % has remote origin', v_set_id;
    	end if;

    	-- ----
    	-- Drop the table from the set and generate the SET_ADD_TABLE event
    	-- ----
    	perform setDropTable_int(p_tab_id);
    	return  createEvent('_schemadoc', 'SET_DROP_TABLE',
    				p_tab_id::text);
    end;

## setdroptable_int(p_tab_id integer)

setdroptable_int(p_tab_id integer)

### Function Properties

Language Return Type PLPGSQL integer

setDropTable_int (tab_id)

This function processes the SET_DROP_TABLE event on remote nodes, dropping a table from replication if the remote node is subscribing to its replication set.

    declare
    	v_set_id		int4;
    	v_local_node_id		int4;
    	v_set_origin		int4;
    	v_sub_provider		int4;
    	v_tab_reloid		oid;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

        -- ----
    	-- Determine the set_id
        -- ----
    	select tab_set into v_set_id from sl_table where tab_id = p_tab_id;

    	-- ----
    	-- Ensure table exists
    	-- ----
    	if not found then
    		return 0;
    	end if;

    	-- ----
    	-- For sets with a remote origin, check that we are subscribed
    	-- to that set. Otherwise we ignore the table because it might
    	-- not even exist in our database.
    	-- ----
    	v_local_node_id := getLocalNodeId('_schemadoc');
    	select set_origin into v_set_origin
    			from sl_set
    			where set_id = v_set_id;
    	if not found then
    		raise exception 'Slony-I: setDropTable_int(): set % not found',
    				v_set_id;
    	end if;
    	if v_set_origin != v_local_node_id then
    		select sub_provider into v_sub_provider
    				from sl_subscribe
    				where sub_set = v_set_id
    				and sub_receiver = getLocalNodeId('_schemadoc');
    		if not found then
    			return 0;
    		end if;
    	end if;

    	-- ----
    	-- Drop the table from sl_table and drop trigger from it.
    	-- ----
    	perform alterTableDropTriggers(p_tab_id);
    	delete from sl_table where tab_id = p_tab_id;
    	return p_tab_id;
    end;

## setmovesequence(p_new_set_id integer, p_seq_id integer)

setmovesequence(p_new_set_id integer, p_seq_id integer)

### Function Properties

Language Return Type PLPGSQL bigint

setMoveSequence(p_seq_id, p_new_set_id) - This generates the SET_MOVE_SEQUENCE event, after validation, notably that both sets exist, are distinct, and have exactly the same subscription lists

    declare
    	v_old_set_id		int4;
    	v_origin			int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Get the sequences current set
    	-- ----
    	select seq_set into v_old_set_id from sl_sequence
    			where seq_id = p_seq_id;
    	if not found then
    		raise exception 'Slony-I: setMoveSequence(): sequence %d not found', p_seq_id;
    	end if;

    	-- ----
    	-- Check that both sets exist and originate here
    	-- ----
    	if p_new_set_id = v_old_set_id then
    		raise exception 'Slony-I: setMoveSequence(): set ids cannot be identical';
    	end if;
    	select set_origin into v_origin from sl_set
    			where set_id = p_new_set_id;
    	if not found then
    		raise exception 'Slony-I: setMoveSequence(): set % not found', p_new_set_id;
    	end if;
    	if v_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: setMoveSequence(): set % does not originate on local node',
    				p_new_set_id;
    	end if;

    	select set_origin into v_origin from sl_set
    			where set_id = v_old_set_id;
    	if not found then
    		raise exception 'Slony-I: set % not found', v_old_set_id;
    	end if;
    	if v_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: set % does not originate on local node',
    				v_old_set_id;
    	end if;

    	-- ----
    	-- Check that both sets are subscribed by the same set of nodes
    	-- ----
    	if exists (select true from sl_subscribe SUB1
    				where SUB1.sub_set = p_new_set_id
    				and SUB1.sub_receiver not in (select SUB2.sub_receiver
    						from sl_subscribe SUB2
    						where SUB2.sub_set = v_old_set_id))
    	then
    		raise exception 'Slony-I: subscriber lists of set % and % are different',
    				p_new_set_id, v_old_set_id;
    	end if;

    	if exists (select true from sl_subscribe SUB1
    				where SUB1.sub_set = v_old_set_id
    				and SUB1.sub_receiver not in (select SUB2.sub_receiver
    						from sl_subscribe SUB2
    						where SUB2.sub_set = p_new_set_id))
    	then
    		raise exception 'Slony-I: subscriber lists of set % and % are different',
    				v_old_set_id, p_new_set_id;
    	end if;

    	-- ----
    	-- Change the set the sequence belongs to
    	-- ----
    	perform setMoveSequence_int(p_seq_id, p_new_set_id);
    	return  createEvent('_schemadoc', 'SET_MOVE_SEQUENCE',
    			p_seq_id::text, p_new_set_id::text);
    end;

## setmovesequence_int(p_new_set_id integer, p_seq_id integer)

setmovesequence_int(p_new_set_id integer, p_seq_id integer)

### Function Properties

Language Return Type PLPGSQL integer

setMoveSequence_int(p_seq_id, p_new_set_id) - processes the SET_MOVE_SEQUENCE event, moving a sequence to another replication set.

    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Move the sequence to the new set
    	-- ----
    	update sl_sequence
    			set seq_set = p_new_set_id
    			where seq_id = p_seq_id;

    	return p_seq_id;
    end;

## setmovetable(p_new_set_id integer, p_tab_id integer)

setmovetable(p_new_set_id integer, p_tab_id integer)

### Function Properties

Language Return Type PLPGSQL bigint

This processes the SET_MOVE_TABLE event. The table is moved to the destination set.

    declare
    	v_old_set_id		int4;
    	v_origin			int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Get the tables current set
    	-- ----
    	select tab_set into v_old_set_id from sl_table
    			where tab_id = p_tab_id;
    	if not found then
    		raise exception 'Slony-I: table %d not found', p_tab_id;
    	end if;

    	-- ----
    	-- Check that both sets exist and originate here
    	-- ----
    	if p_new_set_id = v_old_set_id then
    		raise exception 'Slony-I: set ids cannot be identical';
    	end if;
    	select set_origin into v_origin from sl_set
    			where set_id = p_new_set_id;
    	if not found then
    		raise exception 'Slony-I: set % not found', p_new_set_id;
    	end if;
    	if v_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: set % does not originate on local node',
    				p_new_set_id;
    	end if;

    	select set_origin into v_origin from sl_set
    			where set_id = v_old_set_id;
    	if not found then
    		raise exception 'Slony-I: set % not found', v_old_set_id;
    	end if;
    	if v_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: set % does not originate on local node',
    				v_old_set_id;
    	end if;

    	-- ----
    	-- Check that both sets are subscribed by the same set of nodes
    	-- ----
    	if exists (select true from sl_subscribe SUB1
    				where SUB1.sub_set = p_new_set_id
    				and SUB1.sub_receiver not in (select SUB2.sub_receiver
    						from sl_subscribe SUB2
    						where SUB2.sub_set = v_old_set_id))
    	then
    		raise exception 'Slony-I: subscriber lists of set % and % are different',
    				p_new_set_id, v_old_set_id;
    	end if;

    	if exists (select true from sl_subscribe SUB1
    				where SUB1.sub_set = v_old_set_id
    				and SUB1.sub_receiver not in (select SUB2.sub_receiver
    						from sl_subscribe SUB2
    						where SUB2.sub_set = p_new_set_id))
    	then
    		raise exception 'Slony-I: subscriber lists of set % and % are different',
    				v_old_set_id, p_new_set_id;
    	end if;

    	-- ----
    	-- Change the set the table belongs to
    	-- ----
    	perform createEvent('_schemadoc', 'SYNC', NULL);
    	perform setMoveTable_int(p_tab_id, p_new_set_id);
    	return  createEvent('_schemadoc', 'SET_MOVE_TABLE',
    			p_tab_id::text, p_new_set_id::text);
    end;

## setmovetable_int(p_new_set_id integer, p_tab_id integer)

setmovetable_int(p_new_set_id integer, p_tab_id integer)

### Function Properties

Language Return Type PLPGSQL integer

    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Move the table to the new set
    	-- ----
    	update sl_table
    			set tab_set = p_new_set_id
    			where tab_id = p_tab_id;

    	return p_tab_id;
    end;

## slon_node_health_check()

slon_node_health_check()

### Function Properties

Language Return Type PLPGSQL boolean

called when slon starts up to validate that there are not problems with node configuration. Returns t if all is OK, f if there is a problem.

    declare
    		prec record;
    		all_ok boolean;
    begin
    		all_ok := 't'::boolean;
    		-- validate that all tables in sl_table have:
    		--      sl_table agreeing with pg_class
    		for prec in select tab_id, tab_relname, tab_nspname from
    		sl_table t where not exists (select 1 from pg_catalog.pg_class c, pg_catalog.pg_namespace n
    				where c.oid = t.tab_reloid and c.relname = t.tab_relname and c.relnamespace = n.oid and n.nspname = t.tab_nspname) loop
    				all_ok := 'f'::boolean;
    				raise warning 'table [id,nsp,name]=[%,%,%] - sl_table does not match pg_class/pg_namespace', prec.tab_id, prec.tab_relname, prec.tab_nspname;
    		end loop;
    		if not all_ok then
    		   raise warning 'Mismatch found between sl_table and pg_class.  Slonik command REPAIR CONFIG may be useful to rectify this.';
    		end if;
    		return all_ok;
    end

## slon_quote_brute(p_tab_fqname text)

slon_quote_brute(p_tab_fqname text)

### Function Properties

Language Return Type PLPGSQL text

Brutally quote the given text

    declare
        v_fqname text default '';
    begin
        v_fqname := '"' || replace(p_tab_fqname,'"','""') || '"';
        return v_fqname;
    end;

## slon_quote_input(p_tab_fqname text)

slon_quote_input(p_tab_fqname text)

### Function Properties

Language Return Type PLPGSQL text

quote all words that aren't quoted yet

      declare
         v_nsp_name text;
         v_tab_name text;
    	 v_i integer;
    	 v_l integer;
         v_pq2 integer;
    begin
    	v_l := length(p_tab_fqname);

    	-- Let us search for the dot
    	if p_tab_fqname like '"%' then
    		-- if the first part of the ident starts with a double quote, search
    		-- for the closing double quote, skipping over double double quotes.
    		v_i := 2;
    		while v_i <= v_l loop
    			if substr(p_tab_fqname, v_i, 1) != '"' then
    				v_i := v_i + 1;
    			else
    				v_i := v_i + 1;
    				if substr(p_tab_fqname, v_i, 1) != '"' then
    					exit;
    				end if;
    				v_i := v_i + 1;
    			end if;
    		end loop;
    	else
    		-- first part of ident is not quoted, search for the dot directly
    		v_i := 1;
    		while v_i <= v_l loop
    			if substr(p_tab_fqname, v_i, 1) = '.' then
    				exit;
    			end if;
    			v_i := v_i + 1;
    		end loop;
    	end if;

    	-- v_i now points at the dot or behind the string.

    	if substr(p_tab_fqname, v_i, 1) = '.' then
    		-- There is a dot now, so split the ident into its namespace
    		-- and objname parts and make sure each is quoted
    		v_nsp_name := substr(p_tab_fqname, 1, v_i - 1);
    		v_tab_name := substr(p_tab_fqname, v_i + 1);
    		if v_nsp_name not like '"%' then
    			v_nsp_name := '"' || replace(v_nsp_name, '"', '""') ||
    						  '"';
    		end if;
    		if v_tab_name not like '"%' then
    			v_tab_name := '"' || replace(v_tab_name, '"', '""') ||
    						  '"';
    		end if;

    		return v_nsp_name || '.' || v_tab_name;
    	else
    		-- No dot ... must be just an ident without schema
    		if p_tab_fqname like '"%' then
    			return p_tab_fqname;
    		else
    			return '"' || replace(p_tab_fqname, '"', '""') || '"';
    		end if;
    	end if;

    end;

## slonyversion()

slonyversion()

### Function Properties

Language Return Type PLPGSQL text

Returns the version number of the slony schema

    begin
    	return slonyVersionMajor()::text || '.' ||
    	       slonyVersionMinor()::text || '.' ||
    	       slonyVersionPatchlevel()::text   ;
    end;

## slonyversionmajor()

slonyversionmajor()

### Function Properties

Language Return Type PLPGSQL integer

Returns the major version number of the slony schema

    begin
    	return 2;
    end;

## slonyversionminor()

slonyversionminor()

### Function Properties

Language Return Type PLPGSQL integer

Returns the minor version number of the slony schema

    begin
    	return 2;
    end;

## slonyversionpatchlevel()

slonyversionpatchlevel()

### Function Properties

Language Return Type PLPGSQL integer

Returns the version patch level of the slony schema

    begin
    	return 6;
    end;

## store_application_name(i_name text)

store_application_name(i_name text)

### Function Properties

Language Return Type PLPGSQL text

Set application_name GUC, if possible. Returns NULL if it fails to work.

    declare
    		p_command text;
    begin
    		if exists (select 1 from pg_catalog.pg_settings where name = 'application_name') then
    		   p_command := 'set application_name to '''|| i_name || ''';';
    		   execute p_command;
    		   return i_name;
    		end if;
    		return NULL::text;
    end

## storelisten(p_receiver integer, p_provider integer, p_origin integer)

storelisten(p_receiver integer, p_provider integer, p_origin integer)

### Function Properties

Language Return Type PLPGSQL bigint

FUNCTION storeListen (li_origin, li_provider, li_receiver)

generate STORE_LISTEN event, indicating that receiver node li_receiver listens to node li_provider in order to get messages coming from node li_origin.

    begin
    	perform storeListen_int (p_origin, p_provider, p_receiver);
    	return  createEvent ('_schemadoc', 'STORE_LISTEN',
    			p_origin::text, p_provider::text, p_receiver::text);
    end;

## storelisten_int(p_li_receiver integer, p_li_provider integer, p_li_origin integer)

storelisten_int(p_li_receiver integer, p_li_provider integer, p_li_origin integer)

### Function Properties

Language Return Type PLPGSQL integer

FUNCTION storeListen_int (li_origin, li_provider, li_receiver)

Process STORE_LISTEN event, indicating that receiver node li_receiver listens to node li_provider in order to get messages coming from node li_origin.

    declare
    	v_exists		int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	select 1 into v_exists
    			from sl_listen
    			where li_origin = p_li_origin
    			and li_provider = p_li_provider
    			and li_receiver = p_li_receiver;
    	if not found then
    		-- ----
    		-- In case we receive STORE_LISTEN events before we know
    		-- about the nodes involved in this, we generate those nodes
    		-- as pending.
    		-- ----
    		if not exists (select 1 from sl_node
    						where no_id = p_li_origin) then
    			perform storeNode_int (p_li_origin, '<event pending>');
    		end if;
    		if not exists (select 1 from sl_node
    						where no_id = p_li_provider) then
    			perform storeNode_int (p_li_provider, '<event pending>');
    		end if;
    		if not exists (select 1 from sl_node
    						where no_id = p_li_receiver) then
    			perform storeNode_int (p_li_receiver, '<event pending>');
    		end if;

    		insert into sl_listen
    				(li_origin, li_provider, li_receiver) values
    				(p_li_origin, p_li_provider, p_li_receiver);
    	end if;

    	return 0;
    end;

## storenode(p_no_comment integer, p_no_id text)

storenode(p_no_comment integer, p_no_id text)

### Function Properties

Language Return Type PLPGSQL bigint

no_id - Node ID # no_comment - Human-oriented comment

Generate the STORE_NODE event for node no_id

    begin
    	perform storeNode_int (p_no_id, p_no_comment);
    	return  createEvent('_schemadoc', 'STORE_NODE',
    									p_no_id::text, p_no_comment::text);
    end;

## storenode_int(p_no_comment integer, p_no_id text)

storenode_int(p_no_comment integer, p_no_id text)

### Function Properties

Language Return Type PLPGSQL integer

no_id - Node ID # no_comment - Human-oriented comment

Internal function to process the STORE_NODE event for node no_id

    declare
    	v_old_row		record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check if the node exists
    	-- ----
    	select * into v_old_row
    			from sl_node
    			where no_id = p_no_id
    			for update;
    	if found then
    		-- ----
    		-- Node exists, update the existing row.
    		-- ----
    		update sl_node
    				set no_comment = p_no_comment
    				where no_id = p_no_id;
    	else
    		-- ----
    		-- New node, insert the sl_node row
    		-- ----
    		insert into sl_node
    				(no_id, no_active, no_comment,no_failed) values
    				(p_no_id, 'f', p_no_comment,false);
    	end if;

    	return p_no_id;
    end;

## storepath(p_pa_connretry integer, p_pa_conninfo integer, p_pa_client text, p_pa_server integer)

storepath(p_pa_connretry integer, p_pa_conninfo integer, p_pa_client text, p_pa_server integer)

### Function Properties

Language Return Type PLPGSQL bigint

FUNCTION storePath (pa_server, pa_client, pa_conninfo, pa_connretry)

Generate the STORE_PATH event indicating that node pa_client can access node pa_server using DSN pa_conninfo

    begin
    	perform storePath_int(p_pa_server, p_pa_client,
    			p_pa_conninfo, p_pa_connretry);
    	return  createEvent('_schemadoc', 'STORE_PATH',
    			p_pa_server::text, p_pa_client::text,
    			p_pa_conninfo::text, p_pa_connretry::text);
    end;

## storepath_int(p_pa_connretry integer, p_pa_conninfo integer, p_pa_client text, p_pa_server integer)

storepath_int(p_pa_connretry integer, p_pa_conninfo integer, p_pa_client text, p_pa_server integer)

### Function Properties

Language Return Type PLPGSQL integer

FUNCTION storePath (pa_server, pa_client, pa_conninfo, pa_connretry)

Process the STORE_PATH event indicating that node pa_client can access node pa_server using DSN pa_conninfo

    declare
    	v_dummy			int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check if the path already exists
    	-- ----
    	select 1 into v_dummy
    			from sl_path
    			where pa_server = p_pa_server
    			and pa_client = p_pa_client
    			for update;
    	if found then
    		-- ----
    		-- Path exists, update pa_conninfo
    		-- ----
    		update sl_path
    				set pa_conninfo = p_pa_conninfo,
    					pa_connretry = p_pa_connretry
    				where pa_server = p_pa_server
    				and pa_client = p_pa_client;
    	else
    		-- ----
    		-- New path
    		--
    		-- In case we receive STORE_PATH events before we know
    		-- about the nodes involved in this, we generate those nodes
    		-- as pending.
    		-- ----
    		if not exists (select 1 from sl_node
    						where no_id = p_pa_server) then
    			perform storeNode_int (p_pa_server, '<event pending>');
    		end if;
    		if not exists (select 1 from sl_node
    						where no_id = p_pa_client) then
    			perform storeNode_int (p_pa_client, '<event pending>');
    		end if;
    		insert into sl_path
    				(pa_server, pa_client, pa_conninfo, pa_connretry) values
    				(p_pa_server, p_pa_client, p_pa_conninfo, p_pa_connretry);
    	end if;

    	-- Rewrite sl_listen table
    	perform RebuildListenEntries();

    	return 0;
    end;

## storeset(p_set_comment integer, p_set_id text)

storeset(p_set_comment integer, p_set_id text)

### Function Properties

Language Return Type PLPGSQL bigint

Generate STORE_SET event for set set_id with human readable comment set_comment

    declare
    	v_local_node_id		int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	v_local_node_id := getLocalNodeId('_schemadoc');

    	insert into sl_set
    			(set_id, set_origin, set_comment) values
    			(p_set_id, v_local_node_id, p_set_comment);

    	return createEvent('_schemadoc', 'STORE_SET',
    			p_set_id::text, v_local_node_id::text, p_set_comment::text);
    end;

## storeset_int(p_set_comment integer, p_set_origin integer, p_set_id text)

storeset_int(p_set_comment integer, p_set_origin integer, p_set_id text)

### Function Properties

Language Return Type PLPGSQL integer

storeSet_int (set_id, set_origin, set_comment)

Process the STORE_SET event, indicating the new set with given ID, origin node, and human readable comment.

    declare
    	v_dummy				int4;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	select 1 into v_dummy
    			from sl_set
    			where set_id = p_set_id
    			for update;
    	if found then
    		update sl_set
    				set set_comment = p_set_comment
    				where set_id = p_set_id;
    	else
    		if not exists (select 1 from sl_node
    						where no_id = p_set_origin) then
    			perform storeNode_int (p_set_origin, '<event pending>');
    		end if;
    		insert into sl_set
    				(set_id, set_origin, set_comment) values
    				(p_set_id, p_set_origin, p_set_comment);
    	end if;

    	-- Run addPartialLogIndices() to try to add indices to unused sl_log_? table
    	perform addPartialLogIndices();

    	return p_set_id;
    end;

## string_agg(text)

string_agg(text)

### Function Properties

Language Return Type INTERNAL text

    aggregate_dummy

## subscribeset(p_omit_copy integer, p_sub_forward integer, p_sub_receiver integer, p_sub_provider boolean, p_sub_set boolean)

subscribeset(p_omit_copy integer, p_sub_forward integer, p_sub_receiver integer, p_sub_provider boolean, p_sub_set boolean)

### Function Properties

Language Return Type PLPGSQL bigint

subscribeSet (sub_set, sub_provider, sub_receiver, sub_forward, omit_copy)

Makes sure that the receiver is not the provider, then stores the subscription, and publishes the SUBSCRIBE_SET event to other nodes.

If omit_copy is true, then no data copy will be done.

    declare
    	v_set_origin		int4;
    	v_ev_seqno			int8;
    	v_ev_seqno2			int8;
    	v_rec			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	--
    	-- Check that the receiver exists
    	--
    	if not exists (select no_id from sl_node where no_id=
    	       	      p_sub_receiver) then
    		      raise exception 'Slony-I: subscribeSet() receiver % does not exist' , p_sub_receiver;
    	end if;

    	--
    	-- Check that the provider exists
    	--
    	if not exists (select no_id from sl_node where no_id=
    	       	      p_sub_provider) then
    		      raise exception 'Slony-I: subscribeSet() provider % does not exist' , p_sub_provider;
    	end if;

    	-- ----
    	-- Check that the origin and provider of the set are remote
    	-- ----
    	select set_origin into v_set_origin
    			from sl_set
    			where set_id = p_sub_set;
    	if not found then
    		raise exception 'Slony-I: subscribeSet(): set % not found', p_sub_set;
    	end if;
    	if v_set_origin = p_sub_receiver then
    		raise exception
    				'Slony-I: subscribeSet(): set origin and receiver cannot be identical';
    	end if;
    	if p_sub_receiver = p_sub_provider then
    		raise exception
    				'Slony-I: subscribeSet(): set provider and receiver cannot be identical';
    	end if;
    	-- ----
    	-- Check that this is called on the origin node
    	-- ----
    	if v_set_origin != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: subscribeSet() must be called on origin';
    	end if;

    	-- ---
    	-- Verify that the provider is either the origin or an active subscriber
    	-- Bug report #1362
    	-- ---
    	if v_set_origin <> p_sub_provider then
    		if not exists (select 1 from sl_subscribe
    			where sub_set = p_sub_set and
                                  sub_receiver = p_sub_provider and
    			      sub_forward and sub_active) then
    			raise exception 'Slony-I: subscribeSet(): provider % is not an active forwarding node for replication set %', p_sub_provider, p_sub_set;
    		end if;
    	end if;

    	-- ---
    	-- Enforce that all sets from one origin are subscribed
    	-- using the same data provider per receiver.
    	-- ----
    	if not exists (select 1 from sl_subscribe
    			where sub_set = p_sub_set and sub_receiver = p_sub_receiver) then
    		--
    		-- New subscription - error out if we have any other subscription
    		-- from that origin with a different data provider.
    		--
    		for v_rec in select sub_provider from sl_subscribe
    				join sl_set on set_id = sub_set
    				where set_origin = v_set_origin and sub_receiver = p_sub_receiver
    		loop
    			if v_rec.sub_provider <> p_sub_provider then
    				raise exception 'Slony-I: subscribeSet(): wrong provider % - existing subscription from origin % users provider %',
    					p_sub_provider, v_set_origin, v_rec.sub_provider;
    			end if;
    		end loop;
    	else
    		--
    		-- Existing subscription - in case the data provider changes and
    		-- there are other subscriptions, warn here. subscribeSet_int()
    		-- will currently change the data provider for those sets as well.
    		--
    		for v_rec in select set_id, sub_provider from sl_subscribe
    				join sl_set on set_id = sub_set
    				where set_origin = v_set_origin and sub_receiver = p_sub_receiver
    				and set_id <> p_sub_set
    		loop
    			if v_rec.sub_provider <> p_sub_provider then
    				raise exception 'Slony-I: subscribeSet(): also data provider for set % use resubscribe instead',
    					v_rec.set_id;
    			end if;
    		end loop;
    	end if;

    	-- ----
    	-- Create the SUBSCRIBE_SET event
    	-- ----
    	v_ev_seqno :=  createEvent('_schemadoc', 'SUBSCRIBE_SET',
    			p_sub_set::text, p_sub_provider::text, p_sub_receiver::text,
    			case p_sub_forward when true then 't' else 'f' end,
    			case p_omit_copy when true then 't' else 'f' end
                            );

    	-- ----
    	-- Call the internal procedure to store the subscription
    	-- ----
    	v_ev_seqno2:=subscribeSet_int(p_sub_set, p_sub_provider,
    			p_sub_receiver, p_sub_forward, p_omit_copy);

    	if v_ev_seqno2 is not null then
    	   v_ev_seqno:=v_ev_seqno2;
    	 end if;

    	return v_ev_seqno;
    end;

## subscribeset_int(p_omit_copy integer, p_sub_forward integer, p_sub_receiver integer, p_sub_provider boolean, p_sub_set boolean)

subscribeset_int(p_omit_copy integer, p_sub_forward integer, p_sub_receiver integer, p_sub_provider boolean, p_sub_set boolean)

### Function Properties

Language Return Type PLPGSQL integer

subscribeSet_int (sub_set, sub_provider, sub_receiver, sub_forward, omit_copy)

Internal actions for subscribing receiver sub_receiver to subscription set sub_set.

    declare
    	v_set_origin		int4;
    	v_sub_row			record;
    	v_seq_id			bigint;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Lookup the set origin
    	-- ----
    	select set_origin into v_set_origin
    			from sl_set
    			where set_id = p_sub_set;
    	if not found then
    		raise exception 'Slony-I: subscribeSet_int(): set % not found', p_sub_set;
    	end if;

    	-- ----
    	-- Provider change is only allowed for active sets
    	-- ----
    	if p_sub_receiver = getLocalNodeId('_schemadoc') then
    		select sub_active into v_sub_row from sl_subscribe
    				where sub_set = p_sub_set
    				and sub_receiver = p_sub_receiver;
    		if found then
    			if not v_sub_row.sub_active then
    				raise exception 'Slony-I: subscribeSet_int(): set % is not active, cannot change provider',
    						p_sub_set;
    			end if;
    		end if;
    	end if;

    	-- ----
    	-- Try to change provider and/or forward for an existing subscription
    	-- ----
    	update sl_subscribe
    			set sub_provider = p_sub_provider,
    				sub_forward = p_sub_forward
    			where sub_set = p_sub_set
    			and sub_receiver = p_sub_receiver;
    	if found then

    		-- ----
    		-- This is changing a subscriptoin. Make sure all sets from
    		-- this origin are subscribed using the same data provider.
    		-- For this we first check that the requested data provider
    		-- is subscribed to all the sets, the receiver is subscribed to.
    		-- ----
    		for v_sub_row in select set_id from sl_set
    				join sl_subscribe on set_id = sub_set
    				where set_origin = v_set_origin
    				and sub_receiver = p_sub_receiver
    				and sub_set <> p_sub_set
    		loop
    			if not exists (select 1 from sl_subscribe
    					where sub_set = v_sub_row.set_id
    					and sub_receiver = p_sub_provider
    					and sub_active and sub_forward)
    				and not exists (select 1 from sl_set
    					where set_id = v_sub_row.set_id
    					and set_origin = p_sub_provider)
    			then
    				raise exception 'Slony-I: subscribeSet_int(): node % is not a forwarding subscriber for set %',
    						p_sub_provider, v_sub_row.set_id;
    			end if;

    			-- ----
    			-- New data provider offers this set as well, change that
    			-- subscription too.
    			-- ----
    			update sl_subscribe
    					set sub_provider = p_sub_provider
    					where sub_set = v_sub_row.set_id
    					and sub_receiver = p_sub_receiver;
    		end loop;

    		-- ----
    		-- Rewrite sl_listen table
    		-- ----
    		perform RebuildListenEntries();

    		return p_sub_set;
    	end if;

    	-- ----
    	-- Not found, insert a new one
    	-- ----
    	if not exists (select true from sl_path
    			where pa_server = p_sub_provider
    			and pa_client = p_sub_receiver)
    	then
    		insert into sl_path
    				(pa_server, pa_client, pa_conninfo, pa_connretry)
    				values
    				(p_sub_provider, p_sub_receiver,
    				'<event pending>', 10);
    	end if;
    	insert into sl_subscribe
    			(sub_set, sub_provider, sub_receiver, sub_forward, sub_active)
    			values (p_sub_set, p_sub_provider, p_sub_receiver,
    				p_sub_forward, false);

    	-- ----
    	-- If the set origin is here, then enable the subscription
    	-- ----
    	if v_set_origin = getLocalNodeId('_schemadoc') then
    		select createEvent('_schemadoc', 'ENABLE_SUBSCRIPTION',
    				p_sub_set::text, p_sub_provider::text, p_sub_receiver::text,
    				case p_sub_forward when true then 't' else 'f' end,
    				case p_omit_copy when true then 't' else 'f' end
    				) into v_seq_id;
    		perform enableSubscription(p_sub_set,
    				p_sub_provider, p_sub_receiver);
    	end if;

    	-- ----
    	-- Rewrite sl_listen table
    	-- ----
    	perform RebuildListenEntries();

    	return p_sub_set;
    end;

## tablestovacuum()

tablestovacuum()

### Function Properties

Language Return Type PLPGSQL SET OF vactables

Return a list of tables that require frequent vacuuming. The function is used so that the list is not hardcoded into C code.

    declare
    	prec vactables%rowtype;
    begin
    	prec.nspname := '_schemadoc';
    	prec.relname := 'sl_event';
    	if ShouldSlonyVacuumTable(prec.nspname, prec.relname) then
    		return next prec;
    	end if;
    	prec.nspname := '_schemadoc';
    	prec.relname := 'sl_confirm';
    	if ShouldSlonyVacuumTable(prec.nspname, prec.relname) then
    		return next prec;
    	end if;
    	prec.nspname := '_schemadoc';
    	prec.relname := 'sl_setsync';
    	if ShouldSlonyVacuumTable(prec.nspname, prec.relname) then
    		return next prec;
    	end if;
    	prec.nspname := '_schemadoc';
    	prec.relname := 'sl_seqlog';
    	if ShouldSlonyVacuumTable(prec.nspname, prec.relname) then
    		return next prec;
    	end if;
    	prec.nspname := '_schemadoc';
    	prec.relname := 'sl_archive_counter';
    	if ShouldSlonyVacuumTable(prec.nspname, prec.relname) then
    		return next prec;
    	end if;
    	prec.nspname := '_schemadoc';
    	prec.relname := 'sl_components';
    	if ShouldSlonyVacuumTable(prec.nspname, prec.relname) then
    		return next prec;
    	end if;
    	prec.nspname := '_schemadoc';
    	prec.relname := 'sl_log_script';
    	if ShouldSlonyVacuumTable(prec.nspname, prec.relname) then
    		return next prec;
    	end if;
    	prec.nspname := 'pg_catalog';
    	prec.relname := 'pg_listener';
    	if ShouldSlonyVacuumTable(prec.nspname, prec.relname) then
    		return next prec;
    	end if;
    	prec.nspname := 'pg_catalog';
    	prec.relname := 'pg_statistic';
    	if ShouldSlonyVacuumTable(prec.nspname, prec.relname) then
    		return next prec;
    	end if;

       return;
    end

## terminatenodeconnections(p_failed_node integer)

terminatenodeconnections(p_failed_node integer)

### Function Properties

Language Return Type PLPGSQL integer

terminates all backends that have registered to be from the given node

    declare
    	v_row			record;
    begin
    	for v_row in select nl_nodeid, nl_conncnt,
    			nl_backendpid from sl_nodelock
    			where nl_nodeid = p_failed_node for update
    	loop
    		perform killBackend(v_row.nl_backendpid, 'TERM');
    		delete from sl_nodelock
    			where nl_nodeid = v_row.nl_nodeid
    			and nl_conncnt = v_row.nl_conncnt;
    	end loop;

    	return 0;
    end;

## uninstallnode()

uninstallnode()

### Function Properties

Language Return Type PLPGSQL integer

Reset the whole database to standalone by removing the whole replication system.

    declare
    	v_tab_row		record;
    begin
    	raise notice 'Slony-I: Please drop schema "_schemadoc"';
    	return 0;
    end;

## unlockset(p_set_id integer)

unlockset(p_set_id integer)

### Function Properties

Language Return Type PLPGSQL integer

Remove the special trigger from all tables of a set that disables access to it.

    declare
    	v_local_node_id		int4;
    	v_set_row			record;
    	v_tab_row			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that the set exists and that we are the origin
    	-- and that it is not already locked.
    	-- ----
    	v_local_node_id := getLocalNodeId('_schemadoc');
    	select * into v_set_row from sl_set
    			where set_id = p_set_id
    			for update;
    	if not found then
    		raise exception 'Slony-I: set % not found', p_set_id;
    	end if;
    	if v_set_row.set_origin <> v_local_node_id then
    		raise exception 'Slony-I: set % does not originate on local node',
    				p_set_id;
    	end if;
    	if v_set_row.set_locked isnull then
    		raise exception 'Slony-I: set % is not locked', p_set_id;
    	end if;

    	-- ----
    	-- Drop the lockedSet trigger from all tables in the set.
    	-- ----
    	for v_tab_row in select T.tab_id,
    			slon_quote_brute(PGN.nspname) || '.' ||
    			slon_quote_brute(PGC.relname) as tab_fqname
    			from sl_table T,
    				"pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN
    			where T.tab_set = p_set_id
    				and T.tab_reloid = PGC.oid
    				and PGC.relnamespace = PGN.oid
    			order by tab_id
    	loop
    		execute 'drop trigger "_schemadoc_lockedset" ' ||
    				'on ' || v_tab_row.tab_fqname;
    	end loop;

    	-- ----
    	-- Clear out the set_locked field
    	-- ----
    	update sl_set
    			set set_locked = NULL
    			where set_id = p_set_id;

    	return p_set_id;
    end;

## unsubscribe_abandoned_sets(p_failed_node integer)

unsubscribe_abandoned_sets(p_failed_node integer)

### Function Properties

Language Return Type PLPGSQL bigint

    declare
    v_row record;
    v_seq_id bigint;
    v_local_node int4;
    begin

    	select getLocalNodeId('_schemadoc') into
    			   v_local_node;

    	if found then
    		   --abandon all subscriptions from this origin.
    		for v_row in select sub_set,sub_receiver from
    			sl_subscribe, sl_set
    			where sub_set=set_id and set_origin=p_failed_node
    			and sub_receiver=v_local_node
    		loop
    				raise notice 'Slony-I: failover_abandon_set() is abandoning subscription to set % on node % because it is too far ahead', v_row.sub_set,
    				v_local_node;
    				--If this node is a provider for the set
    				--then the receiver needs to be unsubscribed.
    				--
    			select unsubscribeSet(v_row.sub_set,
    												v_local_node,true)
    				   into v_seq_id;
    		end loop;
    	end if;

    	return v_seq_id;
    end

## unsubscribeset(p_force integer, p_sub_receiver integer, p_sub_set boolean)

unsubscribeset(p_force integer, p_sub_receiver integer, p_sub_set boolean)

### Function Properties

Language Return Type PLPGSQL bigint

unsubscribeSet (sub_set, sub_receiver,force)

Unsubscribe node sub_receiver from subscription set sub_set. This is invoked on the receiver node. It verifies that this does not break any chains (e.g. - where sub_receiver is a provider for another node), then restores tables, drops Slony-specific keys, drops table entries for the set, drops the subscription, and generates an UNSUBSCRIBE_SET node to publish that the node is being dropped.

    declare
    	v_tab_row			record;
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- Check that this is called on the receiver node
    	-- ----
    	if p_sub_receiver != getLocalNodeId('_schemadoc') then
    		raise exception 'Slony-I: unsubscribeSet() must be called on receiver';
    	end if;

    	-- ----
    	-- Check that this does not break any chains
    	-- ----
    	if p_force=false and exists (select true from sl_subscribe
    			 where sub_set = p_sub_set
    				and sub_provider = p_sub_receiver)
    	then
    		raise exception 'Slony-I: Cannot unsubscribe set % while being provider',
    				p_sub_set;
    	end if;

    	if exists (select true from sl_subscribe
    			where sub_set = p_sub_set
    				and sub_provider = p_sub_receiver)
    	then
    		--delete the receivers of this provider.
    		--unsubscribeSet_int() will generate the event
    		--when it runs on the receiver.
    		delete from sl_subscribe
    			   where sub_set=p_sub_set
    			   and sub_provider=p_sub_receiver;
    	end if;

    	-- ----
    	-- Remove the replication triggers.
    	-- ----
    	for v_tab_row in select tab_id from sl_table
    			where tab_set = p_sub_set
    			order by tab_id
    	loop
    		perform alterTableDropTriggers(v_tab_row.tab_id);
    	end loop;

    	-- ----
    	-- Remove the setsync status. This will also cause the
    	-- worker thread to ignore the set and stop replicating
    	-- right now.
    	-- ----
    	delete from sl_setsync
    			where ssy_setid = p_sub_set;

    	-- ----
    	-- Remove all sl_table and sl_sequence entries for this set.
    	-- Should we ever subscribe again, the initial data
    	-- copy process will create new ones.
    	-- ----
    	delete from sl_table
    			where tab_set = p_sub_set;
    	delete from sl_sequence
    			where seq_set = p_sub_set;

    	-- ----
    	-- Call the internal procedure to drop the subscription
    	-- ----
    	perform unsubscribeSet_int(p_sub_set, p_sub_receiver);

    	-- Rewrite sl_listen table
    	perform RebuildListenEntries();

    	-- ----
    	-- Create the UNSUBSCRIBE_SET event
    	-- ----
    	return  createEvent('_schemadoc', 'UNSUBSCRIBE_SET',
    			p_sub_set::text, p_sub_receiver::text);
    end;

## unsubscribeset_int(p_sub_receiver integer, p_sub_set integer)

unsubscribeset_int(p_sub_receiver integer, p_sub_set integer)

### Function Properties

Language Return Type PLPGSQL integer

unsubscribeSet_int (sub_set, sub_receiver)

All the REAL work of removing the subscriber is done before the event is generated, so this function just has to drop the references to the subscription in sl_subscribe.

    declare
    begin
    	-- ----
    	-- Grab the central configuration lock
    	-- ----
    	lock table sl_config_lock;

    	-- ----
    	-- All the real work is done before event generation on the
    	-- subscriber.
    	-- ----

    	--if this event unsubscribes the provider of this node
    	--then this node should unsubscribe itself from the set as well.

    	if exists (select true from
    		   sl_subscribe where
    		   sub_set=p_sub_set and sub_provider=p_sub_receiver
    		   and sub_receiver=getLocalNodeId('_schemadoc'))
    	then
    	   perform unsubscribeSet(p_sub_set,getLocalNodeId('_schemadoc'),true);
    	end if;

    	delete from sl_subscribe
    			where sub_set = p_sub_set
    				and sub_receiver = p_sub_receiver;

    	-- Rewrite sl_listen table
    	perform RebuildListenEntries();

    	return p_sub_set;
    end;

## updaterelname()

updaterelname()

### Function Properties

Language Return Type PLPGSQL integer

updateRelname()

    declare
            v_no_id                 int4;
            v_set_origin            int4;
    begin
            -- ----
            -- Grab the central configuration lock
            -- ----
            lock table sl_config_lock;

            update sl_table set
                    tab_relname = PGC.relname, tab_nspname = PGN.nspname
                    from pg_catalog.pg_class PGC, pg_catalog.pg_namespace PGN
                    where sl_table.tab_reloid = PGC.oid
                            and PGC.relnamespace = PGN.oid and
                        (tab_relname <> PGC.relname or tab_nspname <> PGN.nspname);
            update sl_sequence set
                    seq_relname = PGC.relname, seq_nspname = PGN.nspname
                    from pg_catalog.pg_class PGC, pg_catalog.pg_namespace PGN
                    where sl_sequence.seq_reloid = PGC.oid
                    and PGC.relnamespace = PGN.oid and
     		    (seq_relname <> PGC.relname or seq_nspname <> PGN.nspname);
            return 0;
    end;

## updatereloid(p_only_on_node integer, p_set_id integer)

updatereloid(p_only_on_node integer, p_set_id integer)

### Function Properties

Language Return Type PLPGSQL bigint

updateReloid(set_id, only_on_node)

Updates the respective reloids in sl_table and sl_seqeunce based on their respective FQN

    declare
            v_no_id                 int4;
            v_set_origin            int4;
    	prec			record;
    begin
            -- ----
            -- Check that we either are the set origin or a current
            -- subscriber of the set.
            -- ----
            v_no_id := getLocalNodeId('_schemadoc');
            select set_origin into v_set_origin
                            from sl_set
                            where set_id = p_set_id
                            for update;
            if not found then
                    raise exception 'Slony-I: set % not found', p_set_id;
            end if;
            if v_set_origin <> v_no_id
                    and not exists (select 1 from sl_subscribe
                            where sub_set = p_set_id
                            and sub_receiver = v_no_id)
            then
                    return 0;
            end if;

            -- ----
            -- If execution on only one node is requested, check that
            -- we are that node.
            -- ----
            if p_only_on_node > 0 and p_only_on_node <> v_no_id then
                    return 0;
            end if;

    	-- Update OIDs for tables to values pulled from non-table objects in pg_class
    	-- This ensures that we won't have collisions when repairing the oids
    	for prec in select tab_id from sl_table loop
    		update sl_table set tab_reloid = (select oid from pg_class pc where relkind <> 'r' and not exists (select 1 from sl_table t2 where t2.tab_reloid = pc.oid) limit 1)
    		where tab_id = prec.tab_id;
    	end loop;

    	for prec in select tab_id, tab_relname, tab_nspname from sl_table loop
    	        update sl_table set
            	        tab_reloid = (select PGC.oid
    	                from pg_catalog.pg_class PGC, pg_catalog.pg_namespace PGN
    	                where slon_quote_brute(PGC.relname) = slon_quote_brute(prec.tab_relname)
    	                        and PGC.relnamespace = PGN.oid
    				and slon_quote_brute(PGN.nspname) = slon_quote_brute(prec.tab_nspname))
    		where tab_id = prec.tab_id;
    	end loop;

    	for prec in select seq_id from sl_sequence loop
    		update sl_sequence set seq_reloid = (select oid from pg_class pc where relkind <> 'S' and not exists (select 1 from sl_sequence t2 where t2.seq_reloid = pc.oid) limit 1)
    		where seq_id = prec.seq_id;
    	end loop;

    	for prec in select seq_id, seq_relname, seq_nspname from sl_sequence loop
    	        update sl_sequence set
    	                seq_reloid = (select PGC.oid
    	                from pg_catalog.pg_class PGC, pg_catalog.pg_namespace PGN
    	                where slon_quote_brute(PGC.relname) = slon_quote_brute(prec.seq_relname)
                    	and PGC.relnamespace = PGN.oid
    			and slon_quote_brute(PGN.nspname) = slon_quote_brute(prec.seq_nspname))
    		where seq_id = prec.seq_id;
    	end loop;

    	return 1;
    end;

## upgradeschema(p_old text)

upgradeschema(p_old text)

### Function Properties

Language Return Type PLPGSQL text

Called during "update functions" by slonik to perform schema changes

    declare
    	v_tab_row	record;
    	v_query text;
    	v_keepstatus text;
    begin
    	-- If old version is pre-2.0, then we require a special upgrade process
    	if p_old like '1.%' then
    		raise exception 'Upgrading to Slony-I 2.x requires running slony_upgrade_20';
    	end if;

    	perform upgradeSchemaAddTruncateTriggers();

    	-- Change all Slony-I-defined columns that are "timestamp without time zone" to "timestamp *WITH* time zone"
    	if exists (select 1 from information_schema.columns c
                where table_schema = '_schemadoc' and data_type = 'timestamp without time zone'
    	    and exists (select 1 from information_schema.tables t where t.table_schema = c.table_schema and t.table_name = c.table_name and t.table_type = 'BASE TABLE')
    		and (c.table_name, c.column_name) in (('sl_confirm', 'con_timestamp'), ('sl_event', 'ev_timestamp'), ('sl_registry', 'reg_timestamp'),('sl_archive_counter', 'ac_timestamp')))
    	then

    	  -- Preserve sl_status
    	  select pg_get_viewdef('sl_status') into v_keepstatus;
    	  execute 'drop view sl_status';
    	  for v_tab_row in select table_schema, table_name, column_name from information_schema.columns c
                where table_schema = '_schemadoc' and data_type = 'timestamp without time zone'
    	    and exists (select 1 from information_schema.tables t where t.table_schema = c.table_schema and t.table_name = c.table_name and t.table_type = 'BASE TABLE')
    		and (table_name, column_name) in (('sl_confirm', 'con_timestamp'), ('sl_event', 'ev_timestamp'), ('sl_registry', 'reg_timestamp'),('sl_archive_counter', 'ac_timestamp'))
    	  loop
    		raise notice 'Changing Slony-I column [%.%] to timestamp WITH time zone', v_tab_row.table_name, v_tab_row.column_name;
    		v_query := 'alter table ' || slon_quote_brute(v_tab_row.table_schema) ||
                       '.' || v_tab_row.table_name || ' alter column ' || v_tab_row.column_name ||
                       ' type timestamp with time zone;';
    		execute v_query;
    	  end loop;
    	  -- restore sl_status
    	  execute 'create view sl_status as ' || v_keepstatus;
            end if;

    	if not exists (select 1 from information_schema.tables where table_schema = '_schemadoc' and table_name = 'sl_components') then
    	   v_query := '
    create table sl_components (
    	co_actor	 text not null primary key,
    	co_pid		 integer not null,
    	co_node		 integer not null,
    	co_connection_pid integer not null,
    	co_activity	  text,
    	co_starttime	  timestamptz not null,
    	co_event	  bigint,
    	co_eventtype 	  text
    ) without oids;
    ';
      	   execute v_query;
    	end if;

    	if not exists (select 1 from information_schema.tables t where table_schema = '_schemadoc' and table_name = 'sl_event_lock') then
    	   v_query := 'create table sl_event_lock (dummy integer);';
    	   execute v_query;
            end if;

    	if not exists (select 1 from information_schema.tables t
    			where table_schema = '_schemadoc'
    			and table_name = 'sl_apply_stats') then
    		v_query := '
    			create table sl_apply_stats (
    				as_origin			int4,
    				as_num_insert		int8,
    				as_num_update		int8,
    				as_num_delete		int8,
    				as_num_truncate		int8,
    				as_num_script		int8,
    				as_num_total		int8,
    				as_duration			interval,
    				as_apply_first		timestamptz,
    				as_apply_last		timestamptz,
    				as_cache_prepare	int8,
    				as_cache_hit		int8,
    				as_cache_evict		int8,
    				as_cache_prepare_max int8
    			) WITHOUT OIDS;';
    		execute v_query;
    	end if;

    	--
    	-- On the upgrade to 2.2, we change the layout of sl_log_N by
    	-- adding columns log_tablenspname, log_tablerelname, and
    	-- log_cmdupdncols as well as changing log_cmddata into
    	-- log_cmdargs, which is a text array.
    	--
    	if not check_table_field_exists('_schemadoc', 'sl_log_1', 'log_cmdargs') then
    		--
    		-- Check that the cluster is completely caught up
    		--
    		if check_unconfirmed_log() then
    			raise EXCEPTION 'cannot upgrade to new sl_log_N format due to existing unreplicated data';
    		end if;

    		--
    		-- Drop tables sl_log_1 and sl_log_2
    		--
    		drop table sl_log_1;
    		drop table sl_log_2;

    		--
    		-- Create the new sl_log_1
    		--
    		create table sl_log_1 (
    			log_origin          int4,
    			log_txid            bigint,
    			log_tableid         int4,
    			log_actionseq       int8,
    			log_tablenspname    text,
    			log_tablerelname    text,
    			log_cmdtype         "char",
    			log_cmdupdncols     int4,
    			log_cmdargs         text[]
    		) without oids;
    		create index sl_log_1_idx1 on sl_log_1
    			(log_origin, log_txid, log_actionseq);

    		comment on table sl_log_1 is 'Stores each change to be propagated to subscriber nodes';
    		comment on column sl_log_1.log_origin is 'Origin node from which the change came';
    		comment on column sl_log_1.log_txid is 'Transaction ID on the origin node';
    		comment on column sl_log_1.log_tableid is 'The table ID (from sl_table.tab_id) that this log entry is to affect';
    		comment on column sl_log_1.log_actionseq is 'The sequence number in which actions will be applied on replicas';
    		comment on column sl_log_1.log_tablenspname is 'The schema name of the table affected';
    		comment on column sl_log_1.log_tablerelname is 'The table name of the table affected';
    		comment on column sl_log_1.log_cmdtype is 'Replication action to take. U = Update, I = Insert, D = DELETE, T = TRUNCATE';
    		comment on column sl_log_1.log_cmdupdncols is 'For cmdtype=U the number of updated columns in cmdargs';
    		comment on column sl_log_1.log_cmdargs is 'The data needed to perform the log action on the replica';

    		--
    		-- Create the new sl_log_2
    		--
    		create table sl_log_2 (
    			log_origin          int4,
    			log_txid            bigint,
    			log_tableid         int4,
    			log_actionseq       int8,
    			log_tablenspname    text,
    			log_tablerelname    text,
    			log_cmdtype         "char",
    			log_cmdupdncols     int4,
    			log_cmdargs         text[]
    		) without oids;
    		create index sl_log_2_idx1 on sl_log_2
    			(log_origin, log_txid, log_actionseq);

    		comment on table sl_log_2 is 'Stores each change to be propagated to subscriber nodes';
    		comment on column sl_log_2.log_origin is 'Origin node from which the change came';
    		comment on column sl_log_2.log_txid is 'Transaction ID on the origin node';
    		comment on column sl_log_2.log_tableid is 'The table ID (from sl_table.tab_id) that this log entry is to affect';
    		comment on column sl_log_2.log_actionseq is 'The sequence number in which actions will be applied on replicas';
    		comment on column sl_log_2.log_tablenspname is 'The schema name of the table affected';
    		comment on column sl_log_2.log_tablerelname is 'The table name of the table affected';
    		comment on column sl_log_2.log_cmdtype is 'Replication action to take. U = Update, I = Insert, D = DELETE, T = TRUNCATE';
    		comment on column sl_log_2.log_cmdupdncols is 'For cmdtype=U the number of updated columns in cmdargs';
    		comment on column sl_log_2.log_cmdargs is 'The data needed to perform the log action on the replica';

    		create table sl_log_script (
    			log_origin			int4,
    			log_txid			bigint,
    			log_actionseq		int8,
    			log_cmdtype			"char",
    			log_cmdargs			text[]
    			) WITHOUT OIDS;
    		create index sl_log_script_idx1 on sl_log_script
    		(log_origin, log_txid, log_actionseq);

    		comment on table sl_log_script is 'Captures SQL script queries to be propagated to subscriber nodes';
    		comment on column sl_log_script.log_origin is 'Origin name from which the change came';
    		comment on column sl_log_script.log_txid is 'Transaction ID on the origin node';
    		comment on column sl_log_script.log_actionseq is 'The sequence number in which actions will be applied on replicas';
    		comment on column sl_log_2.log_cmdtype is 'Replication action to take. S = Script statement, s = Script complete';
    		comment on column sl_log_script.log_cmdargs is 'The DDL statement, optionally followed by selected nodes to execute it on.';

    		--
    		-- Put the log apply triggers back onto sl_log_1/2
    		--
    		create trigger apply_trigger
    			before INSERT on sl_log_1
    			for each row execute procedure logApply('_schemadoc');
    		alter table sl_log_1
    			enable replica trigger apply_trigger;
    		create trigger apply_trigger
    			before INSERT on sl_log_2
    			for each row execute procedure logApply('_schemadoc');
    		alter table sl_log_2
    			enable replica trigger apply_trigger;
    	end if;
    	if not exists (select 1 from information_schema.routines where routine_schema = '_schemadoc' and routine_name = 'string_agg') then
    	       CREATE AGGREGATE string_agg(text) (
    	   	      SFUNC=agg_text_sum,
    		      STYPE=text,
    		      INITCOND=''
    		      );
    	end if;
    	if not exists (select 1 from information_schema.views where table_schema='_schemadoc' and table_name='sl_failover_targets') then
    	   create view sl_failover_targets as
    	   	  select  set_id,
    		  set_origin as set_origin,
    		  sub1.sub_receiver as backup_id

    		  FROM
    		  sl_subscribe sub1
    		  ,sl_set set1
    		  where
     		  sub1.sub_set=set_id
    		  and sub1.sub_forward=true
    		  --exclude candidates where the set_origin
    		  --has a path a node but the failover
    		  --candidate has no path to that node
    		  and sub1.sub_receiver not in
    	    	  (select p1.pa_client from
    	    	  sl_path p1
    	    	  left outer join sl_path p2 on
    	    	  (p2.pa_client=p1.pa_client
    	    	  and p2.pa_server=sub1.sub_receiver)
    	    	  where p2.pa_client is null
    	    	  and p1.pa_server=set_origin
    	    	  and p1.pa_client<>sub1.sub_receiver
    	    	  )
    		  and sub1.sub_provider=set_origin
    		  --exclude any subscribers that are not
    		  --direct subscribers of all sets on the
    		  --origin
    		  and sub1.sub_receiver not in
    		  (select direct_recv.sub_receiver
    		  from

    			(--all direct receivers of the first set
    			select subs2.sub_receiver
    			from sl_subscribe subs2
    			where subs2.sub_provider=set1.set_origin
    		      	and subs2.sub_set=set1.set_id) as
    		      	direct_recv
    			inner join
    			(--all other sets from the origin
    			select set_id from sl_set set2
    			where set2.set_origin=set1.set_origin
    			and set2.set_id<>sub1.sub_set)
    			as othersets on(true)
    			left outer join sl_subscribe subs3
    			on(subs3.sub_set=othersets.set_id
    		   	and subs3.sub_forward=true
    		   	and subs3.sub_provider=set1.set_origin
    		   	and direct_recv.sub_receiver=subs3.sub_receiver)
    	    		where subs3.sub_receiver is null
    	    	);
    	end if;

    	if not check_table_field_exists('_schemadoc', 'sl_node', 'no_failed') then
    	   alter table sl_node add column no_failed bool;
    	   update sl_node set no_failed=false;
    	end if;
    	return p_old;
    end;