Merge DEV_SLONY. Scary. Still a few bugs and many rough edges. Also, there's no...
authorchriskl <chriskl>
Thu, 16 Jun 2005 14:40:10 +0000 (14:40 +0000)
committerchriskl <chriskl>
Thu, 16 Jun 2005 14:40:10 +0000 (14:40 +0000)
26 files changed:
CREDITS
HISTORY
classes/Misc.php
classes/database/Connection.php
classes/database/Postgres.php
classes/database/Postgres73.php
classes/database/Postgres74.php
classes/database/Postgres80.php
classes/plugins/Plugin.php [new file with mode: 0755]
classes/plugins/Slony.php [new file with mode: 0755]
conf/slony.inc.php [new file with mode: 0755]
database.php
lang/english.php
lang/recoded/english.php
libraries/lib.inc.php
plugin_slony.php [new file with mode: 0755]
sequences.php
sql.php
sql/plugins/slony1_base.sql [new file with mode: 0755]
sql/plugins/slony1_base.v73.sql [new file with mode: 0755]
sql/plugins/slony1_base.v74.sql [new file with mode: 0755]
sql/plugins/slony1_funcs.sql [new file with mode: 0755]
sql/plugins/slony1_funcs.v73.sql [new file with mode: 0755]
sql/plugins/slony1_funcs.v74.sql [new file with mode: 0755]
sql/plugins/xxid.v73.sql [new file with mode: 0755]
sql/plugins/xxid.v74.sql [new file with mode: 0755]

diff --git a/CREDITS b/CREDITS
index 45caa9e0c41fb470eac715fbb50bf93797f24a2d..8f044e27d89caab5f74950e8138f744d78e38ce7 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -58,3 +58,7 @@ Third Party Libraries
 
 - Highlight.php (Jacob D. Cohen of rafb.net)
 - XLoadTree2 (Erik Arvidsson & Emil A Eklund of webfx.eae.net)
+
+Corporate Sponsors
+
+- SpikeSource (www.spikesource.com) - Slony support
\ No newline at end of file
diff --git a/HISTORY b/HISTORY
index f08863a0970bd00ad09aacb795783a026fa59ad5..8d3b7e04f4d20869f21ba9b19fe922ae0f241b23 100644 (file)
--- a/HISTORY
+++ b/HISTORY
@@ -1,10 +1,11 @@
 phpPgAdmin History
 ------------------
 
-Version 3.6-dev
+Version 4.0-dev
 ---------------
 
 Features
+* Slony replication support (Thanks to sponsorship from SpikeSource)
 * Allow current database to be at the top
 * Allow base URL of PostgreSQL documentation to be configured
 * Allow variable size textarea when editing values (Juergen Weigert)
@@ -21,6 +22,7 @@ Features
   (Using XLoadTree2 from http://webfx.eae.net/)
 * Avoid getting and setting encoding queries if possible
 * Allow language change from the intro page at any time
+* Avoid version query in PHP 5 / PostgreSQL 7.4+
   
 Bugs
 * Tree Icons are displayed middle instead of top
index 6bafe1368a450574fd0154616b8663c9ed2c9b27..71b4debe15a31ac62cded16da08db6198f498d07 100644 (file)
@@ -2,7 +2,7 @@
        /**
         * Class to hold various commonly used functions
         *
-        * $Id: Misc.php,v 1.101 2005/05/16 14:30:14 jollytoad Exp $
+        * $Id: Misc.php,v 1.102 2005/06/16 14:40:11 chriskl Exp $
         */
         
        class Misc {
 
                                case 'database':
 #                                      $vars = $servervar . $databasevar . '&subject=database';
-                                       return array (
+                                       $tabs = array (
                                                'schemas' => array (
                                                        'title' => $lang['strschemas'],
                                                        'url'   => 'database.php',
                                                        'urlvars' => array('subject' => 'database'),
                                                        'hide'  => (!$data->hasSchemas()),
                                                        'help'  => 'pg.schema',
+                                                       'tree'  => false,
                                                ),
                                                'sql' => array (
                                                        'title' => $lang['strsql'],
                                                        'url'   => 'database.php',
                                                        'urlvars' => array('subject' => 'database', 'action' => 'sql'),
                                                        'help'  => 'pg.sql',
+                                                       'tree'  => false,
                                                ),
                                                'find' => array (
                                                        'title' => $lang['strfind'],
                                                        'url'   => 'database.php',
                                                        'urlvars' => array('subject' => 'database', 'action' => 'find'),
+                                                       'tree'  => false,
                                                ),
                                                'variables' => array (
                                                        'title' => $lang['strvariables'],
                                                        'urlvars' => array('subject' => 'database', 'action' => 'variables'),
                                                        'hide'  => (!$data->hasVariables()),
                                                        'help'  => 'pg.variable',
+                                                       'tree'  => false,
                                                ),
                                                'processes' => array (
                                                        'title' => $lang['strprocesses'],
                                                        'urlvars' => array('subject' => 'database', 'action' => 'processes'),
                                                        'hide'  => (!$data->hasProcesses()),
                                                        'help'  => 'pg.process',
+                                                       'tree'  => false,
                                                ),
                                                'admin' => array (
                                                        'title' => $lang['stradmin'],
                                                        'url'   => 'database.php',
                                                        'urlvars' => array('subject' => 'database', 'action' => 'admin'),
+                                                       'tree'  => false,
                                                ),
                                                'privileges' => array (
                                                        'title' => $lang['strprivileges'],
                                                        'urlvars' => array('subject' => 'database'),
                                                        'hide'  => (!isset($data->privlist['database'])),
                                                        'help'  => 'pg.privilege',
+                                                       'tree'  => false,
                                                ),
                                                'languages' => array (
                                                        'title' => $lang['strlanguages'],
                                                        'url'   => 'database.php',
                                                        'urlvars' => array('subject' => 'database', 'action' => 'export'),
                                                        'hide'  => (!$this->isDumpEnabled()),
+                                                       'tree'  => false,
                                                ),
                                        );
 
+                                       return $tabs;
                                case 'schema':
 #                                      $vars = $servervar . $databasevar . $schemavar . '&subject=schema';
                                        return array (
                                                        'urlvars' => array('subject' => 'schema'),
                                                        'hide'  => (!$data->hasSchemas()),
                                                        'help'  => 'pg.privilege',
+                                                       'tree'  => false,
                                                ),
                                        );
 
                        }
                }
                
+               function &adjustTabsForTree(&$tabs) {
+                       include_once('classes/ArrayRecordSet.php');
+                       
+                       foreach ($tabs as $i => $tab) {
+                               if ((isset($tab['hide']) && $tab['hide'] === true) || (isset($tab['tree']) && $tab['tree'] === false)) {
+                                       unset($tabs[$i]);
+                               }
+                       }
+                       return new ArrayRecordSet($tabs);
+               }
+               
                function icon($icon) {
                        global $conf;
                        $path = "images/themes/{$conf['theme']}/{$icon}";
index 76ed6364e064d8e1ed1212757d6ed86c476bff7e..8622505a3f38dbe155dfb601f28b0afe9c1d7124 100755 (executable)
@@ -3,7 +3,7 @@
 /**
  * Class to represent a database connection
  *
- * $Id: Connection.php,v 1.10 2005/06/02 01:35:51 chriskl Exp $
+ * $Id: Connection.php,v 1.11 2005/06/16 14:40:11 chriskl Exp $
  */
 
 include_once('./classes/database/ADODB_base.php');
index f6177ee381e20907d8e4fd88d54b125d5a2250de..9322124f3fa9f63df2a84b66bb4604d5d7387909 100755 (executable)
@@ -4,7 +4,7 @@
  * A class that implements the DB interface for Postgres
  * Note: This class uses ADODB and returns RecordSets.
  *
- * $Id: Postgres.php,v 1.262 2005/06/02 11:31:02 chriskl Exp $
+ * $Id: Postgres.php,v 1.263 2005/06/16 14:40:11 chriskl Exp $
  */
 
 // @@@ THOUGHT: What about inherits? ie. use of ONLY???
@@ -1801,14 +1801,15 @@ class Postgres extends ADODB_base {
         * Returns all sequences in the current database
         * @return A recordset
         */
-       function &getSequences() {
+       function &getSequences($all = false) {
+               // $all argument is ignored as it makes no difference
                $sql = "SELECT
                                        c.relname AS seqname,
                                        u.usename AS seqowner,
                                        (SELECT description FROM pg_description pd WHERE c.oid=pd.objoid) AS seqcomment
                                FROM 
                                        pg_class c, pg_user u WHERE c.relowner=u.usesysid AND c.relkind = 'S' ORDER BY seqname";
-               
+                                       
                return $this->selectSet( $sql );
        }
 
@@ -2135,14 +2136,17 @@ class Postgres extends ADODB_base {
        /**
         * Grabs a list of indexes for a table
         * @param $table The name of a table whose indexes to retrieve
+        * @param $unique Only get unique/pk indexes
         * @return A recordset
         */
-       function &getIndexes($table = '') {
+       function &getIndexes($table = '', $unique = false) {
                $this->clean($table);
                $sql = "SELECT c2.relname AS indname, i.indisprimary, i.indisunique, pg_get_indexdef(i.indexrelid) AS inddef
                        FROM pg_class c, pg_class c2, pg_index i
                        WHERE c.relname = '{$table}' AND c.oid = i.indrelid AND i.indexrelid = c2.oid
-                       ORDER BY c2.relname";
+               ";
+               if ($unique) $sql .= " AND i.indisunique ";
+               $sql .= " ORDER BY c2.relname";
 
                return $this->selectSet($sql);
        }
index 0ea905c196fd38a073488cbc947b04a6ceb51707..a445ddeaeee05f4ecf5ef9401113bfd8eb72a0c4 100644 (file)
@@ -4,7 +4,7 @@
  * A class that implements the DB interface for Postgres
  * Note: This class uses ADODB and returns RecordSets.
  *
- * $Id: Postgres73.php,v 1.143 2005/05/02 15:47:26 chriskl Exp $
+ * $Id: Postgres73.php,v 1.144 2005/06/16 14:40:12 chriskl Exp $
  */
 
 // @@@ THOUGHT: What about inherits? ie. use of ONLY???
@@ -587,11 +587,21 @@ class Postgres73 extends Postgres72 {
         * Returns all sequences in the current database
         * @return A recordset
         */
-       function &getSequences() {
-               $sql = "SELECT c.relname AS seqname, u.usename AS seqowner, pg_catalog.obj_description(c.oid, 'pg_class') AS seqcomment
-                       FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
-                       WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
-                       AND c.relkind = 'S' AND n.nspname='{$this->_schema}' ORDER BY seqname";
+       function &getSequences($all = false) {
+               if ($all) {
+                       // Exclude pg_catalog and information_schema tables
+                       $sql = "SELECT n.nspname, c.relname AS seqname, u.usename AS seqowner
+                               FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
+                               WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
+                               AND c.relkind = 'S' 
+                               AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') 
+                               ORDER BY nspname, seqname";
+               } else {
+                       $sql = "SELECT c.relname AS seqname, u.usename AS seqowner, pg_catalog.obj_description(c.oid, 'pg_class') AS seqcomment
+                               FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
+                               WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
+                               AND c.relkind = 'S' AND n.nspname='{$this->_schema}' ORDER BY seqname";
+               }
                        
                return $this->selectSet( $sql );
        }
@@ -612,9 +622,10 @@ class Postgres73 extends Postgres72 {
        /**
         * Grabs a list of indexes for a table
         * @param $table The name of a table whose indexes to retrieve
+        * @param $unique Only get unique/pk indexes
         * @return A recordset
         */
-       function &getIndexes($table = '') {
+       function &getIndexes($table = '', $unique = false) {
                $this->clean($table);
                
                $sql = "SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
@@ -622,7 +633,9 @@ class Postgres73 extends Postgres72 {
                        FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
                        WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid) 
                        AND c.oid = i.indrelid AND i.indexrelid = c2.oid
-                       ORDER BY c2.relname";
+               ";
+               if ($unique) $sql .= " AND i.indisunique ";
+               $sql .= " ORDER BY c2.relname";
 
                return $this->selectSet($sql);
        }
index 94c010bf12673ae67ce236d9d736c8e6f79ca9ca..6dab325a2088c2bcae638daa020e77085e2aacaf 100644 (file)
@@ -4,7 +4,7 @@
  * A class that implements the DB interface for Postgres
  * Note: This class uses ADODB and returns RecordSets.
  *
- * $Id: Postgres74.php,v 1.44 2005/04/30 18:02:01 soranzo Exp $
+ * $Id: Postgres74.php,v 1.45 2005/06/16 14:40:12 chriskl Exp $
  */
 
 include_once('./classes/database/Postgres73.php');
@@ -186,9 +186,10 @@ class Postgres74 extends Postgres73 {
        /**
         * Grabs a list of indexes for a table
         * @param $table The name of a table whose indexes to retrieve
+        * @param $unique Only get unique/pk indexes
         * @return A recordset
         */
-       function &getIndexes($table = '') {
+       function &getIndexes($table = '', $unique = false) {
                $this->clean($table);
 
                $sql = "SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
@@ -196,7 +197,9 @@ class Postgres74 extends Postgres73 {
                        FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
                        WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid) 
                        AND c.oid = i.indrelid AND i.indexrelid = c2.oid
-                       ORDER BY c2.relname";
+               ";
+               if ($unique) $sql .= " AND i.indisunique ";
+               $sql .= " ORDER BY c2.relname";
 
                return $this->selectSet($sql);
        }
index abba26118f6b2345f2bd01ad3da891cbeadb63a2..37f5ad16ce148123796f68de902055c5fbbb7167 100644 (file)
@@ -3,7 +3,7 @@
 /**
  * PostgreSQL 8.0 support
  *
- * $Id: Postgres80.php,v 1.13 2005/05/02 15:47:26 chriskl Exp $
+ * $Id: Postgres80.php,v 1.14 2005/06/16 14:40:12 chriskl Exp $
  */
 
 include_once('./classes/database/Postgres74.php');
@@ -324,13 +324,23 @@ class Postgres80 extends Postgres74 {
         * Returns all sequences in the current database
         * @return A recordset
         */
-       function &getSequences() {
-               $sql = "SELECT c.relname AS seqname, u.usename AS seqowner, pg_catalog.obj_description(c.oid, 'pg_class') AS seqcomment,
-                       (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
-                       FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
-                       WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
-                       AND c.relkind = 'S' AND n.nspname='{$this->_schema}' ORDER BY seqname";
-                       
+       function &getSequences($all = false) {
+               if ($all) {
+                       // Exclude pg_catalog and information_schema tables
+                       $sql = "SELECT n.nspname, c.relname AS seqname, u.usename AS seqowner
+                               FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
+                               WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
+                               AND c.relkind = 'S' 
+                               AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') 
+                               ORDER BY nspname, seqname";
+               } else {
+                       $sql = "SELECT c.relname AS seqname, u.usename AS seqowner, pg_catalog.obj_description(c.oid, 'pg_class') AS seqcomment,
+                               (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
+                               FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
+                               WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
+                               AND c.relkind = 'S' AND n.nspname='{$this->_schema}' ORDER BY seqname";
+               }
+                                       
                return $this->selectSet( $sql );
        }
        
diff --git a/classes/plugins/Plugin.php b/classes/plugins/Plugin.php
new file mode 100755 (executable)
index 0000000..687d4e2
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * A class that implements the plugin system
+ *
+ * $Id: Plugin.php,v 1.2 2005/06/16 14:40:12 chriskl Exp $
+ */
+
+class Plugin {
+
+       var $tabname = null;
+       var $config = null;
+       var $name = null;
+       
+       /**
+        * Constructor
+        */
+       function Plugin($name) {
+               $this->name = $name;
+               
+               // Read in configuration
+               if ($this->config !== null) {
+                       global $conf;
+                       include('./conf/' . $name . '.inc.php');                        
+               }
+       }
+
+}
+
+?>
diff --git a/classes/plugins/Slony.php b/classes/plugins/Slony.php
new file mode 100755 (executable)
index 0000000..3049a60
--- /dev/null
@@ -0,0 +1,834 @@
+<?php
+
+/**
+ * A class that implements the Slony 1.0.x support plugin
+ *
+ * $Id: Slony.php,v 1.2 2005/06/16 14:40:12 chriskl Exp $
+ */
+
+include_once('./classes/plugins/Plugin.php');
+
+class Slony extends Plugin {
+
+       var $config = 'slony.inc.php';
+       var $category = 'Replication';
+       var $slony_version;
+       var $slony_schema;
+       var $slony_cluster;
+       var $slony_owner;
+       var $slony_comment;
+       
+       /**
+        * Constructor
+        */
+       function Slony() {
+               $this->Plugin('slony');
+       }
+
+       /**
+        * Determines whether or not Slony is installed in the current
+        * database.
+        * @post Will populate version and schema fields, etc.
+        * @return True if Slony is installed, false otherwise.
+        */
+       function isEnabled() {
+               global $data;
+               
+               // Slony needs schemas
+               if (!$data->hasSchemas()) return false;
+               
+               // Check for the slonyversion() function and find the schema
+               // it's in.  We put an order by and limit 1 in here to guarantee
+               // only finding the first one, even if there are somehow two
+               // Slony schemas.
+               $sql = "SELECT pn.nspname AS schema, pu.usename AS owner, SUBSTRING(pn.nspname FROM 2) AS cluster,
+                                       pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment
+                                       FROM pg_catalog.pg_proc pp, pg_catalog.pg_namespace pn, pg_catalog.pg_user pu
+                                       WHERE pp.pronamespace=pn.oid
+                                       AND pn.nspowner = pu.usesysid
+                                       AND pp.proname='slonyversion'
+                                       AND pn.nspname LIKE '\\\\_%' 
+                                       ORDER BY pn.nspname LIMIT 1";
+               $rs = $data->selectSet($sql);
+               if ($rs->recordCount() == 1) {
+                       $schema = $rs->f['schema'];
+                       $this->slony_schema = $schema;
+                       $this->slony_owner = $rs->f['owner'];
+                       $this->slony_comment = $rs->f['nspcomment'];
+                       // Cluster name is schema minus "_" prefix.
+                       $this->slony_cluster = $rs->f['cluster'];
+                       $data->fieldClean($schema);
+                       $sql = "SELECT \"{$schema}\".slonyversion() AS version";
+                       $version = $data->selectField($sql, 'version');
+                       if ($version === -1) return false;
+                       else {
+                               $this->slony_version = $version;
+                               return true;
+                       }
+               }
+               else return false;
+       }       
+
+       // CLUSTERS
+
+       /**
+        * Gets the clusters in this database
+        */
+       function getClusters() {
+               include_once('classes/ArrayRecordSet.php');
+
+               if ($this->isEnabled()) {
+                       $clusters = array(array('cluster' => $this->slony_cluster, 'comment' => $this->slony_comment));
+               }
+               else
+                       $clusters = array();
+
+               return new ArrayRecordSet($clusters);
+       }
+       
+       /**
+        * Gets a single cluster
+        */
+       function getCluster() {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               
+               $sql = "SELECT no_id, no_comment, \"{$schema}\".slonyversion() AS version
+                                       FROM \"{$schema}\".sl_local_node_id, \"{$schema}\".sl_node
+                                       WHERE no_id=last_value";
+               
+               
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Drops an entire cluster.
+        */
+       function dropCluster() {
+               global $data;
+               
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+
+               $sql = "SELECT \"{$schema}\".uninstallnode(); DROP SCHEMA \"{$schema}\" CASCADE";
+               
+               return $data->execute($sql);
+       }
+       
+       /**
+        * Helper function to get a file into a string and replace
+        * variables.
+        */
+       function _getFile($file, $cluster) {
+               global $data;
+               $schema = '_' . $cluster;
+               $data->fieldClean($cluster);
+               
+               $buffer = null;
+               $handle = fopen("./sql/plugins/{$file}", "r");\r
+               while (!feof($handle)) {\r
+                  $temp = fgets($handle, 4096);
+                  $temp = str_replace('@CLUSTERNAME@', $cluster, $temp);
+                  
+                  $temp = str_replace('@NAMESPACE@', $schema, $temp);
+                  $buffer .= $temp;\r
+               }\r
+               fclose($handle);
+               
+               return $buffer;
+       }
+       
+       /**
+        * Initializes a new cluster
+        */
+       function initCluster($name, $no_id, $no_comment) {
+               global $data, $misc;
+               
+               // Prevent timeouts since cluster initialization can be slow
+               if (!ini_get('safe_mode')) set_time_limit(0);
+
+               $server_info = $misc->getServerInfo();
+               
+               if (!$data->isSuperUser($server_info['username'])) {
+                       return -10;
+               }
+                               
+               $status = $data->beginTransaction();
+               if ($status != 0) return -1;
+               
+               // Create the schema
+               $status = $data->createSchema('_' . $name);
+               if ($status != 0) {
+                       $data->rollbackTransaction();
+                       return -2;
+               }
+
+               // XXX: Support only Postgresql 7.4+ at the moment              
+               $sql = $this->_getFile('xxid.v74.sql', $name);
+               $status = $data->execute($sql);
+               if ($status != 0) {
+                       $data->rollbackTransaction();
+                       return -3;
+               }
+               
+               $sql = $this->_getFile('slony1_base.sql', $name);
+               $status = $data->execute($sql);
+               if ($status != 0) {
+                       $data->rollbackTransaction();
+                       return -3;
+               }
+/* THIS FILE IS EMPTY AND JUST CAUSES ERRORS
+               $sql = $this->_getFile('slony1_base.v74.sql', $name);
+               $status = $data->execute($sql);
+               if ($status != 0) {
+                       $data->rollbackTransaction();
+                       return -3;
+               }
+*/
+               $sql = $this->_getFile('slony1_funcs.sql', $name);
+               $status = $data->execute($sql);
+               if ($status != 0) {
+                       $data->rollbackTransaction();
+                       return -3;
+               }
+
+               $sql = $this->_getFile('slony1_funcs.v74.sql', $name);
+               $status = $data->execute($sql);
+               if ($status != 0) {
+                       $data->rollbackTransaction();
+                       return -3;
+               }
+               
+               $enabled = $this->isEnabled();
+               if (!$enabled) {
+                       $data->rollbackTransaction();
+                       return -4;
+               }
+               
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               $data->clean($no_comment);
+
+               $sql = "SELECT \"{$schema}\".initializelocalnode('{$no_id}', '{$no_comment}'); SELECT \"{$schema}\".enablenode('{$no_id}')";
+               $status = $data->execute($sql);
+               if ($status != 0) {
+                       $data->rollbackTransaction();
+                       return -5;
+               }
+               
+               return $data->endTransaction();
+       }
+       
+       // NODES
+
+       /**
+        * Gets the nodes in this database
+        */
+       function getNodes() {
+               global $data;
+               
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               
+               $sql = "SELECT * FROM \"{$schema}\".sl_node ORDER BY no_comment";
+               
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Gets a single node
+        */
+       function getNode($no_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               
+               $sql = "SELECT * FROM \"{$schema}\".sl_node WHERE no_id='{$no_id}'";
+               
+               return $data->selectSet($sql);
+       }
+       
+       /**
+        * Creates a node
+        */
+       function createNode($no_id, $no_comment) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_comment);
+               $data->clean($no_id);
+               
+               if ($no_id != '')
+                       $sql = "SELECT \"{$schema}\".storenode('{$no_id}', '{$no_comment}')";
+               else
+                       $sql = "SELECT \"{$schema}\".storenode((SELECT COALESCE(MAX(no_id), 0) + 1 FROM \"{$schema}\".sl_node), '{$no_comment}')";
+
+               return $data->execute($sql);
+       }
+
+       /**
+        * Drops a node
+        */
+       function dropNode($no_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+
+               $sql = "SELECT \"{$schema}\".dropnode('{$no_id}')";
+
+               return $data->execute($sql);
+       }       
+       
+       // REPLICATION SETS
+       
+       /**
+        * Gets the replication sets in this database
+        */
+       function getReplicationSets() {
+               global $data;
+               
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               
+               $sql = "SELECT *, set_locked IS NOT NULL AS is_locked FROM \"{$schema}\".sl_set ORDER BY set_id";
+               
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Gets a particular replication set
+        */
+       function getReplicationSet($set_id) {
+               global $data;
+               
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);
+               
+               $sql = "SELECT *, (SELECT COUNT(*) FROM \"{$schema}\".sl_subscribe ssub WHERE ssub.sub_set=ss.set_id) AS subscriptions,
+                                       set_locked IS NOT NULL AS is_locked
+                                       FROM \"{$schema}\".sl_set ss, \"{$schema}\".sl_node sn
+                                       WHERE ss.set_origin=sn.no_id
+                                       AND set_id='{$set_id}'";
+               
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Creates a set
+        */
+       function createReplicationSet($set_id, $set_comment) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_comment);
+               $data->clean($set_id);
+
+               if ($set_id != '')
+                       $sql = "SELECT \"{$schema}\".storeset('{$set_id}', '{$set_comment}')";
+               else
+                       $sql = "SELECT \"{$schema}\".storeset((SELECT COALESCE(MAX(set_id), 0) + 1 FROM \"{$schema}\".sl_set), '{$set_comment}')";
+
+               return $data->execute($sql);
+       }
+
+       /**
+        * Drops a set
+        */
+       function dropReplicationSet($set_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);
+
+               $sql = "SELECT \"{$schema}\".dropset('{$set_id}')";
+
+               return $data->execute($sql);
+       }       
+
+       /**
+        * Locks or unlocks a set
+        * @param boolean $lock True to lock, false to unlock
+        */
+       function lockReplicationSet($set_id, $lock) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);
+
+               if ($lock)
+                       $sql = "SELECT \"{$schema}\".lockset('{$set_id}')";
+               else
+                       $sql = "SELECT \"{$schema}\".unlockset('{$set_id}')";           
+
+               return $data->execute($sql);
+       }       
+               
+       /**
+        * Merges two sets
+        */
+       function mergeReplicationSet($set_id, $target) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);
+               $data->clean($target);
+
+               $sql = "SELECT \"{$schema}\".mergeset('{$target}', '{$set_id}')";
+
+               return $data->execute($sql);
+       }
+
+       /**
+        * Moves a set to a new origin
+        */
+       function moveReplicationSet($set_id, $new_origin) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);
+               $data->clean($new_origin);
+
+               $sql = "SELECT \"{$schema}\".moveset('{$set_id}', '{$new_origin}')";
+
+               return $data->execute($sql);
+       }
+
+       /**
+        * Executes schema changing DDL set on nodes
+        */
+       function executeReplicationSet($set_id, $script) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);
+               $data->clean($script);
+
+               $sql = "SELECT \"{$schema}\".ddlscript('{$set_id}', '{$script}')";
+
+               return $data->execute($sql);
+       }       
+       
+       // TABLES
+       
+       /**
+        * Return all tables in a replication set
+        * @param $set_id The ID of the replication set
+        * @return Tables in the replication set, sorted alphabetically 
+        */
+       function getTables($set_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);          
+
+               $sql = "SELECT st.tab_id, c.relname, n.nspname, n.nspname||'.'||c.relname AS qualname,
+                                       pg_catalog.pg_get_userbyid(c.relowner) AS relowner, 
+                                       reltuples::integer";
+               // Tablespace
+               if ($data->hasTablespaces()) {
+                       $sql .= ", (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace";
+               }
+               $sql .= " FROM pg_catalog.pg_class c, \"{$schema}\".sl_table st, pg_catalog.pg_namespace n
+                                       WHERE c.oid=st.tab_reloid
+                                       AND c.relnamespace=n.oid
+                                       AND st.tab_set='{$set_id}'
+                                       ORDER BY n.nspname, c.relname";
+
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Adds a table to a replication set
+        */
+       function addTable($set_id, $tab_id, $nspname, $relname, $idxname, $comment, $storedtriggers) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);
+               $data->clean($tab_id);
+               $fqname = $nspname . '.' . $relname;
+               $data->clean($fqname);
+               $data->clean($nspname);
+               $data->clean($relname);
+               $data->clean($idxname);
+               $data->clean($comment);
+
+               $hastriggers = (sizeof($storedtriggers) > 0);
+               if ($hastriggers) {
+                       // Begin a transaction
+                       $status = $data->beginTransaction();
+                       if ($status != 0) return -1;
+               }
+
+               if ($tab_id != '')
+                       $sql = "SELECT \"{$schema}\".setaddtable('{$set_id}', '{$tab_id}', '{$fqname}', '{$idxname}', '{$comment}')";
+               else {
+                       $sql = "SELECT \"{$schema}\".setaddtable('{$set_id}', (SELECT COALESCE(MAX(tab_id), 0) + 1 FROM \"{$schema}\".sl_table), '{$fqname}', '{$idxname}', '{$comment}')";                     
+               }
+
+               $status = $data->execute($sql); 
+               if ($status != 0) {
+                       if ($hastriggers) $data->rollbackTransaction();
+                       return -3;
+               }               
+
+               // If we are storing triggers, we need to know the tab_id that was assigned to the table
+               if ($tab_id == '' && $hastriggers) {
+                       $sql = "SELECT tab_id
+                                               FROM \"{$schema}\".sl_table
+                                               WHERE tab_set='{$set_id}'
+                                               AND tab_reloid=(SELECT pc.oid FROM pg_catalog.pg_class pc, pg_namespace pn 
+                                                                                               WHERE pc.relnamespace=pn.oid AND pc.relname='{$relname}'
+                                                                                               AND pn.nspname='{$nspname}')";
+                       $tab_id = $data->selectField($sql, 'tab_id');
+                       if ($tab_id === -1) {
+                               $data->rollbackTransaction();
+                               return -4;
+                       }
+               }
+               
+               // Store requested triggers
+               if ($hastriggers) {
+                       foreach ($storedtriggers as $tgname) {
+                               $data->clean($tgname);
+                               $sql = "SELECT \"{$schema}\".storetrigger('{$tab_id}', '{$tgname}')";
+                               $status = $data->execute($sql); 
+                               if ($status != 0) {
+                                       $data->rollbackTransaction();
+                                       return -5;
+                               }               
+                       }                               
+               }
+
+               if ($hastriggers)
+                       return $data->endTransaction();
+               else
+                       return $status;
+       }
+               
+       /**
+        * Removes a table from a replication set
+        */
+       function removeTable($tab_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($tab_id);
+
+               $sql = "SELECT \"{$schema}\".setdroptable('{$tab_id}')";
+
+               return $data->execute($sql);
+       }               
+
+       /**
+        * Moves a table to another replication set
+        */
+       function moveTable($tab_id, $new_set_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($tab_id);
+               $data->clean($new_set_id);
+
+               $sql = "SELECT \"{$schema}\".setmovetable('{$tab_id}', '{$new_set_id}')";
+
+               return $data->execute($sql);
+       }               
+
+       // SEQUENCES
+       
+       /**
+        * Return all sequences in a replication set
+        * @param $set_id The ID of the replication set
+        * @return Sequences in the replication set, sorted alphabetically 
+        */
+       function getSequences($set_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);          
+
+               $sql = "SELECT ss.seq_id, c.relname AS seqname, n.nspname, n.nspname||'.'||c.relname AS qualname,
+                                               pg_catalog.obj_description(c.oid, 'pg_class') AS seqcomment,
+                                               pg_catalog.pg_get_userbyid(c.relowner) AS seqowner 
+                                       FROM pg_catalog.pg_class c, \"{$schema}\".sl_sequence ss, pg_catalog.pg_namespace n
+                                       WHERE c.oid=ss.seq_reloid
+                                       AND c.relnamespace=n.oid
+                                       AND ss.seq_set='{$set_id}'
+                                       ORDER BY n.nspname, c.relname";
+
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Adds a sequence to a replication set
+        */
+       function addSequence($set_id, $seq_id, $fqname, $comment) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);
+               $data->clean($seq_id);
+               $data->clean($fqname);
+               $data->clean($comment);
+
+               if ($seq_id != '')
+                       $sql = "SELECT \"{$schema}\".setaddsequence('{$set_id}', '{$seq_id}', '{$fqname}', '{$comment}')";
+               else
+                       $sql = "SELECT \"{$schema}\".setaddsequence('{$set_id}', (SELECT COALESCE(MAX(seq_id), 0) + 1 FROM \"{$schema}\".sl_sequence), '{$fqname}', '{$comment}')";
+
+               return $data->execute($sql);    }               
+               
+       /**
+        * Removes a sequence from a replication set
+        */
+       function removeSequence($seq_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($seq_id);
+
+               $sql = "SELECT \"{$schema}\".setdropsequence('{$seq_id}')";
+
+               return $data->execute($sql);
+       }               
+
+       /**
+        * Moves a sequence to another replication set
+        */
+       function moveSequence($seq_id, $new_set_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($seq_id);
+               $data->clean($new_set_id);
+
+               $sql = "SELECT \"{$schema}\".setmovesequence('{$seq_id}', '{$new_set_id}')";
+
+               return $data->execute($sql);
+       }               
+       
+       // SUBSCRIPTIONS
+       
+       /**
+        * Gets all nodes subscribing to a set
+        * @param $set_id The ID of the replication set
+        * @return Nodes subscribing to this set
+        */
+       function getSubscribedNodes($set_id) {
+               global $data;
+               
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);          
+               
+               $sql = "SELECT sn.*, ss.sub_set
+                                       FROM \"{$schema}\".sl_subscribe ss, \"{$schema}\".sl_node sn
+                                       WHERE ss.sub_set='{$set_id}'
+                                       AND ss.sub_receiver = sn.no_id
+                                       ORDER BY sn.no_comment";
+               
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Gets all nodes subscribing to a set
+        * @param $set_id The ID of the replication set
+        * @return Nodes subscribing to this set
+        */
+       function getSubscription($set_id, $no_id) {
+               global $data;
+               
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($set_id);
+               $data->clean($no_id);
+               
+               $sql = "SELECT ss.*, sn.no_comment AS receiver, sn2.no_comment AS provider
+                                       FROM \"{$schema}\".sl_subscribe ss, \"{$schema}\".sl_node sn, \"{$schema}\".sl_node sn2
+                                       WHERE ss.sub_set='{$set_id}'
+                                       AND ss.sub_receiver = sn.no_id
+                                       AND ss.sub_provider = sn2.no_id
+                                       AND sn.no_id='{$no_id}'";
+               
+               return $data->selectSet($sql);
+       }
+       
+       // NODES
+       
+       /**
+        * Gets node paths
+        */
+       function getPaths($no_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               
+               $sql = "SELECT * FROM \"{$schema}\".sl_path sp, \"{$schema}\".sl_node sn
+                                       WHERE sp.pa_server=sn.no_id 
+                                       AND sp.pa_client='{$no_id}'
+                                       ORDER BY sn.no_comment";
+               
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Gets node path details
+        */
+       function getPath($no_id, $path_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               $data->clean($path_id);
+               
+               $sql = "SELECT * FROM \"{$schema}\".sl_path sp, \"{$schema}\".sl_node sn
+                                       WHERE sp.pa_server=sn.no_id 
+                                       AND sp.pa_client='{$no_id}'
+                                       AND sn.no_id='{$path_id}'";
+               
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Creates a path
+        */
+       function createPath($no_id, $server, $conn, $retry) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               $data->clean($server);
+               $data->clean($conn);
+               $data->clean($retry);
+
+               $sql = "SELECT \"{$schema}\".storepath('{$server}', '{$no_id}', '{$conn}', '{$retry}')";
+
+               return $data->execute($sql);
+       }
+
+       /**
+        * Drops a path
+        */
+       function dropPath($no_id, $path_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               $data->clean($path_id);
+
+               $sql = "SELECT \"{$schema}\".droppath('{$path_id}', '{$no_id}')";
+
+               return $data->execute($sql);
+       }       
+       
+       // LISTENS
+       
+       /**
+        * Gets node listens
+        */
+       function getListens($no_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               
+               $sql = "SELECT * FROM \"{$schema}\".sl_listen sl, \"{$schema}\".sl_node sn
+                                       WHERE sl.li_provider=sn.no_id 
+                                       AND sl.li_receiver='{$no_id}'
+                                       ORDER BY sn.no_comment";
+               
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Gets node listen details
+        */
+       function getListen($no_id, $listen_id) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               $data->clean($listen_id);
+               
+               $sql = "SELECT sl.*, sn.*, sn2.no_comment AS origin FROM \"{$schema}\".sl_listen sl, \"{$schema}\".sl_node sn, \"{$schema}\".sl_node sn2
+                                       WHERE sl.li_provider=sn.no_id 
+                                       AND sl.li_receiver='{$no_id}'
+                                       AND sn.no_id='{$listen_id}'
+                                       AND sn2.no_id=sl.li_origin";
+               
+               return $data->selectSet($sql);
+       }
+
+       /**
+        * Creates a listen
+        */
+       function createListen($no_id, $origin, $provider) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               $data->clean($origin);
+               $data->clean($provider);
+
+               $sql = "SELECT \"{$schema}\".storelisten('{$origin}', '{$provider}', '{$no_id}')";
+
+               return $data->execute($sql);
+       }
+
+       /**
+        * Drops a listen
+        */
+       function dropListen($no_id, $origin, $provider) {
+               global $data;
+
+               $schema = $this->slony_schema;
+               $data->fieldClean($schema);
+               $data->clean($no_id);
+               $data->clean($origin);
+               $data->clean($provider);
+
+               $sql = "SELECT \"{$schema}\".droplisten('{$origin}', '{$provider}', '{$no_id}')";
+
+               return $data->execute($sql);
+       }       
+       
+       // ACTIONS
+       
+       
+
+}
+
+?>
diff --git a/conf/slony.inc.php b/conf/slony.inc.php
new file mode 100755 (executable)
index 0000000..5094074
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+
+       /**
+        * Slony plugin configuration
+        *
+        * $Id: slony.inc.php,v 1.2 2005/06/16 14:40:13 chriskl Exp $
+        */
+
+       $conf['slony_enabled'] = true;
+       
+?>
index ae7566c67d1ece3105fdc5fabbd19d88710de85c..4c9768aaf67b1ed379e95d8301b4b57fba42d623 100755 (executable)
@@ -3,7 +3,7 @@
        /**
         * Manage schemas within a database
         *
-        * $Id: database.php,v 1.68 2005/06/06 15:13:11 soranzo Exp $
+        * $Id: database.php,v 1.69 2005/06/16 14:40:10 chriskl Exp $
         */
 
        // Include application functions
        }
        
        function doTree() {
-               global $misc, $data, $lang, $PHP_SELF;
+               global $misc, $data, $lang, $PHP_SELF, $slony;
                
                $schemas = &$data->getSchemas();
                
                                                                'action'  => 'subtree',
                                                                'schema'  => field('nspname')
                                                        )
-                                               )
+                                               ),
+                       'nofoot' => true
                );
                
                $misc->printTreeXML($schemas, $attrs);
+               
+               $tabs = $misc->getNavTabs('database');
+               $tabs['slony'] = array (
+                                                               'title' => 'Slony',
+                                                               'url'   => 'plugin_slony.php',
+                                                               'urlvars' => array('action' => 'clusters_properties'),
+                                                               'hide'  => false,
+                                                               'help'  => ''
+                                                       );
+               
+               $items =& $misc->adjustTabsForTree($tabs);
+               
+               $attrs = array(
+                       'text'   => noEscape(field('title')),
+                       'icon'   => field('icon', 'folder'),
+                       'action' => url(field('url'),
+                                                       $reqvars,
+                                                       field('urlvars', array())
+                                               ),
+                       'branch' => url(field('url'),
+                                                       $reqvars,
+                                                       field('urlvars'),
+                                                       array('action' => 'tree')
+                                               ),
+                       'nohead' => true
+               );
+               
+               $misc->printTreeXML($items, $attrs);
+               
                exit;
        }
        
        function doSubTree() {
-               global $misc, $data, $lang;
+               global $misc, $data, $lang, $slony;
                
-               include_once('classes/ArrayRecordSet.php');
                $tabs = $misc->getNavTabs('schema');
                
-               // Remove Privileges link
-               unset($tabs['privileges']);
-               
-               // Remove hidden links
-               foreach ($tabs as $i => $tab) {
-                       if (isset($tab['hide']) && $tab['hide'] === true)
-                               unset($tabs[$i]);
-               }
-               $items =& new ArrayRecordSet($tabs);
+               $items =& $misc->adjustTabsForTree($tabs);
                
                $reqvars = $misc->getRequestVars('schema');
                
index 12681c9d323a555404876c9a30788a5b12ff3052..b34e00157b511906ad0be29ad7bc4564ee7f5dde 100755 (executable)
@@ -4,7 +4,7 @@
         * English language file for phpPgAdmin.  Use this as a basis
         * for new translations.
         *
-        * $Id: english.php,v 1.177 2005/06/06 15:13:12 soranzo Exp $
+        * $Id: english.php,v 1.178 2005/06/16 14:40:13 chriskl Exp $
         */
 
        // Language and character set
@@ -95,6 +95,7 @@
        $lang['strreindex'] = 'Reindex';
        $lang['strrun'] = 'Run';
        $lang['stradd'] = 'Add';
+       $lang['strremove'] = 'Remove';
        $lang['strevent'] = 'Event';
        $lang['strwhere'] = 'Where';
        $lang['strinstead'] = 'Do Instead';
        $lang['strviewdroppedbad'] = 'View drop failed.';
        $lang['strviewupdated'] = 'View updated.';
        $lang['strviewupdatedbad'] = 'View update failed.';
-       $lang['strviewlink'] = 'Linking Keys';
-       $lang['strviewconditions'] = 'Additional Conditions';
+       $lang['strviewlink'] = 'Linking keys';
+       $lang['strviewconditions'] = 'Additional conditions';
        $lang['strcreateviewwiz'] = 'Create view with wizard';
 
        // Sequences
        $lang['strtablespacealtered'] = 'Tablespace altered.';
        $lang['strtablespacealteredbad'] = 'Tablespace alteration failed.';
 
+       // Slony clusters
+       $lang['strnoclusters'] = 'No clusters found.';
+       $lang['strconfdropcluster'] = 'Are you sure you want to drop cluster "%s"?';
+       $lang['strclusterdropped'] = 'Cluster dropped.';
+       $lang['strclusterdroppedbad'] = 'Cluster drop failed.';
+       $lang['strinitcluster'] = 'Initialize Cluster';
+       $lang['strclustercreated'] = 'Cluster initialized.';
+       $lang['strclustercreatedbad'] = 'Cluster initialization failed.';
+       $lang['strclusterneedsname'] = 'You must give a name for the cluster.';
+       $lang['strclusterneedsnodeid'] = 'You must give an ID for the local node.';
+       
+       // Slony nodes
+       $lang['strnodes'] = 'Nodes';
+       $lang['strnonodes'] = 'No nodes found.';
+       $lang['strcreatenode'] = 'Create node';
+       $lang['strid'] = 'ID';
+       $lang['stractive'] = 'Active';
+       $lang['strnodecreated'] = 'Node created.';
+       $lang['strnodecreatedbad'] = 'Node creation failed.';
+       $lang['strconfdropnode'] = 'Are you sure you want to drop node "%s"?';
+       $lang['strnodedropped'] = 'Node dropped.';
+       $lang['strnodedroppedbad'] = 'Node drop failed';
+       $lang['strfailover'] = 'Failover';
+       $lang['strnodefailedover'] = 'Node failed over.';
+       $lang['strnodefailedoverbad'] = 'Node fail over fail.';
+       
+       // Slony paths  
+       $lang['strpaths'] = 'Paths';
+       $lang['strnopaths'] = 'No paths found.';
+       $lang['strcreatepath'] = 'Create path';
+       $lang['strnodename'] = 'Node name';
+       $lang['strnodeid'] = 'Node ID';
+       $lang['strconninfo'] = 'Connection string';
+       $lang['strconnretry'] = 'Seconds before retry to connect';
+       $lang['strpathneedsconninfo'] = 'You must give a connection string for the path.';
+       $lang['strpathneedsconnretry'] = 'You must give the number of seconds to wait before retry to connect.';
+       $lang['strpathcreated'] = 'Path created.';
+       $lang['strpathcreatedbad'] = 'Path creation failed.';
+       $lang['strconfdroppath'] = 'Are you sure you want to drop path "%s"?';
+       $lang['strpathdropped'] = 'Path dropped.';
+       $lang['strpathdroppedbad'] = 'Path drop failed.';
+
+       // Slony listens
+       $lang['strlistens'] = 'Listens';
+       $lang['strnolistens'] = 'No listens found.';
+       $lang['strcreatelisten'] = 'Create listen';
+       $lang['strlistencreated'] = 'Listen created.';
+       $lang['strlistencreatedbad'] = 'Listen creation failed.';
+       $lang['strconfdroplisten'] = 'Are you sure you want to drop listen "%s"?';
+       $lang['strlistendropped'] = 'Listen dropped.';
+       $lang['strlistendroppedbad'] = 'Listen drop failed.';
+
+       // Slony replication sets
+       $lang['strrepsets'] = 'Replication sets';
+       $lang['strnorepsets'] = 'No replication sets found.';
+       $lang['strcreaterepset'] = 'Create replication set';
+       $lang['strrepsetcreated'] = 'Replication set created.';
+       $lang['strrepsetcreatedbad'] = 'Replication set creation failed.';
+       $lang['strconfdroprepset'] = 'Are you sure you want to drop replication set "%s"?';
+       $lang['strrepsetdropped'] = 'Replication set dropped.';
+       $lang['strrepsetdroppedbad'] = 'Replication set drop failed.';
+       $lang['strmerge'] = 'Merge';
+       $lang['strmergeinto'] = 'Merge Into';
+       $lang['strrepsetmerged'] = 'Replication sets merged.';
+       $lang['strrepsetmergedbad'] = 'Replication sets merge failed.';
+       $lang['strmove'] = 'Move';
+       $lang['strneworigin'] = 'New Origin';
+       $lang['strrepsetmoved'] = 'Replication set moved.';
+       $lang['strrepsetmovedbad'] = 'Replication set move failed.';
+       $lang['strnewrepset'] = 'New replication set';
+       $lang['strlock'] = 'Lock';
+       $lang['strlocked'] = 'Locked';
+       $lang['strunlock'] = 'Unlock';
+       $lang['strconflockrepset'] = 'Are you sure you want to lock replication set "%s"?';
+       $lang['strrepsetlocked'] = 'Replication set locked.';
+       $lang['strrepsetlockedbad'] = 'Replication set lock failed.';
+       $lang['strconfunlockrepset'] = 'Are you sure you want to unlock replication set "%s"?';
+       $lang['strrepsetunlocked'] = 'Replication set unlocked.';
+       $lang['strrepsetunlockedbad'] = 'Replication set unlock failed.';
+       $lang['strexecute'] = 'Execute';
+       $lang['stronlyonnode'] = 'Only on node';
+       $lang['strddlscript'] = 'DDL Script';
+       $lang['strscriptneedsbody'] = 'You must supply a script to be executed on all nodes.';
+       $lang['strscriptexecuted'] = 'Replication set DDL script executed.';
+       $lang['strscriptexecutedbad'] = 'Failed executing replication set DDL script.';
+       $lang['strtabletriggerstoretain'] = 'The following triggers will NOT be disabled by Slony:';
+
+       // Slony tables in replication sets
+       $lang['straddtable'] = 'Add table';
+       $lang['strtableneedsuniquekey'] = 'Table to be added requires a primary or unique key.';
+       $lang['strtableaddedtorepset'] = 'Table added to replication set.';
+       $lang['strtableaddedtorepsetbad'] = 'Failed adding table to replication set.';
+       $lang['strconfremovetablefromrepset'] = 'Are you sure you want to remove the table "%s" from replication set "%s"?';
+       $lang['strtableremovedfromrepset'] = 'Table removed from replication set.';
+       $lang['strtableremovedfromrepsetbad'] = 'Failed to remove table from replication set.';
+
+       // Slony sequences in replication sets
+       $lang['straddsequence'] = 'Add sequence';
+       $lang['strsequenceaddedtorepset'] = 'Sequence added to replication set.';
+       $lang['strsequenceaddedtorepsetbad'] = 'Failed adding sequence to replication set.';
+       $lang['strconfremovesequencefromrepset'] = 'Are you sure you want to remove the sequence "%s" from replication set "%s"?';
+       $lang['strsequenceremovedfromrepset'] = 'Sequence removed from replication set.';
+       $lang['strsequenceremovedfromrepsetbad'] = 'Failed to remove sequence from replication set.';
+
+       // Slony subscriptions
+       $lang['strsubscriptions'] = 'Subscriptions';
+       $lang['strnosubscriptions'] = 'No subscriptions found.';
+
        // Miscellaneous
        $lang['strtopbar'] = '%s running on %s:%s -- You are logged in as user "%s", %s';
        $lang['strtimefmt'] = 'jS M, Y g:iA';
index ce1cc1bfe892b146b559506d1c03b2425ebacf15..fc7ada8946519df79f4824c1646c8aafd9235b39 100644 (file)
@@ -4,7 +4,7 @@
         * English language file for phpPgAdmin.  Use this as a basis
         * for new translations.
         *
-        * $Id: english.php,v 1.129 2005/06/06 15:13:13 soranzo Exp $
+        * $Id: english.php,v 1.130 2005/06/16 14:40:13 chriskl Exp $
         */
 
        // Language and character set
@@ -95,6 +95,7 @@
        $lang['strreindex'] = 'Reindex';
        $lang['strrun'] = 'Run';
        $lang['stradd'] = 'Add';
+       $lang['strremove'] = 'Remove';
        $lang['strevent'] = 'Event';
        $lang['strwhere'] = 'Where';
        $lang['strinstead'] = 'Do Instead';
        $lang['strviewdroppedbad'] = 'View drop failed.';
        $lang['strviewupdated'] = 'View updated.';
        $lang['strviewupdatedbad'] = 'View update failed.';
-       $lang['strviewlink'] = 'Linking Keys';
-       $lang['strviewconditions'] = 'Additional Conditions';
+       $lang['strviewlink'] = 'Linking keys';
+       $lang['strviewconditions'] = 'Additional conditions';
        $lang['strcreateviewwiz'] = 'Create view with wizard';
 
        // Sequences
        $lang['strtablespacealtered'] = 'Tablespace altered.';
        $lang['strtablespacealteredbad'] = 'Tablespace alteration failed.';
 
+       // Slony clusters
+       $lang['strnoclusters'] = 'No clusters found.';
+       $lang['strconfdropcluster'] = 'Are you sure you want to drop cluster &quot;%s&quot;?';
+       $lang['strclusterdropped'] = 'Cluster dropped.';
+       $lang['strclusterdroppedbad'] = 'Cluster drop failed.';
+       $lang['strinitcluster'] = 'Initialize Cluster';
+       $lang['strclustercreated'] = 'Cluster initialized.';
+       $lang['strclustercreatedbad'] = 'Cluster initialization failed.';
+       $lang['strclusterneedsname'] = 'You must give a name for the cluster.';
+       $lang['strclusterneedsnodeid'] = 'You must give an ID for the local node.';
+       
+       // Slony nodes
+       $lang['strnodes'] = 'Nodes';
+       $lang['strnonodes'] = 'No nodes found.';
+       $lang['strcreatenode'] = 'Create node';
+       $lang['strid'] = 'ID';
+       $lang['stractive'] = 'Active';
+       $lang['strnodecreated'] = 'Node created.';
+       $lang['strnodecreatedbad'] = 'Node creation failed.';
+       $lang['strconfdropnode'] = 'Are you sure you want to drop node &quot;%s&quot;?';
+       $lang['strnodedropped'] = 'Node dropped.';
+       $lang['strnodedroppedbad'] = 'Node drop failed';
+       $lang['strfailover'] = 'Failover';
+       $lang['strnodefailedover'] = 'Node failed over.';
+       $lang['strnodefailedoverbad'] = 'Node fail over fail.';
+       
+       // Slony paths  
+       $lang['strpaths'] = 'Paths';
+       $lang['strnopaths'] = 'No paths found.';
+       $lang['strcreatepath'] = 'Create path';
+       $lang['strnodename'] = 'Node name';
+       $lang['strnodeid'] = 'Node ID';
+       $lang['strconninfo'] = 'Connection string';
+       $lang['strconnretry'] = 'Seconds before retry to connect';
+       $lang['strpathneedsconninfo'] = 'You must give a connection string for the path.';
+       $lang['strpathneedsconnretry'] = 'You must give the number of seconds to wait before retry to connect.';
+       $lang['strpathcreated'] = 'Path created.';
+       $lang['strpathcreatedbad'] = 'Path creation failed.';
+       $lang['strconfdroppath'] = 'Are you sure you want to drop path &quot;%s&quot;?';
+       $lang['strpathdropped'] = 'Path dropped.';
+       $lang['strpathdroppedbad'] = 'Path drop failed.';
+
+       // Slony listens
+       $lang['strlistens'] = 'Listens';
+       $lang['strnolistens'] = 'No listens found.';
+       $lang['strcreatelisten'] = 'Create listen';
+       $lang['strlistencreated'] = 'Listen created.';
+       $lang['strlistencreatedbad'] = 'Listen creation failed.';
+       $lang['strconfdroplisten'] = 'Are you sure you want to drop listen &quot;%s&quot;?';
+       $lang['strlistendropped'] = 'Listen dropped.';
+       $lang['strlistendroppedbad'] = 'Listen drop failed.';
+
+       // Slony replication sets
+       $lang['strrepsets'] = 'Replication sets';
+       $lang['strnorepsets'] = 'No replication sets found.';
+       $lang['strcreaterepset'] = 'Create replication set';
+       $lang['strrepsetcreated'] = 'Replication set created.';
+       $lang['strrepsetcreatedbad'] = 'Replication set creation failed.';
+       $lang['strconfdroprepset'] = 'Are you sure you want to drop replication set &quot;%s&quot;?';
+       $lang['strrepsetdropped'] = 'Replication set dropped.';
+       $lang['strrepsetdroppedbad'] = 'Replication set drop failed.';
+       $lang['strmerge'] = 'Merge';
+       $lang['strmergeinto'] = 'Merge Into';
+       $lang['strrepsetmerged'] = 'Replication sets merged.';
+       $lang['strrepsetmergedbad'] = 'Replication sets merge failed.';
+       $lang['strmove'] = 'Move';
+       $lang['strneworigin'] = 'New Origin';
+       $lang['strrepsetmoved'] = 'Replication set moved.';
+       $lang['strrepsetmovedbad'] = 'Replication set move failed.';
+       $lang['strnewrepset'] = 'New replication set';
+       $lang['strlock'] = 'Lock';
+       $lang['strlocked'] = 'Locked';
+       $lang['strunlock'] = 'Unlock';
+       $lang['strconflockrepset'] = 'Are you sure you want to lock replication set &quot;%s&quot;?';
+       $lang['strrepsetlocked'] = 'Replication set locked.';
+       $lang['strrepsetlockedbad'] = 'Replication set lock failed.';
+       $lang['strconfunlockrepset'] = 'Are you sure you want to unlock replication set &quot;%s&quot;?';
+       $lang['strrepsetunlocked'] = 'Replication set unlocked.';
+       $lang['strrepsetunlockedbad'] = 'Replication set unlock failed.';
+       $lang['strexecute'] = 'Execute';
+       $lang['stronlyonnode'] = 'Only on node';
+       $lang['strddlscript'] = 'DDL Script';
+       $lang['strscriptneedsbody'] = 'You must supply a script to be executed on all nodes.';
+       $lang['strscriptexecuted'] = 'Replication set DDL script executed.';
+       $lang['strscriptexecutedbad'] = 'Failed executing replication set DDL script.';
+       $lang['strtabletriggerstoretain'] = 'The following triggers will NOT be disabled by Slony:';
+
+       // Slony tables in replication sets
+       $lang['straddtable'] = 'Add table';
+       $lang['strtableneedsuniquekey'] = 'Table to be added requires a primary or unique key.';
+       $lang['strtableaddedtorepset'] = 'Table added to replication set.';
+       $lang['strtableaddedtorepsetbad'] = 'Failed adding table to replication set.';
+       $lang['strconfremovetablefromrepset'] = 'Are you sure you want to remove the table &quot;%s&quot; from replication set &quot;%s&quot;?';
+       $lang['strtableremovedfromrepset'] = 'Table removed from replication set.';
+       $lang['strtableremovedfromrepsetbad'] = 'Failed to remove table from replication set.';
+
+       // Slony sequences in replication sets
+       $lang['straddsequence'] = 'Add sequence';
+       $lang['strsequenceaddedtorepset'] = 'Sequence added to replication set.';
+       $lang['strsequenceaddedtorepsetbad'] = 'Failed adding sequence to replication set.';
+       $lang['strconfremovesequencefromrepset'] = 'Are you sure you want to remove the sequence &quot;%s&quot; from replication set &quot;%s&quot;?';
+       $lang['strsequenceremovedfromrepset'] = 'Sequence removed from replication set.';
+       $lang['strsequenceremovedfromrepsetbad'] = 'Failed to remove sequence from replication set.';
+
+       // Slony subscriptions
+       $lang['strsubscriptions'] = 'Subscriptions';
+       $lang['strnosubscriptions'] = 'No subscriptions found.';
+
        // Miscellaneous
        $lang['strtopbar'] = '%s running on %s:%s -- You are logged in as user &quot;%s&quot;, %s';
        $lang['strtimefmt'] = 'jS M, Y g:iA';
index 0070c7287c1005958efa24f53a8f9521e44ae313..761749bcfee19df201f2fff4cd646d757bcd56e1 100644 (file)
@@ -3,7 +3,7 @@
        /**
         * Function library read in upon startup
         *
-        * $Id: lib.inc.php,v 1.95 2005/06/02 11:31:03 chriskl Exp $
+        * $Id: lib.inc.php,v 1.96 2005/06/16 14:40:14 chriskl Exp $
         */
        include_once('decorator.inc.php');
        include_once('./lang/translations.php');
@@ -15,7 +15,7 @@
        $appName = 'phpPgAdmin';
 
        // Application version
-       $appVersion = '3.6-dev';
+       $appVersion = '4.0-dev';
 
        // PostgreSQL and PHP minimum version
        $postgresqlMinVer = '7.0';
                        else
                                $lang['appcharset'] = $dbEncoding;
                }
+               
+               // XXX: THIS IS A TEMPORARY HACK TO GET SLONY OBJECT LOADED
+               include('./classes/plugins/Slony.php');
+               $slony = new Slony();
+               if ($slony->isEnabled()) {
+                       $plugins = array(&$slony);
+               }
+               else $plugins = array();
+               
        }
 
 ?>
diff --git a/plugin_slony.php b/plugin_slony.php
new file mode 100755 (executable)
index 0000000..fc17917
--- /dev/null
@@ -0,0 +1,2235 @@
+<?php
+
+       /**
+        * Slony database tab plugin
+        *
+        * $Id: plugin_slony.php,v 1.2 2005/06/16 14:40:10 chriskl Exp $
+        */
+
+       // Include application functions
+       include_once('./libraries/lib.inc.php');
+
+       $action = (isset($_REQUEST['action'])) ? $_REQUEST['action'] : '';
+       $PHP_SELF = $_SERVER['PHP_SELF'];
+
+       /**
+        * Generate the somewhat complex Slony tree
+        * @param string $subject The tree node to return
+        */
+       function doTree($subject) {
+               global $misc, $data, $lang, $PHP_SELF, $slony;
+
+               $reqvars = $misc->getRequestVars('database');
+
+               // Determine what actual tree we are building
+               switch ($subject) {                     
+                       case 'clusters':
+                               // Clusters
+                               
+                               // Enabled check here is just a hack.
+                               if ($slony->isEnabled()) {
+                                               $tabs = array('cluster' => array (
+                                                                                       'title' => $slony->slony_cluster,
+                                                                                       'url'   => 'plugin_slony.php',
+                                                                                       'urlvars' => array('subject' => 'clusters_top')
+                                                                               ));
+                               }
+                               else $tabs = array();
+                                       
+                               $items =& $misc->adjustTabsForTree($tabs);
+                               
+                               $attrs = array(
+                                       'text'   => noEscape(field('title')),
+                                       'icon'   => field('icon', 'folder'),
+                                       'action' => url(field('url'),
+                                                                       $reqvars,
+                                                                       array(
+                                                                               'action'  => 'cluster_properties'
+                                                                       )
+                                                               ),
+                                       'branch' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars'),
+                                                                       array('action' => 'clusters_top')
+                                                               ),
+                               );
+                               
+                               $misc->printTreeXML($items, $attrs);
+
+                               break;                  
+                       case 'clusters_top':
+                               // Top level Nodes and Replication sets folders
+                               $tabs = array('nodes' => array (
+                                                                               'title' => $lang['strnodes'],
+                                                                               'url'   => 'plugin_slony.php',
+                                                                               'urlvars' => array('subject' => 'nodes')
+                                                                       ));
+                               
+                               $items =& $misc->adjustTabsForTree($tabs);
+                               
+                               $attrs = array(
+                                       'text'   => noEscape(field('title')),
+                                       'icon'   => field('icon', 'folder'),
+                                       'action' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars', array()),
+                                                                       array('action' => 'nodes_properties')
+                                                               ),
+                                       'branch' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars'),
+                                                                       array('action' => 'nodes')
+                                                               ),
+                                       'nofoot' => true
+                               );
+                               
+                               $misc->printTreeXML($items, $attrs);
+
+                               $tabs = array('sets' => array (
+                                                                               'title' =>      $lang['strrepsets'],
+                                                                               'url'   => 'plugin_slony.php',
+                                                                               'urlvars' => array('subject' => 'sets')
+                                                                       ));
+                               
+                               $items =& $misc->adjustTabsForTree($tabs);
+                               
+                               $attrs = array(
+                                       'text'   => noEscape(field('title')),
+                                       'icon'   => field('icon', 'folder'),
+                                       'action' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars', array()),
+                                                                       array('action' => 'sets_properties')
+                                                               ),
+                                       'branch' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars'),
+                                                                       array('action' => 'sets')
+                                                               ),
+                                       'nohead' => true
+                               );
+                               
+                               $misc->printTreeXML($items, $attrs);
+                               
+                               break;
+                       case 'nodes':                   
+                               $nodes = &$slony->getNodes();
+                               
+                               $attrs = array(
+                                       'text'   => field('no_comment'),
+                                       'icon'   => 'folder',
+                                       'action' => url('plugin_slony.php',
+                                                                       $reqvars,
+                                                                       array(
+                                                                               'action'  => 'node_properties',
+                                                                               'no_id' => field('no_id')
+                                                                       )
+                                                               ),
+                                       'branch' => url('plugin_slony.php',
+                                                                       $reqvars,
+                                                                       array(
+                                                                               'action'  => 'nodes_top',
+                                                                               'no_id' => field('no_id')
+                                                                       )
+                                                               )
+                               );
+
+                               $misc->printTreeXML($nodes, $attrs);
+                               
+                               break;
+                       case 'nodes_top':
+                               // Nodes paths and listens entries
+                               
+                               $tabs = array('paths' => array (
+                                                                               'title' => $lang['strpaths'],
+                                                                               'url'   => 'plugin_slony.php',
+                                                                               'urlvars' => array('subject' => 'paths')
+                                                                       ));
+                               
+                               $items =& $misc->adjustTabsForTree($tabs);
+                               
+                               $attrs = array(
+                                       'text'   => noEscape(field('title')),
+                                       'icon'   => field('icon', 'folder'),
+                                       'action' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars', array()),
+                                                                       array('action' => 'paths_properties', 'no_id' => $_REQUEST['no_id'])
+                                                               ),
+                                       'branch' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars'),
+                                                                       array('action' => 'paths', 'no_id' => $_REQUEST['no_id'])
+                                                               ),
+                                       'nofoot' => true
+                               );
+                               
+                               $misc->printTreeXML($items, $attrs);
+
+                               $tabs = array('listens' => array (
+                                                                               'title' => $lang['strlistens'],
+                                                                               'url'   => 'plugin_slony.php',
+                                                                               'urlvars' => array('subject' => 'listens')
+                                                                       ));
+                               
+                               $items =& $misc->adjustTabsForTree($tabs);
+                               
+                               $attrs = array(
+                                       'text'   => noEscape(field('title')),
+                                       'icon'   => field('icon', 'folder'),
+                                       'action' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars', array()),
+                                                                       array('action' => 'listens_properties', 'no_id' => $_REQUEST['no_id'])
+                                                               ),
+                                       'branch' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars'),
+                                                                       array('action' => 'listens', 'no_id' => $_REQUEST['no_id'])
+                                                               ),
+                                       'nohead' => true
+                               );
+                               
+                               $misc->printTreeXML($items, $attrs);
+                               
+                               break;                  
+                       case 'paths':
+                               $tables = &$slony->getPaths($_REQUEST['no_id']);
+                               
+                               $attrs = array(
+                                       'text'   => field('no_comment'),
+                                       'icon'   => field('icon', 'folder'),
+                                       'action' => url('plugin_slony.php',
+                                                                       $reqvars,
+                                                                       array('no_id' => field('pa_client'), 'path_id' => field('no_id'), 'action' => 'path_properties')
+                                                               )
+                               );
+                               
+                               $misc->printTreeXML($tables, $attrs);
+                       
+                               break;
+                       case 'listens':
+                               $tables = &$slony->getListens($_REQUEST['no_id']);
+                               
+                               $attrs = array(
+                                       'text'   => field('no_comment'),
+                                       'icon'   => field('icon', 'folder'),
+                                       'action' => url('plugin_slony.php',
+                                                                       $reqvars,
+                                                                       array('no_id' => field('li_receiver'), 'listen_id' => field('no_id'), 'action' => 'listen_properties')
+                                                               )
+                               );
+                               
+                               $misc->printTreeXML($tables, $attrs);
+                       
+                               break;
+                       case 'sets':
+                               $sets = &$slony->getReplicationSets();
+                       
+                               $attrs = array(
+                                       'text'   => field('set_comment'),
+                                       'icon'   => 'folder',
+                                       'action' => url('plugin_slony.php',
+                                                                       $reqvars,
+                                                                       array(
+                                                                               'action'  => 'set_properties',
+                                                                               'set_id' => field('set_id')
+                                                                       )
+                                                               ),
+                                       'branch' => url('plugin_slony.php',
+                                                                       $reqvars,
+                                                                       array(
+                                                                               'action'  => 'sets_top',
+                                                                               'set_id' => field('set_id')
+                                                                       )
+                                                               )
+                               );
+                               
+                               $misc->printTreeXML($sets, $attrs);
+                               break;
+                       case 'sets_top':
+                               // Top level Nodes and Replication sets folders
+                               
+                               $tabs = array('sequences' => array (
+                                                                               'title' => $lang['strsequences'],
+                                                                               'url'   => 'plugin_slony.php',
+                                                                               'urlvars' => array('subject' => 'sequences')
+                                                                       ));
+                               
+                               $items =& $misc->adjustTabsForTree($tabs);
+                               
+                               $attrs = array(
+                                       'text'   => noEscape(field('title')),
+                                       'icon'   => field('icon', 'sequences'),
+                                       'action' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars', array()),
+                                                                       array('action' => 'sequences_properties', 'set_id' => $_REQUEST['set_id'])
+                                                               ),
+                                       'branch' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars'),
+                                                                       array('action' => 'sequences', 'set_id' => $_REQUEST['set_id'])
+                                                               ),
+                                       'nofoot' => true
+                               );
+                               
+                               $misc->printTreeXML($items, $attrs);
+
+                               $tabs = array('tables' => array (
+                                                                               'title' => $lang['strtables'],
+                                                                               'url'   => 'plugin_slony.php',
+                                                                               'urlvars' => array('subject' => 'tables')
+                                                                       ));
+                               
+                               $items =& $misc->adjustTabsForTree($tabs);
+                               
+                               $attrs = array(
+                                       'text'   => noEscape(field('title')),
+                                       'icon'   => field('icon', 'tables'),
+                                       'action' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars', array()),
+                                                                       array('action' => 'tables_properties', 'set_id' => $_REQUEST['set_id'])
+                                                               ),
+                                       'branch' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars'),
+                                                                       array('action' => 'tables', 'set_id' => $_REQUEST['set_id'])
+                                                               ),
+                                       'nohead' => true,
+                                       'nofoot' => true
+                               );
+                               
+                               $misc->printTreeXML($items, $attrs);
+
+                               $tabs = array('subscriptions' => array (
+                                                                               'title' => $lang['strsubscriptions'],
+                                                                               'url'   => 'plugin_slony.php',
+                                                                               'urlvars' => array('subject' => 'subscriptions')
+                                                                       ));
+                               
+                               $items =& $misc->adjustTabsForTree($tabs);
+                               
+                               $attrs = array(
+                                       'text'   => noEscape(field('title')),
+                                       'icon'   => field('icon', 'folder'),
+                                       'action' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars', array()),
+                                                                       array('action' => 'subscriptions_properties', 'set_id' => $_REQUEST['set_id'])
+                                                               ),
+                                       'branch' => url(field('url'),
+                                                                       $reqvars,
+                                                                       field('urlvars'),
+                                                                       array('action' => 'subscriptions', 'set_id' => $_REQUEST['set_id'])
+                                                               ),
+                                       'nohead' => true
+                               );
+                               
+                               $misc->printTreeXML($items, $attrs);
+                               
+                               break;
+                       case 'sequences':
+                               $tables = &$slony->getSequences($_REQUEST['set_id']);
+                               
+                               $reqvars = $misc->getRequestVars('sequence');
+
+                               $attrs = array(
+                                       'text'   => field('qualname'),
+                                       'icon'   => 'sequences',
+                                       'toolTip'=> field('seqcomment'),
+                                       'action' => url('sequences.php',
+                                                                       $reqvars,
+                                                                       array (
+                                                                               'action' => 'properties',
+                                                                               'sequence' => field('seqname'),
+                                                                               'schema' => field('nspname')
+                                                                       )
+                                                               )
+                               );
+                               
+                               $misc->printTreeXML($tables, $attrs);
+                       
+                               break;
+                       case 'tables':
+                               $tables = &$slony->getTables($_REQUEST['set_id']);
+                               
+                               $reqvars = $misc->getRequestVars('table');
+                               
+                               $attrs = array(
+                                       'text'   => field('qualname'),
+                                       'icon'   => 'tables',
+                                       'toolTip'=> field('relcomment'),
+                                       'action' => url('redirect.php',
+                                                                       $reqvars,
+                                                                       array('table' => field('relname'), 'schema' => field('nspname'))
+                                                               )
+                               );
+                               
+                               $misc->printTreeXML($tables, $attrs);
+                       
+                               break;
+                       case 'subscriptions':
+                               $tables = &$slony->getSubscribedNodes($_REQUEST['set_id']);
+                               
+                               $attrs = array(
+                                       'text'   => field('no_comment'),
+                                       'icon'   => field('icon', 'folder'),
+                                       'action' => url('plugin_slony.php',
+                                                                       $reqvars,
+                                                                       array('set_id' => field('sub_set'), 'no_id' => field('no_id'), 'action' => 'subscription_properties')
+                                                               )
+                               );
+                               
+                               $misc->printTreeXML($tables, $attrs);
+                       
+                               break;
+               }
+                       
+               exit;
+       }
+
+       /**
+        * Display the slony clusters (we only support one)
+        */      
+       function doClusters($msg = '') {
+               global $PHP_SELF, $slony, $misc;
+               global $lang;
+
+               $misc->printTrail('database');
+               $misc->printMsg($msg);
+
+               $clusters = $slony->getClusters();
+
+               $columns = array(
+                       'no_name' => array(
+                               'title' => $lang['strcluster'],
+                               'field' => 'cluster'
+                       ),
+                       'actions' => array(
+                               'title' => $lang['stractions'],
+                       ),
+                       'no_comment' => array(
+                               'title' => $lang['strcomment'],
+                               'field' => 'comment'
+                       )
+               );
+               
+               $actions = array (
+                       'properties' => array(
+                               'title' => $lang['strproperties'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=cluster_properties&amp;",
+                               'vars'  => array()
+                       ),
+                       'drop' => array(
+                               'title' => $lang['strdrop'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=confirm_drop_cluster&amp;",
+                               'vars'  => array()
+                       )
+               );
+               
+               $misc->printTable($clusters, $columns, $actions, $lang['strnoclusters']);
+
+               // XXX: FIX THIS ONCE WE SUPPORT MULTIPLE CLUSTERS
+               if ($clusters->recordCount() == 0) {            
+                       echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=create_cluster&amp;{$misc->href}\">{$lang['strinitcluster']}</a></p>\n";
+               }
+       }
+
+       // CLUSTERS
+
+       /**
+        * Display the properties of a slony cluster
+        */      
+       function doCluster($msg = '') {
+               global $data, $slony, $misc, $PHP_SELF;
+               global $lang;
+               
+               $misc->printTrail('slony_cluster');
+               $misc->printTitle($lang['strproperties']);
+               $misc->printMsg($msg);
+               
+               // Fetch the cluster information
+               $cluster = &$slony->getCluster();
+               
+               if (is_object($cluster) && $cluster->recordCount() > 0) {                       
+                       echo "<table>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strname']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($slony->slony_cluster), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Local Node ID</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($cluster->f['no_id']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Local Node</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($cluster->f['no_comment']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Version</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($cluster->f['version']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strowner']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($slony->slony_owner), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strcomment']}</th>\n";
+                       echo "<td class=\"data1\"></td></tr>\n";
+                       echo "</table>\n";
+               }
+               else echo "<p>{$lang['strnodata']}</p>\n";
+       }
+
+       /**
+        * Displays a screen where they can enter a new cluster
+        */
+       function doCreateCluster($confirm, $msg = '') {
+               global $data, $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               if ($confirm) {
+                       if (!isset($_POST['cluster'])) $_POST['cluster'] = '';
+                       if (!isset($_POST['no_id'])) $_POST['no_id'] = '1';
+                       if (!isset($_POST['no_comment'])) $_POST['no_comment'] = '';
+       
+                       $misc->printTrail('slony_clusters');
+                       $misc->printTitle($lang['strinitcluster']);
+                       $misc->printMsg($msg);
+       
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo $misc->form;
+                       echo "<table width=\"100%\">\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strcluster']}</th>\n";
+                       echo "\t\t<td class=\"data1\"><input name=\"cluster\" size=\"32\" maxlength=\"{$data->_maxNameLen}\" value=\"",
+                               htmlspecialchars($_POST['cluster']), "\" /></td>\n\t</tr>\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strid']}</th>\n";
+                       echo "\t\t<td class=\"data1\"><input name=\"no_id\" size=\"5\" value=\"",
+                               htmlspecialchars($_POST['no_id']), "\" /></td>\n\t</tr>\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strcomment']}</th>\n";
+                       echo "\t\t<td class=\"data1\"><textarea name=\"no_comment\" rows=\"3\" cols=\"32\" wrap=\"virtual\">", 
+                               htmlspecialchars($_POST['no_comment']), "</textarea></td>\n\t</tr>\n";
+                       echo "</table>\n";
+                       echo "<p>\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"save_create_cluster\" />\n";
+                       echo "<input type=\"submit\" value=\"{$lang['strinitcluster']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</p>\n";
+                       echo "</form>\n";
+               }
+               else {
+                       if (trim($_POST['cluster']) == '') {
+                               doCreateCluster(true, $lang['strclusterneedsname']);
+                               return;
+                       }
+                       elseif (trim($_POST['no_id']) == '') {
+                               doCreateCluster(true, $lang['strclusterneedsnodeid']);
+                               return;
+                       }
+                       
+                       $status = $slony->initCluster($_POST['cluster'], $_POST['no_id'], $_POST['no_comment']);
+                       if ($status == 0)
+                               doClusters($lang['strclustercreated']);
+                       else
+                               doCreateCluster(true, $lang['strclustercreatedbad'] . ':' . $status);
+               }
+       }
+
+       /**
+        * Show confirmation of drop and perform actual drop of a cluster
+        */
+       function doDropCluster($confirm) {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+
+               if ($confirm) {
+                       $misc->printTrail('slony_cluster');
+                       $misc->printTitle($lang['strdrop']);
+
+                       echo "<p>", sprintf($lang['strconfdropcluster'], $misc->printVal($slony->slony_cluster)), "</p>\n";
+
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"drop_cluster\" />\n";
+                       echo $misc->form;
+                       echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strdrop']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->dropCluster();
+                       if ($status == 0)
+                               doClusters($lang['strclusterdropped']);
+                       else
+                               doClusters($lang['strclusterdroppedbad']);
+               }
+       }
+
+       // NODES
+       
+       /**
+        * List all the nodes
+        */
+       function doNodes($msg = '') {
+               global $PHP_SELF, $slony, $misc;
+               global $lang;
+
+               $misc->printTrail('slony_nodes');
+               $misc->printMsg($msg);
+
+               $nodes = $slony->getNodes();
+
+               $columns = array(
+                       'no_name' => array(
+                               'title' => $lang['strname'],
+                               'field' => 'no_comment'
+                       ),
+                       'actions' => array(
+                               'title' => $lang['stractions'],
+                       ),
+                       'no_comment' => array(
+                               'title' => $lang['strcomment'],
+                               'field' => 'no_comment'
+                       )
+               );
+               
+               $actions = array (
+                       'properties' => array(
+                               'title' => $lang['strproperties'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=node_properties&amp;",
+                               'vars'  => array('no_id' => 'no_id')
+                       ),
+                       'drop' => array(
+                               'title' => $lang['strdrop'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=confirm_drop_node&amp;",
+                               'vars'  => array('no_id' => 'no_id')
+                       )
+               );
+               
+               $misc->printTable($nodes, $columns, $actions, $lang['strnonodes']);
+               
+               echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=create_node&amp;{$misc->href}\">{$lang['strcreatenode']}</a></p>\n";
+       }
+       
+       /**
+        * Display the properties of a node
+        */      
+       function doNode($msg = '') {
+               global $data, $slony, $misc, $PHP_SELF;
+               global $lang;
+               
+               $misc->printTrail('slony_node');
+               $misc->printTitle($lang['strproperties']);
+               $misc->printMsg($msg);
+               
+               // Fetch the node information
+               $node = &$slony->getNode($_REQUEST['no_id']);           
+               
+               if (is_object($node) && $node->recordCount() > 0) {                     
+                       // Show comment if any
+                       if ($node->f['no_comment'] !== null)
+                               echo "<p class=\"comment\">", $misc->printVal($node->f['no_comment']), "</p>\n";
+
+                       echo "<table>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strname']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($node->f['no_comment']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strid']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($node->f['no_id']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['stractive']}</th>\n";
+                       echo "<td class=\"data1\">", ($data->phpBool($node->f['no_active'])) ? $lang['stryes'] : $lang['strno'], "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strcomment']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($node->f['no_comment']), "</td></tr>\n";
+                       echo "</table>\n";
+               }
+               else echo "<p>{$lang['strnodata']}</p>\n";
+
+               echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=confirm_drop_node&amp;{$misc->href}&amp;no_id={$_REQUEST['no_id']}\">{$lang['strdrop']}</a></p>\n";
+       }
+
+       /**
+        * Displays a screen where they can enter a new node
+        */
+       function doCreateNode($confirm, $msg = '') {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               if ($confirm) {
+                       if (!isset($_POST['nodeid'])) $_POST['nodeid'] = '';
+                       if (!isset($_POST['nodecomment'])) $_POST['nodecomment'] = '';
+       
+                       $misc->printTrail('slony_nodes');
+                       $misc->printTitle($lang['strcreatenode']);
+                       $misc->printMsg($msg);
+       
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo $misc->form;
+                       echo "<table width=\"100%\">\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strid']}</th>\n";
+                       echo "\t\t<td class=\"data1\"><input name=\"nodeid\" size=\"5\" value=\"",
+                               htmlspecialchars($_POST['nodeid']), "\" /></td>\n\t</tr>\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strcomment']}</th>\n";
+                       echo "\t\t<td class=\"data1\"><textarea name=\"nodecomment\" rows=\"3\" cols=\"32\" wrap=\"virtual\">", 
+                               htmlspecialchars($_POST['nodecomment']), "</textarea></td>\n\t</tr>\n";
+                               
+                       echo "\t</tr>\n";
+                       echo "</table>\n";
+                       echo "<p>\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"save_create_node\" />\n";
+                       echo "<input type=\"submit\" value=\"{$lang['strcreate']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</p>\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->createNode($_POST['nodeid'], $_POST['nodecomment']);
+                       if ($status == 0)
+                               doNodes($lang['strnodecreated']);
+                       else
+                               doCreateNode(true, $lang['strnodecreatedbad']);
+               }
+       }
+
+       /**
+        * Show confirmation of drop and perform actual drop of a node
+        */
+       function doDropNode($confirm) {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+
+               if ($confirm) {
+                       $misc->printTrail('slony_cluster');
+                       $misc->printTitle($lang['strdrop']);
+
+                       echo "<p>", sprintf($lang['strconfdropnode'], $misc->printVal($_REQUEST['no_id'])), "</p>\n";
+
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"drop_node\" />\n";
+                       echo "<input type=\"hidden\" name=\"no_id\" value=\"", htmlspecialchars($_REQUEST['no_id']), "\" />\n";
+                       echo $misc->form;
+                       echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strdrop']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->dropNode($_REQUEST['no_id']);
+                       if ($status == 0)
+                               doNodes($lang['strnodedropped']);
+                       else
+                               doNodes($lang['strnodedroppedbad']);
+               }
+       }
+                       
+       // PATHS
+
+       /**
+        * List all the paths
+        */
+       function doPaths($msg = '') {
+               global $PHP_SELF, $slony, $misc;
+               global $lang;
+
+               $misc->printTrail('database');
+               $misc->printMsg($msg);
+
+               $paths = $slony->getPaths($_REQUEST['no_id']);
+
+               $columns = array(
+                       'no_name' => array(
+                               'title' => $lang['strname'],
+                               'field' => 'no_comment'
+                       ),
+                       'actions' => array(
+                               'title' => $lang['stractions'],
+                       ),
+                       'no_comment' => array(
+                               'title' => $lang['strcomment'],
+                               'field' => 'no_comment'
+                       )
+               );
+               
+               $actions = array (
+                       'properties' => array(
+                               'title' => $lang['strproperties'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=path_properties&amp;",
+                               'vars'  => array('no_id' => 'pa_client', 'path_id' => 'no_id')
+                       ),
+                       'drop' => array(
+                               'title' => $lang['strdrop'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=confirm_drop_path&amp;",
+                               'vars'  => array('no_id' => 'pa_client', 'path_id' => 'no_id')
+                       )
+               );
+               
+               $misc->printTable($paths, $columns, $actions, $lang['strnopaths']);
+       
+               echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=create_path&amp;{$misc->href}&amp;no_id={$_REQUEST['no_id']}\">{$lang['strcreatepath']}</a></p>\n";
+       }
+       
+       /**
+        * Display the properties of a path
+        */      
+       function doPath($msg = '') {
+               global $data, $slony, $misc, $PHP_SELF;
+               global $lang;
+               
+               $misc->printTrail('slony_path');
+               $misc->printTitle($lang['strproperties']);
+               $misc->printMsg($msg);
+               
+               // Fetch the path information
+               $path = &$slony->getPath($_REQUEST['no_id'], $_REQUEST['path_id']);             
+               
+               if (is_object($path) && $path->recordCount() > 0) {                     
+                       // Show comment if any
+                       if ($path->f['no_comment'] !== null)
+                               echo "<p class=\"comment\">", $misc->printVal($path->f['no_comment']), "</p>\n";
+
+                       echo "<table>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strnodename']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($path->f['no_comment']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strnodeid']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($path->f['no_id']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strconninfo']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($path->f['pa_conninfo']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strconnretry']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($path->f['pa_connretry']), "</td></tr>\n";
+                       echo "</table>\n";
+               }
+               else echo "<p>{$lang['strnodata']}</p>\n";
+
+               echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=confirm_drop_path&amp;{$misc->href}&amp;no_id={$_REQUEST['no_id']}&amp;path_id={$_REQUEST['path_id']}\">{$lang['strdrop']}</a></p>\n";
+       }
+
+       /**
+        * Displays a screen where they can enter a new path
+        */
+       function doCreatePath($confirm, $msg = '') {
+               global $data, $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               if ($confirm) {
+                       if (!isset($_POST['pathserver'])) $_POST['pathserver'] = '';
+                       if (!isset($_POST['pathconn'])) $_POST['pathconn'] = '';
+                       if (!isset($_POST['pathretry'])) $_POST['pathretry'] = '10';
+       
+                       // Fetch all servers
+                       $nodes = &$slony->getNodes();
+
+                       $misc->printTrail('slony_paths');
+                       $misc->printTitle($lang['strcreatepath']);
+                       $misc->printMsg($msg);
+       
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo $misc->form;
+                       echo "<table width=\"100%\">\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strnodename']}</th>\n";
+                       echo "\t\t<td class=\"data1\">\n\t\t\t<select name=\"pathserver\">\n";
+                       while (!$nodes->EOF) {
+                               echo "\t\t\t\t<option value=\"{$nodes->f['no_id']}\"",
+                                       ($nodes->f['no_id'] == $_POST['pathserver']) ? ' selected="selected"' : '', ">", htmlspecialchars($nodes->f['no_comment']), "</option>\n";
+                               $nodes->moveNext();
+                       }
+                       echo "\t\t\t</select>\n\t\t</td>\n\t\n";                
+                       echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strconninfo']}</th>\n";
+                       echo "\t\t<td class=\"data1\"><input name=\"pathconn\" size=\"32\" maxlength=\"{$data->_maxNameLen}\" value=\"",
+                               htmlspecialchars($_POST['pathconn']), "\" /></td>\n\t</tr>\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strconnretry']}</th>\n";
+                       echo "\t\t<td class=\"data1\"><input name=\"pathretry\" size=\"32\" maxlength=\"{$data->_maxNameLen}\" value=\"",
+                               htmlspecialchars($_POST['pathretry']), "\" /></td>\n\t</tr>\n";
+                               
+                       echo "\t</tr>\n";
+                       echo "</table>\n";
+                       echo "<p>\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"save_create_path\" />\n";
+                       echo "<input type=\"hidden\" name=\"no_id\" value=\"", htmlspecialchars($_REQUEST['no_id']), "\" />\n";
+                       echo "<input type=\"submit\" value=\"{$lang['strcreate']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</p>\n";
+                       echo "</form>\n";
+               }
+               else {
+                       if (trim($_POST['pathconn']) == '') {
+                               doCreatePath(true, $lang['strpathneedsconninfo']);
+                               return;
+                       }
+                       elseif (trim($_POST['pathretry']) == '') {
+                               doCreatePath(true, $lang['strpathneedsconnretry']);
+                               return;
+                       }
+                               
+                       $status = $slony->createPath($_POST['no_id'], $_POST['pathserver'], $_POST['pathconn'], $_POST['pathretry']);
+                       if ($status == 0)
+                               doPaths($lang['strpathcreated']);
+                       else
+                               doCreatePath(true, $lang['strpathcreatedbad']);
+               }
+       }
+
+       /**
+        * Show confirmation of drop and perform actual drop of a path
+        */
+       function doDropPath($confirm) {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+
+               if ($confirm) {
+                       $misc->printTrail('slony_cluster');
+                       $misc->printTitle($lang['strdrop']);
+
+                       echo "<p>", sprintf($lang['strconfdroppath'], $misc->printVal($_REQUEST['path_id'])), "</p>\n";
+
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"drop_path\" />\n";
+                       echo "<input type=\"hidden\" name=\"no_id\" value=\"", htmlspecialchars($_REQUEST['no_id']), "\" />\n";
+                       echo "<input type=\"hidden\" name=\"path_id\" value=\"", htmlspecialchars($_REQUEST['path_id']), "\" />\n";
+                       echo $misc->form;
+                       echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strdrop']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->dropPath($_REQUEST['no_id'], $_REQUEST['path_id']);
+                       if ($status == 0)
+                               doPaths($lang['strpathdropped']);
+                       else
+                               doPaths($lang['strpathdroppedbad']);
+               }
+       }
+       
+       // LISTENS
+       
+       /**
+        * List all the listens
+        */
+       function doListens($msg = '') {
+               global $PHP_SELF, $slony, $misc;
+               global $lang;
+
+               $misc->printTrail('database');
+               $misc->printMsg($msg);
+
+               $listens = $slony->getListens($_REQUEST['no_id']);
+
+               $columns = array(
+                       'no_name' => array(
+                               'title' => $lang['strname'],
+                               'field' => 'no_comment'
+                       ),
+                       'actions' => array(
+                               'title' => $lang['stractions'],
+                       ),
+                       'no_comment' => array(
+                               'title' => $lang['strcomment'],
+                               'field' => 'no_comment'
+                       )
+               );
+               
+               $actions = array (
+                       'properties' => array(
+                               'title' => $lang['strproperties'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=listen_properties&amp;",
+                               'vars'  => array('no_id' => 'li_receiver', 'listen_id' => 'no_id', 'origin_id' => 'li_origin')
+                       ),
+                       'drop' => array(
+                               'title' => $lang['strdrop'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=confirm_drop_listen&amp;",
+                               'vars'  => array('no_id' => 'li_receiver', 'listen_id' => 'no_id', 'origin_id' => 'li_origin')
+                       )
+
+               );
+               
+               $misc->printTable($listens, $columns, $actions, $lang['strnolistens']);
+
+               echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=create_listen&amp;{$misc->href}&amp;no_id={$_REQUEST['no_id']}\">{$lang['strcreatelisten']}</a></p>\n";
+       }
+
+       /**
+        * Display the properties of a listen
+        */      
+       function doListen($msg = '') {
+               global $data, $slony, $misc, $PHP_SELF;
+               global $lang;
+               
+               $misc->printTrail('slony_path');
+               $misc->printTitle($lang['strproperties']);
+               $misc->printMsg($msg);
+               
+               // Fetch the listen information
+               $listen = &$slony->getListen($_REQUEST['no_id'], $_REQUEST['listen_id']);               
+               
+               if (is_object($listen) && $listen->recordCount() > 0) {                 
+                       // Show comment if any
+                       if ($listen->f['no_comment'] !== null)
+                               echo "<p class=\"comment\">", $misc->printVal($listen->f['no_comment']), "</p>\n";
+
+                       echo "<table>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Provider</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($listen->f['no_comment']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Provider ID</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($listen->f['li_provider']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Origin</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($listen->f['origin']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Origin ID</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($listen->f['li_origin']), "</td></tr>\n";
+                       echo "</table>\n";
+               }
+               else echo "<p>{$lang['strnodata']}</p>\n";
+
+               echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=confirm_drop_listen&amp;{$misc->href}&amp;no_id={$_REQUEST['no_id']}&amp;listen_id={$_REQUEST['listen_id']}&amp;origin_id={$listen->f['li_origin']}\">{$lang['strdrop']}</a></p>\n";
+       }
+
+       /**
+        * Displays a screen where they can enter a new listen
+        */
+       function doCreateListen($confirm, $msg = '') {
+               global $data, $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               if ($confirm) {
+                       if (!isset($_POST['listenorigin'])) $_POST['listenorigin'] = '';
+                       if (!isset($_POST['listenprovider'])) $_POST['listenprovider'] = '';
+       
+                       // Fetch all servers
+                       $nodes = &$slony->getNodes();
+
+                       $misc->printTrail('slony_listens');
+                       $misc->printTitle($lang['strcreatelisten']);
+                       $misc->printMsg($msg);
+       
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo $misc->form;
+                       echo "<table width=\"100%\">\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left required\">Origin</th>\n";
+                       echo "\t\t<td class=\"data1\">\n\t\t\t<select name=\"listenorigin\">\n";
+                       while (!$nodes->EOF) {
+                               echo "\t\t\t\t<option value=\"{$nodes->f['no_id']}\"",
+                                       ($nodes->f['no_id'] == $_POST['listenorigin']) ? ' selected="selected"' : '', ">", htmlspecialchars($nodes->f['no_comment']), "</option>\n";
+                               $nodes->moveNext();
+                       }
+                       echo "\t\t\t</select>\n\t\t</td>\n\t\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left required\">Provider</th>\n";
+                       echo "\t\t<td class=\"data1\">\n\t\t\t<select name=\"listenprovider\">\n";
+                       $nodes->moveFirst();
+                       while (!$nodes->EOF) {
+                               echo "\t\t\t\t<option value=\"{$nodes->f['no_id']}\"",
+                                       ($nodes->f['no_id'] == $_POST['listenprovider']) ? ' selected="selected"' : '', ">", htmlspecialchars($nodes->f['no_comment']), "</option>\n";
+                               $nodes->moveNext();
+                       }
+                       echo "\t\t\t</select>\n\t\t</td>\n\t\n";                
+                       echo "\t</tr>\n";
+                       echo "</table>\n";
+                       echo "<p>\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"save_create_listen\" />\n";
+                       echo "<input type=\"hidden\" name=\"no_id\" value=\"", htmlspecialchars($_REQUEST['no_id']), "\" />\n";
+                       echo "<input type=\"submit\" value=\"{$lang['strcreate']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</p>\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->createListen($_POST['no_id'], $_POST['listenorigin'], $_POST['listenprovider']);
+                       if ($status == 0)
+                               doListens($lang['strlistencreated']);
+                       else
+                               doCreateListen(true, $lang['strlistencreatedbad']);
+               }
+       }
+
+       /**
+        * Show confirmation of drop and perform actual drop of a listen
+        */
+       function doDropListen($confirm) {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+
+               if ($confirm) {
+                       $misc->printTrail('slony_cluster');
+                       $misc->printTitle($lang['strdrop']);
+
+                       echo "<p>", sprintf($lang['strconfdroplisten'], $misc->printVal($_REQUEST['listen_id'])), "</p>\n";
+
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"drop_listen\" />\n";
+                       echo "<input type=\"hidden\" name=\"no_id\" value=\"", htmlspecialchars($_REQUEST['no_id']), "\" />\n";
+                       echo "<input type=\"hidden\" name=\"listen_id\" value=\"", htmlspecialchars($_REQUEST['listen_id']), "\" />\n";
+                       echo "<input type=\"hidden\" name=\"origin_id\" value=\"", htmlspecialchars($_REQUEST['origin_id']), "\" />\n";
+                       echo $misc->form;
+                       echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strdrop']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->dropListen($_REQUEST['no_id'], $_REQUEST['origin_id'], $_REQUEST['listen_id']);
+                       if ($status == 0)
+                               doListens($lang['strlistendropped']);
+                       else
+                               doListens($lang['strlistendroppedbad']);
+               }
+       }
+       
+       // REPLICATION SETS
+               
+       /**
+        * List all the replication sets
+        */
+       function doReplicationSets($msg = '') {
+               global $PHP_SELF, $slony, $misc;
+               global $lang;
+
+               $misc->printTrail('database');
+               $misc->printMsg($msg);
+
+               $sets = $slony->getReplicationSets();
+
+               $columns = array(
+                       'set_name' => array(
+                               'title' => $lang['strname'],
+                               'field' => 'set_comment'
+                       ),
+                       'actions' => array(
+                               'title' => $lang['stractions'],
+                       ),
+                       'set_comment' => array(
+                               'title' => $lang['strcomment'],
+                               'field' => 'set_comment'
+                       )
+               );
+               
+               $actions = array (
+                       'properties' => array(
+                               'title' => $lang['strproperties'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=set_properties&amp;",
+                               'vars'  => array('set_id' => 'set_id')
+                       ),
+                       'drop' => array(
+                               'title' => $lang['strdrop'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=confirm_drop_set&amp;",
+                               'vars'  => array('set_id' => 'set_id')
+                       ),
+                       'lock' => array(
+                               'title' => $lang['strlock'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=confirm_lock_set&amp;",
+                               'vars'  => array('set_id' => 'set_id')
+                       ),
+                       'unlock' => array(
+                               'title' => $lang['strunlock'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=confirm_unlock_set&amp;",
+                               'vars'  => array('set_id' => 'set_id')
+                       ),
+                       'merge' => array(
+                               'title' => $lang['strmerge'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=merge_set&amp;",
+                               'vars'  => array('set_id' => 'set_id')
+                       ),
+                       'move' => array(
+                               'title' => $lang['strmove'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=move_set&amp;",
+                               'vars'  => array('set_id' => 'set_id')
+                       ),
+                       'execute' => array(
+                               'title' => $lang['strexecute'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=execute_set&amp;",
+                               'vars'  => array('set_id' => 'set_id')
+                       )
+               );
+               
+               $misc->printTable($sets, $columns, $actions, $lang['strnorepsets']);
+               
+               echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=create_set&amp;{$misc->href}\">{$lang['strcreaterepset']}</a></p>\n";
+       }       
+
+       /**
+        * Display the properties of a replication set
+        */      
+       function doReplicationSet($msg = '') {
+               global $data, $slony, $misc, $PHP_SELF;
+               global $lang;
+               
+               $misc->printTrail('slony_set');
+               $misc->printTitle($lang['strproperties']);
+               $misc->printMsg($msg);
+               
+               // Fetch the set information
+               $set = &$slony->getReplicationSet($_REQUEST['set_id']);         
+               
+               if (is_object($set) && $set->recordCount() > 0) {                       
+                       // Show comment if any
+                       if ($set->f['set_comment'] !== null)
+                               echo "<p class=\"comment\">", $misc->printVal($set->f['set_comment']), "</p>\n";
+
+                       echo "<table>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strname']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($set->f['set_comment']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strid']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($set->f['set_id']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strlocked']}</th>\n";
+                       echo "<td class=\"data1\">", ($data->phpBool($set->f['is_locked'])) ? $lang['stryes'] : $lang['strno'], "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Origin ID</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($set->f['set_origin']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Origin Node</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($set->f['no_comment']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Subscriptions</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($set->f['subscriptions']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['strcomment']}</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($set->f['set_comment']), "</td></tr>\n";
+                       echo "</table>\n";
+               }
+               else echo "<p>{$lang['strnodata']}</p>\n";
+
+               echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=confirm_drop_set&amp;{$misc->href}&amp;set_id={$_REQUEST['set_id']}\">{$lang['strdrop']}</a> |\n";
+               echo "<a class=\"navlink\" href=\"{$PHP_SELF}?action=confirm_lock_set&amp;{$misc->href}&amp;set_id={$_REQUEST['set_id']}\">{$lang['strlock']}</a> |\n";
+               echo "<a class=\"navlink\" href=\"{$PHP_SELF}?action=confirm_unlock_set&amp;{$misc->href}&amp;set_id={$_REQUEST['set_id']}\">{$lang['strunlock']}</a> |\n";
+               echo "<a class=\"navlink\" href=\"{$PHP_SELF}?action=merge_set&amp;{$misc->href}&amp;set_id={$_REQUEST['set_id']}\">{$lang['strmerge']}</a> |\n";
+               echo "<a class=\"navlink\" href=\"{$PHP_SELF}?action=move_set&amp;{$misc->href}&amp;set_id={$_REQUEST['set_id']}\">{$lang['strmove']}</a> |\n";
+               echo "<a class=\"navlink\" href=\"{$PHP_SELF}?action=execute_set&amp;{$misc->href}&amp;set_id={$_REQUEST['set_id']}\">{$lang['strexecute']}</a></p>\n";
+       }
+
+       /**
+        * Displays a screen where they can enter a new set
+        */
+       function doCreateReplicationSet($confirm, $msg = '') {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               if ($confirm) {
+                       if (!isset($_POST['setid'])) $_POST['setid'] = '';
+                       if (!isset($_POST['setcomment'])) $_POST['setcomment'] = '';
+       
+                       $misc->printTrail('slony_sets');
+                       $misc->printTitle($lang['strcreaterepset']);
+                       $misc->printMsg($msg);
+       
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo $misc->form;
+                       echo "<table width=\"100%\">\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strid']}</th>\n";
+                       echo "\t\t<td class=\"data1\"><input name=\"setid\" size=\"5\" value=\"",
+                               htmlspecialchars($_POST['setid']), "\" /></td>\n\t</tr>\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strcomment']}</th>\n";
+                       echo "\t\t<td class=\"data1\"><textarea name=\"setcomment\" rows=\"3\" cols=\"32\" wrap=\"virtual\">", 
+                               htmlspecialchars($_POST['setcomment']), "</textarea></td>\n\t</tr>\n";
+                               
+                       echo "\t</tr>\n";
+                       echo "</table>\n";
+                       echo "<p>\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"save_create_set\" />\n";
+                       echo "<input type=\"submit\" value=\"{$lang['strcreate']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</p>\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->createReplicationSet($_POST['setid'], $_POST['setcomment']);
+                       if ($status == 0)
+                               doReplicationSets($lang['strrepsetcreated']);
+                       else
+                               doCreateReplicationSet(true, $lang['strrepsetcreatedbad']);
+               }
+       }
+
+       /**
+        * Show confirmation of drop and perform actual drop of a set
+        */
+       function doDropReplicationSet($confirm) {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+
+               if ($confirm) {
+                       $misc->printTrail('slony_cluster');
+                       $misc->printTitle($lang['strdrop']);
+
+                       echo "<p>", sprintf($lang['strconfdroprepset'], $misc->printVal($_REQUEST['set_id'])), "</p>\n";
+
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"drop_set\" />\n";
+                       echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                       echo $misc->form;
+                       echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strdrop']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->dropReplicationSet($_REQUEST['set_id']);
+                       if ($status == 0)
+                               doReplicationSet($lang['strrepsetdropped']);
+                       else
+                               doReplicationSet($lang['strrepsetdroppedbad']);
+               }
+       }
+
+       /**
+        * Show confirmation of lock and perform actual lock of a set
+        */
+       function doLockReplicationSet($confirm) {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+
+               if ($confirm) {
+                       $misc->printTrail('slony_cluster');
+                       $misc->printTitle($lang['strlock']);
+
+                       echo "<p>", sprintf($lang['strconflockrepset'], $misc->printVal($_REQUEST['set_id'])), "</p>\n";
+
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"lock_set\" />\n";
+                       echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                       echo $misc->form;
+                       echo "<input type=\"submit\" name=\"lock\" value=\"{$lang['strlock']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->lockReplicationSet($_REQUEST['set_id'], true);
+                       if ($status == 0)
+                               doReplicationSet($lang['strrepsetlocked']);
+                       else
+                               doReplicationSet($lang['strrepsetlockedbad']);
+               }
+       }
+
+       /**
+        * Show confirmation of unlock and perform actual unlock of a set
+        */
+       function doUnlockReplicationSet($confirm) {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+
+               if ($confirm) {
+                       $misc->printTrail('slony_cluster');
+                       $misc->printTitle($lang['strunlock']);
+
+                       echo "<p>", sprintf($lang['strconfunlockrepset'], $misc->printVal($_REQUEST['set_id'])), "</p>\n";
+
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"unlock_set\" />\n";
+                       echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                       echo $misc->form;
+                       echo "<input type=\"submit\" name=\"unlock\" value=\"{$lang['strunlock']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->lockReplicationSet($_REQUEST['set_id'], false);
+                       if ($status == 0)
+                               doReplicationSets($lang['strrepsetunlocked']);
+                       else
+                               doReplicationSets($lang['strrepsetunlockedbad']);
+               }
+       }
+       
+       /**
+        * Displays a screen where they can merge one set into another
+        */
+       function doMergeReplicationSet($confirm, $msg = '') {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               if ($confirm) {
+                       if (!isset($_POST['target'])) $_POST['target'] = '';
+       
+                       $sets = $slony->getReplicationSets();
+       
+                       $misc->printTrail('slony_sets');
+                       $misc->printTitle($lang['strmerge']);
+                       $misc->printMsg($msg);
+       
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo $misc->form;
+                       echo "<table>\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strmergeinto']}</th>\n";
+                       echo "<td class=\"data1\" colspan=\"3\"><select name=\"target\">";
+                       while (!$sets->EOF) {
+                               if ($sets->f['set_id'] != $_REQUEST['set_id']) {
+                                       echo "<option value=\"{$sets->f['set_id']}\">";
+                                       echo htmlspecialchars($sets->f['set_comment']), "</option>\n";
+                               }
+                               $sets->moveNext();      
+                       }
+                       echo "</select></td></tr>\n";
+                       echo "</table>\n";
+                       echo "<p>\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"save_merge_set\" />\n";
+                       echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                       echo "<input type=\"submit\" value=\"{$lang['strmerge']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</p>\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->mergeReplicationSet($_POST['set_id'], $_POST['target']);
+                       if ($status == 0)
+                               doReplicationSet($lang['strrepsetmerged']);
+                       else
+                               doMergeReplicationSet(true, $lang['strrepsetmergedbad']);
+               }
+       }
+
+       /**
+        * Displays a screen where they can move one set into another
+        */
+       function doMoveReplicationSet($confirm, $msg = '') {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               if ($confirm) {
+                       if (!isset($_POST['new_origin'])) $_POST['new_origin'] = '';
+       
+                       $nodes = $slony->getNodes();
+                       $set = $slony->getReplicationSet($_REQUEST['set_id']);
+       
+                       $misc->printTrail('slony_sets');
+                       $misc->printTitle($lang['strmove']);
+                       $misc->printMsg($msg);
+       
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo $misc->form;
+                       echo "<table>\n";
+                       echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strneworigin']}</th>\n";
+                       echo "<td class=\"data1\" colspan=\"3\"><select name=\"new_origin\">";
+                       while (!$nodes->EOF) {
+                               if ($nodes->f['no_id'] != $set->f['set_origin']) {
+                                       echo "<option value=\"{$nodes->f['no_id']}\">";
+                                       echo htmlspecialchars($nodes->f['no_comment']), "</option>\n";
+                               }
+                               $nodes->moveNext();     
+                       }
+                       echo "</select></td></tr>\n";
+                       echo "</table>\n";
+                       echo "<p>\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"save_move_set\" />\n";
+                       echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                       echo "<input type=\"submit\" value=\"{$lang['strmove']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</p>\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->moveReplicationSet($_POST['set_id'], $_POST['new_origin']);
+                       if ($status == 0)
+                               doReplicationSet($lang['strrepsetmoved']);
+                       else
+                               doMoveReplicationSet(true, $lang['strrepsetmovedbad']);
+               }
+       }
+
+       /**
+        * Displays a screen where they can enter a DDL script to
+        * be executed on all or a particular node, for this set.
+        */
+       function doExecuteReplicationSet($confirm, $msg = '') {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               if ($confirm) {
+                       if (!isset($_POST['script'])) $_POST['script'] = '';
+       
+                       $nodes = $slony->getNodes();
+       
+                       $misc->printTrail('slony_sets');
+                       $misc->printTitle($lang['strexecute']);
+                       $misc->printMsg($msg);
+       
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo $misc->form;
+                       echo "<table>\n";
+                       /* Slony 1.1 only
+                       echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['stronlyonnode']}</th>\n";
+                       echo "<td class=\"data1\" colspan=\"3\"><select name=\"only_on_node\">";
+                       echo "<option value=\"0\"></option>\n";
+                       while (!$nodes->EOF) {
+                               echo "<option value=\"{$nodes->f['no_id']}\"", ($_POST['only_on_node'] == $nodes->f['no_id'] ? ' selected="selected"' : ''), ">";
+                               echo htmlspecialchars($nodes->f['no_comment']), "</option>\n";
+                               $nodes->moveNext();     
+                       }
+                       echo "</select></td></tr>\n";
+                       */
+                       echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strddlscript']}</th>\n";
+                       echo "\t\t<td class=\"data1\"><textarea name=\"script\" rows=\"20\" cols=\"40\" wrap=\"virtual\">", 
+                               htmlspecialchars($_POST['script']), "</textarea></td>\n\t</tr>\n";
+                       echo "</table>\n";
+                       echo "<p>\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"save_execute_set\" />\n";
+                       echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                       echo "<input type=\"submit\" value=\"{$lang['strexecute']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</p>\n";
+                       echo "</form>\n";
+               }
+               else {
+                       if (trim($_POST['script']) == '') {
+                               doExecuteReplicationSet(true, $lang['strscriptneedsbody']);
+                               return;
+                       }
+                       
+                       $status = $slony->executeReplicationSet($_POST['set_id'], $_POST['script']);
+                       if ($status == 0)
+                               doReplicationSet($lang['strscriptexecuted']);
+                       else
+                               doExecuteReplicationSet(true, $lang['strscriptexecutedbad']);
+               }
+       }
+                       
+       // TABLES
+       
+       /**
+        * List all the tables in a replication set
+        */
+       function doTables($msg = '') {
+               global $PHP_SELF, $data, $slony, $misc;
+               global $lang;
+
+               $misc->printTrail('database');
+               $misc->printMsg($msg);
+
+               $tables = $slony->getTables($_REQUEST['set_id']);
+
+               $columns = array(
+                       'table' => array(
+                               'title' => $lang['strtable'],
+                               'field' => 'qualname',
+                       ),
+                       'owner' => array(
+                               'title' => $lang['strowner'],
+                               'field' => 'relowner',
+                       ),
+                       'tablespace' => array(
+                               'title' => $lang['strtablespace'],
+                               'field' => 'tablespace'
+                       ),
+                       'tuples' => array(
+                               'title' => $lang['strestimatedrowcount'],
+                               'field' => 'reltuples',
+                               'type'  => 'numeric'
+                       ),
+                       'actions' => array(
+                               'title' => $lang['stractions'],
+                       ),
+                       'comment' => array(
+                               'title' => $lang['strcomment'],
+                               'field' => 'relcomment',
+                       ),
+               );
+               
+               $actions = array(
+                       'properties' => array(
+                               'title' => $lang['strproperties'],
+                               'url'   => "redirect.php?subject=table&amp;{$misc->href}&amp;",
+                               'vars'  => array('table' => 'relname', 'schema' => 'nspname'),
+                       ),
+                       'remove' => array(
+                               'title' => $lang['strremove'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=confirm_drop_table&amp;set_id={$_REQUEST['set_id']}&amp;",
+                               'vars'  => array('tab_id' => 'tab_id', 'qualname' => 'qualname'),
+                       ),
+                       'move' => array(
+                               'title' => $lang['strmove'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=move_table&amp;set_id={$_REQUEST['set_id']}&amp;stage=1&amp;",
+                               'vars'  => array('tab_id' => 'tab_id'),
+                       )
+               );
+               
+               if (!$data->hasTablespaces()) unset($columns['tablespace']);
+
+               $misc->printTable($tables, $columns, $actions, $lang['strnotables']);
+               
+               echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=add_table&amp;stage=1&amp;set_id={$_REQUEST['set_id']}&amp;{$misc->href}\">{$lang['straddtable']}</a></p>\n";
+       }
+
+       /**
+        * Displays a screen where they can add a table to a 
+        * replication set.
+        */
+       function doAddTable($stage, $msg = '') {
+               global $data, $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               switch ($stage) {
+                       case 1:
+                               if (!isset($_POST['tab_id'])) $_POST['tab_id'] = '';
+                               if (!isset($_POST['comment'])) $_POST['comment'] = '';
+                               
+                               $tables = &$data->getTables(true);
+               
+                               $misc->printTrail('slony_sets');
+                               $misc->printTitle($lang['straddtable']);
+                               $misc->printMsg($msg);
+               
+                               echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                               echo $misc->form;
+                               echo "<table width=\"100%\">\n";
+                               echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strtable']}</th>\n";
+                               echo "<td class=\"data1\" colspan=\"3\"><select name=\"target\">";
+                               while (!$tables->EOF) {
+                                       $key = array('schemaname' => $tables->f['nspname'], 'tablename' => $tables->f['relname']);
+                                       $key = serialize($key);
+                                       echo "<option value=\"", htmlspecialchars($key), "\">";
+                                       echo htmlspecialchars($tables->f['nspname']), '.';
+                                       echo htmlspecialchars($tables->f['relname']), "</option>\n";
+                                       $tables->moveNext();    
+                               }
+                               echo "</select></td></tr>\n";
+                               echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strid']}</th>\n";
+                               echo "\t\t<td class=\"data1\"><input name=\"tab_id\" size=\"5\" value=\"",
+                                       htmlspecialchars($_POST['tab_id']), "\" /></td>\n\t</tr>\n";
+                               echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strcomment']}</th>\n";
+                               echo "\t\t<td class=\"data1\"><textarea name=\"comment\" rows=\"3\" cols=\"32\" wrap=\"virtual\">", 
+                                       htmlspecialchars($_POST['comment']), "</textarea></td>\n\t</tr>\n";
+                                       
+                               echo "\t</tr>\n";
+                               echo "</table>\n";
+                               echo "<p>\n";
+                               echo "<input type=\"hidden\" name=\"action\" value=\"add_table\" />\n";
+                               echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"stage\" value=\"2\" />\n";
+                               echo "<input type=\"submit\" value=\"{$lang['strnext']}\" />\n";
+                               echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                               echo "</p>\n";
+                               echo "</form>\n";
+                               break;
+                       case 2:
+                               // Unserialize table and fetch. This is a bit messy
+                               // because the table could be in another schema.
+                               $_REQUEST['target'] = unserialize($_REQUEST['target']);
+                               $data->setSchema($_REQUEST['target']['schemaname']);
+                               // Get indexes
+                               $indexes = &$data->getIndexes($_REQUEST['target']['tablename'], true);
+                               if ($indexes->recordCount() == 0) {
+                                       doAddTable(1, $lang['strtableneedsuniquekey']);
+                                       return;
+                               }
+                               
+                               // Get triggers
+                               $triggers = &$data->getTriggers($_REQUEST['target']['tablename']);                              
+
+                               // If only one index and no triggers then jump to next step
+                               if ($indexes->recordCount() == 1 && $triggers->recordCount() == 0) {
+                                       $_REQUEST['idxname'] = $indexes->f['indname'];
+                                       $_REQUEST['nspname'] = $_REQUEST['target']['schemaname'];
+                                       $_REQUEST['relname'] = $_REQUEST['target']['tablename'];
+                                       $_REQUEST['target'] = serialize($_REQUEST['target']);
+                                       doAddTable(3);
+                                       return;
+                               }
+                               
+                               $misc->printTrail('slony_sets');
+                               $misc->printTitle($lang['straddtable']);
+                               $misc->printMsg($msg);
+               
+                               echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                               echo $misc->form;
+                               echo "<table>\n";
+                               if ($indexes->recordCount() > 1) {
+                                       echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strindex']}</th>\n";
+                                       echo "<td class=\"data1\" colspan=\"3\"><select name=\"idxname\">";
+                                       while (!$indexes->EOF) {
+                                               echo "<option value=\"", htmlspecialchars($indexes->f['indname']), "\">";
+                                               echo htmlspecialchars($indexes->f['indname']), "</option>\n";
+                                               $indexes->moveNext();   
+                                       }
+                                       echo "</select></td></tr>\n";
+                               }
+                               else {
+                                       echo "<input type=\"hidden\" name=\"idxname\" value=\"", htmlspecialchars($indexes->f['indname']), "\" />\n";
+                               }
+                               if ($triggers->recordCount() > 0) {
+                                       echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strtriggers']}</th>\n";
+                                       echo "<td class=\"data1\" colspan=\"3\"><p>{$lang['strtabletriggerstoretain']}</p>\n";
+                                       while (!$triggers->EOF) {
+                                               echo "<input type=\"checkbox\" name=\"storedtriggers[", htmlspecialchars($triggers->f['tgname']), "]\">";
+                                               echo htmlspecialchars($triggers->f['tgname']), "<br/>\n";
+                                               $triggers->moveNext();  
+                                       }
+                                       echo "</select></td></tr>\n";                                   
+                               }
+                               echo "</table>\n";
+                               echo "<p>\n";
+                               echo "<input type=\"hidden\" name=\"action\" value=\"add_table\" />\n";
+                               echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"tab_id\" value=\"", htmlspecialchars($_REQUEST['tab_id']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"comment\" value=\"", htmlspecialchars($_REQUEST['comment']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"nspname\" value=\"", htmlspecialchars($_REQUEST['target']['schemaname']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"relname\" value=\"", htmlspecialchars($_REQUEST['target']['tablename']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"target\" value=\"", htmlspecialchars(serialize($_REQUEST['target'])), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"stage\" value=\"3\" />\n";
+                               echo "<input type=\"submit\" value=\"{$lang['stradd']}\" />\n";
+                               echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                               echo "</p>\n";
+                               echo "</form>\n";
+                               break;
+                       case 3:
+                               if (!isset($_REQUEST['storedtriggers'])) $_REQUEST['storedtriggers'] = array();
+                               $status = $slony->addTable($_REQUEST['set_id'], $_REQUEST['tab_id'], $_REQUEST['nspname'], $_REQUEST['relname'], 
+                                                                                                               $_REQUEST['idxname'], $_REQUEST['comment'], array_keys($_REQUEST['storedtriggers']));
+                               if ($status == 0)
+                                       doTables($lang['strtableaddedtorepset']);
+                               else
+                                       doAddTable(2, $lang['strtableaddedtorepsetbad']);
+                               break;
+               }
+       }
+
+       /**
+        * Displays a screen where they can move a table to a 
+        * replication set.
+        */
+       function doMoveTable($stage, $msg = '') {
+               global $data, $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               switch ($stage) {
+                       case 1:
+                               if (!isset($_POST['new_set_id'])) $_POST['new_set_id'] = '';
+                               
+                               $sets = &$slony->getReplicationSets();
+               
+                               $misc->printTrail('slony_sets');
+                               $misc->printTitle($lang['strmove']);
+                               $misc->printMsg($msg);
+               
+                               echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                               echo $misc->form;
+                               echo "<table>\n";
+                               echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strnewrepset']}</th>\n";
+                               echo "<td class=\"data1\" colspan=\"3\"><select name=\"new_set_id\">";
+                               while (!$sets->EOF) {
+                                       if ($sets->f['set_id'] != $_REQUEST['set_id']) {
+                                               echo "<option value=\"{$sets->f['set_id']}\">";
+                                               echo htmlspecialchars($sets->f['set_comment']), "</option>\n";
+                                       }
+                                       $sets->moveNext();      
+                               }
+                               echo "</select></td></tr>\n";
+                               echo "</table>\n";
+                               echo "<p>\n";
+                               echo "<input type=\"hidden\" name=\"action\" value=\"move_table\" />\n";
+                               echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"tab_id\" value=\"", htmlspecialchars($_REQUEST['tab_id']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"stage\" value=\"2\" />\n";
+                               echo "<input type=\"submit\" value=\"{$lang['strmove']}\" />\n";
+                               echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                               echo "</p>\n";
+                               echo "</form>\n";
+                               break;
+                       case 2:
+                               $status = $slony->moveTable($_REQUEST['tab_id'], $_REQUEST['new_set_id']);
+                               if ($status == 0)
+                                       doTables('Table moved to replication set.');
+                               else
+                                       doMoveTable(1, 'Failed moving table to replication set.');
+                               break;
+               }
+       }
+
+       /**
+        * Show confirmation of drop and perform actual drop of a table from a
+        * replication set.
+        */
+       function doRemoveTable($confirm) {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+
+               if ($confirm) {
+                       $misc->printTrail('slony_cluster');
+                       $misc->printTitle('Remove');
+
+                       echo "<p>", sprintf($lang['strconfremovetablefromrepset'], 
+                               $misc->printVal($_REQUEST['qualname']), $misc->printVal($_REQUEST['set_id'])), "</p>\n";
+
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"drop_table\" />\n";
+                       echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                       echo "<input type=\"hidden\" name=\"tab_id\" value=\"", htmlspecialchars($_REQUEST['tab_id']), "\" />\n";
+                       echo $misc->form;
+                       echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strremove']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->removeTable($_REQUEST['tab_id']);
+                       if ($status == 0)
+                               doTables($lang['strtableremovedfromrepset']);
+                       else
+                               doTables($lang['strtableremovedfromrepsetbad']);
+               }
+       }
+
+       // SEQUENCES
+       
+       /**
+        * List all the sequences in a replication set
+        */
+       function doSequences($msg = '') {
+               global $PHP_SELF, $data, $slony, $misc;
+               global $lang;
+
+               $misc->printTrail('database');
+               $misc->printMsg($msg);
+
+               $sequences = $slony->getSequences($_REQUEST['set_id']);
+
+               $columns = array(
+                       'sequence' => array(
+                               'title' => $lang['strsequence'],
+                               'field' => 'qualname',
+                       ),
+                       'owner' => array(
+                               'title' => $lang['strowner'],
+                               'field' => 'seqowner',
+                       ),
+                       'actions' => array(
+                               'title' => $lang['stractions'],
+                       ),
+                       'comment' => array(
+                               'title' => $lang['strcomment'],
+                               'field' => 'seqcomment',
+                       ),
+               );
+               
+               $actions = array(
+                       'properties' => array(
+                               'title' => $lang['strproperties'],
+                               'url'   => "sequences.php?action=properties&amp;{$misc->href}&amp;",
+                               'vars'  => array('sequence' => 'seqname', 'schema' => 'nspname'),
+                       ),
+                       'remove' => array(
+                               'title' => $lang['strremove'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=confirm_drop_sequence&amp;set_id={$_REQUEST['set_id']}&amp;",
+                               'vars'  => array('seq_id' => 'seq_id', 'qualname' => 'qualname'),
+                       ),
+                       'move' => array(
+                               'title' => $lang['strmove'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=move_sequence&amp;set_id={$_REQUEST['set_id']}&amp;stage=1&amp;",
+                               'vars'  => array('seq_id' => 'seq_id'),
+                       )
+               );
+               
+               $misc->printTable($sequences, $columns, $actions, $lang['strnosequences']);
+               
+               echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=add_sequence&amp;stage=1&amp;set_id={$_REQUEST['set_id']}&amp;{$misc->href}\">{$lang['straddsequence']}</a></p>\n";
+       }
+
+       /**
+        * Displays a screen where they can add a sequence to a 
+        * replication set.
+        */
+       function doAddSequence($stage, $msg = '') {
+               global $data, $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               switch ($stage) {
+                       case 1:
+                               if (!isset($_POST['seq_id'])) $_POST['seq_id'] = '';
+                               if (!isset($_POST['comment'])) $_POST['comment'] = '';
+                               
+                               $sequences = &$data->getSequences(true);
+               
+                               $misc->printTrail('slony_sets');
+                               $misc->printTitle($lang['straddsequence']);
+                               $misc->printMsg($msg);
+               
+                               echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                               echo $misc->form;
+                               echo "<table width=\"100%\">\n";
+                               echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strsequence']}</th>\n";
+                               echo "<td class=\"data1\" colspan=\"3\"><select name=\"target\">";
+                               while (!$sequences->EOF) {
+                                       $key = array('schemaname' => $sequences->f['nspname'], 'sequencename' => $sequences->f['seqname']);
+                                       $key = serialize($key);
+                                       echo "<option value=\"", htmlspecialchars($key), "\">";
+                                       echo htmlspecialchars($sequences->f['nspname']), '.';
+                                       echo htmlspecialchars($sequences->f['seqname']), "</option>\n";
+                                       $sequences->moveNext(); 
+                               }
+                               echo "</select></td></tr>\n";
+                               echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strid']}</th>\n";
+                               echo "\t\t<td class=\"data1\"><input name=\"seq_id\" size=\"5\" value=\"",
+                                       htmlspecialchars($_POST['seq_id']), "\" /></td>\n\t</tr>\n";
+                               echo "\t<tr>\n\t\t<th class=\"data left\">{$lang['strcomment']}</th>\n";
+                               echo "\t\t<td class=\"data1\"><textarea name=\"comment\" rows=\"3\" cols=\"32\" wrap=\"virtual\">", 
+                                       htmlspecialchars($_POST['comment']), "</textarea></td>\n\t</tr>\n";
+                                       
+                               echo "\t</tr>\n";
+                               echo "</table>\n";
+                               echo "<p>\n";
+                               echo "<input type=\"hidden\" name=\"action\" value=\"add_sequence\" />\n";
+                               echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"stage\" value=\"2\" />\n";
+                               echo "<input type=\"submit\" value=\"{$lang['stradd']}\" />\n";
+                               echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                               echo "</p>\n";
+                               echo "</form>\n";
+                               break;
+                       case 2:
+                               // Unserialize sequence.
+                               $_REQUEST['target'] = unserialize($_REQUEST['target']);
+
+                               $status = $slony->addSequence($_REQUEST['set_id'], $_REQUEST['seq_id'], 
+                                                                                                                       $_REQUEST['target']['schemaname'] . '.' . $_REQUEST['target']['sequencename'],
+                                                                                                                       $_REQUEST['comment']);
+                               if ($status == 0)
+                                       doSequences($lang['strsequenceaddedtorepset']);
+                               else
+                                       doAddSequence(1, $lang['strsequenceaddedtorepsetbad']);
+                               break;
+               }
+       }
+
+       /**
+        * Show confirmation of drop and perform actual drop of a sequence from a
+        * replication set.
+        */
+       function doRemoveSequence($confirm) {
+               global $slony, $misc;
+               global $PHP_SELF, $lang;
+
+               if ($confirm) {
+                       $misc->printTrail('slony_cluster');
+                       $misc->printTitle($lang['strremove']);
+
+                       echo "<p>", sprintf($lang['strconfremovesequencefromrepset'], 
+                               $misc->printVal($_REQUEST['qualname']), $misc->printVal($_REQUEST['set_id'])), "</p>\n";
+
+                       echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                       echo "<input type=\"hidden\" name=\"action\" value=\"drop_sequence\" />\n";
+                       echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                       echo "<input type=\"hidden\" name=\"seq_id\" value=\"", htmlspecialchars($_REQUEST['seq_id']), "\" />\n";
+                       echo $misc->form;
+                       echo "<input type=\"submit\" name=\"drop\" value=\"{$lang['strremove']}\" />\n";
+                       echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                       echo "</form>\n";
+               }
+               else {
+                       $status = $slony->removeSequence($_REQUEST['seq_id']);
+                       if ($status == 0)
+                               doSequences($lang['strsequenceremovedfromrepset']);
+                       else
+                               doSequences($lang['strsequenceremovedfromrepsetbad']);
+               }
+       }
+
+       /**
+        * Displays a screen where they can move a sequence to a 
+        * replication set.
+        */
+       function doMoveSequence($stage, $msg = '') {
+               global $data, $slony, $misc;
+               global $PHP_SELF, $lang;
+               
+               switch ($stage) {
+                       case 1:
+                               if (!isset($_POST['new_set_id'])) $_POST['new_set_id'] = '';
+                               
+                               $sets = &$slony->getReplicationSets();
+               
+                               $misc->printTrail('slony_sets');
+                               $misc->printTitle($lang['strmove']);
+                               $misc->printMsg($msg);
+               
+                               echo "<form action=\"$PHP_SELF\" method=\"post\">\n";
+                               echo $misc->form;
+                               echo "<sequence>\n";
+                               echo "\t<tr>\n\t\t<th class=\"data left required\">{$lang['strnewrepset']}</th>\n";
+                               echo "<td class=\"data1\" colspan=\"3\"><select name=\"new_set_id\">";
+                               while (!$sets->EOF) {
+                                       if ($sets->f['set_id'] != $_REQUEST['set_id']) {
+                                               echo "<option value=\"{$sets->f['set_id']}\">";
+                                               echo htmlspecialchars($sets->f['set_comment']), "</option>\n";
+                                       }
+                                       $sets->moveNext();      
+                               }
+                               echo "</select></td></tr>\n";
+                               echo "</sequence>\n";
+                               echo "<p>\n";
+                               echo "<input type=\"hidden\" name=\"action\" value=\"move_sequence\" />\n";
+                               echo "<input type=\"hidden\" name=\"set_id\" value=\"", htmlspecialchars($_REQUEST['set_id']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"seq_id\" value=\"", htmlspecialchars($_REQUEST['seq_id']), "\" />\n";
+                               echo "<input type=\"hidden\" name=\"stage\" value=\"2\" />\n";
+                               echo "<input type=\"submit\" value=\"{$lang['strmove']}\" />\n";
+                               echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
+                               echo "</p>\n";
+                               echo "</form>\n";
+                               break;
+                       case 2:
+                               $status = $slony->moveSequence($_REQUEST['seq_id'], $_REQUEST['new_set_id']);
+                               if ($status == 0)
+                                       doSequences('Sequence moved to replication set.');
+                               else
+                                       doMoveSequence(1, 'Failed moving sequence to replication set.');
+                               break;
+               }
+       }
+                               
+       // SUBSCRIPTIONS
+       
+       /**
+        * List all the subscriptions
+        */
+       function doSubscriptions($msg = '') {
+               global $slony, $misc;
+               global $lang;
+
+               $misc->printTrail('database');
+               $misc->printMsg($msg);
+
+               $subscriptions = $slony->getSubscribedNodes($_REQUEST['set_id']);
+
+               $columns = array(
+                       'no_name' => array(
+                               'title' => $lang['strname'],
+                               'field' => 'no_comment'
+                       ),
+/*                     'actions' => array(
+                               'title' => $lang['stractions'],
+                       ),*/
+                       'no_comment' => array(
+                               'title' => $lang['strcomment'],
+                               'field' => 'no_comment'
+                       )
+               );
+               
+               $actions = array (
+                       'properties' => array(
+                               'title' => $lang['strproperties'],
+                               'url'   => "plugin_slony.php?{$misc->href}&amp;action=subscription_properties&amp;",
+                               'vars'  => array('set_id' => 'sub_set', 'no_id' => 'no_id')
+                       )
+               );
+               
+               $misc->printTable($subscriptions, $columns, $actions, $lang['strnosubscriptions']);
+       }
+       
+       /**
+        * Display the properties of a subscription
+        */      
+       function doSubscription($msg = '') {
+               global $data, $slony, $misc, $PHP_SELF;
+               global $lang;
+               
+               $misc->printTrail('slony_subscription');
+               $misc->printTitle($lang['strproperties']);
+               $misc->printMsg($msg);
+               
+               // Fetch the subscription information
+               $subscription = &$slony->getSubscription($_REQUEST['set_id'], $_REQUEST['no_id']);              
+               
+               if (is_object($subscription) && $subscription->recordCount() > 0) {                     
+                       // Show comment if any
+                       if ($subscription->f['receiver'] !== null)
+                               echo "<p class=\"comment\">", $misc->printVal($subscription->f['receiver']), "</p>\n";
+
+                       echo "<table>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Provider ID</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($subscription->f['sub_provider']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Provider Name</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($subscription->f['provider']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Receiver ID</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($subscription->f['sub_receiver']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">Receiver Name</th>\n";
+                       echo "<td class=\"data1\">", $misc->printVal($subscription->f['receiver']), "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">{$lang['stractive']}</th>\n";
+                       echo "<td class=\"data1\">", ($data->phpBool($subscription->f['sub_active'])) ? $lang['stryes'] : $lang['strno'], "</td></tr>\n";
+                       echo "<tr><th class=\"data left\" width=\"70\">May Forward</th>\n";
+                       echo "<td class=\"data1\">", ($data->phpBool($subscription->f['sub_forward'])) ? $lang['stryes'] : $lang['strno'], "</td></tr>\n";
+                       echo "</table>\n";
+               }
+               else echo "<p>{$lang['strnodata']}</p>\n";
+       }
+
+       // Tree actions
+       if ($action == 'tree') doTree('clusters');
+       elseif ($action == 'clusters_top') doTree('clusters_top');
+       elseif ($action == 'nodes') doTree('nodes');
+       elseif ($action == 'nodes_top') doTree('nodes_top');
+       elseif ($action == 'paths') doTree('paths');
+       elseif ($action == 'listens') doTree('listens');
+       elseif ($action == 'sets') doTree('sets');
+       elseif ($action == 'sets_top') doTree('sets_top');
+       elseif ($action == 'subscriptions') doTree('subscriptions');
+       elseif ($action == 'sequences') doTree('sequences');
+       elseif ($action == 'tables') doTree('tables');
+
+       $misc->printHeader('Slony');
+       $misc->printBody();
+
+       switch ($action) {
+               case 'save_create_cluster':
+                       if (isset($_POST['cancel'])) doClusters();
+                       else doCreateCluster(false);
+                       break;
+               case 'create_cluster':
+                       doCreateCluster(true);
+                       break;
+               case 'drop_cluster':
+                       if (isset($_POST['cancel'])) doClusters();
+                       else doDropCluster(false);
+                       break;
+               case 'confirm_drop_cluster':
+                       doDropCluster(true);
+                       break;
+               case 'nodes_properties':
+                       doNodes();
+                       break;
+               case 'node_properties':
+                       doNode();
+                       break;
+               case 'save_create_node':
+                       if (isset($_POST['cancel'])) doNodes();
+                       else doCreateNode(false);
+                       break;
+               case 'create_node':
+                       doCreateNode(true);
+                       break;
+               case 'drop_node':
+                       if (isset($_POST['cancel'])) doNodes();
+                       else doDropNode(false);
+                       break;
+               case 'confirm_drop_node':
+                       doDropNode(true);
+                       break;
+               case 'failover_node':
+                       if (isset($_POST['cancel'])) doNodes();
+                       else doFailoverNode(false);
+                       break;
+               case 'confirm_failover_node':
+                       doFailoverNode(true);
+                       break;
+               case 'paths_properties':
+                       doPaths();
+                       break;
+               case 'path_properties':
+                       doPath();
+                       break;
+               case 'save_create_path':
+                       if (isset($_POST['cancel'])) doPaths();
+                       else doCreatePath(false);
+                       break;
+               case 'create_path':
+                       doCreatePath(true);
+                       break;
+               case 'drop_path':
+                       if (isset($_POST['cancel'])) doPaths();
+                       else doDropPath(false);
+                       break;
+               case 'confirm_drop_path':
+                       doDropPath(true);
+                       break;
+               case 'listens_properties':
+                       doListens();
+                       break;
+               case 'listen_properties':
+                       doListen();
+                       break;
+               case 'save_create_listen':
+                       if (isset($_POST['cancel'])) doListens();
+                       else doCreateListen(false);
+                       break;
+               case 'create_listen':
+                       doCreateListen(true);
+                       break;
+               case 'drop_listen':
+                       if (isset($_POST['cancel'])) doListens();
+                       else doDropListen(false);
+                       break;
+               case 'confirm_drop_listen':
+                       doDropListen(true);
+                       break;
+               case 'sets_properties':
+                       doReplicationSets();
+                       break;
+               case 'set_properties':
+                       doReplicationSet();
+                       break;
+               case 'save_create_set':
+                       if (isset($_POST['cancel'])) doReplicationSets();
+                       else doCreateReplicationSet(false);
+                       break;
+               case 'create_set':
+                       doCreateReplicationSet(true);
+                       break;
+               case 'drop_set':
+                       if (isset($_POST['cancel'])) doReplicationSets();
+                       else doDropReplicationSet(false);
+                       break;
+               case 'confirm_drop_set':
+                       doDropReplicationSet(true);
+                       break;
+               case 'lock_set':
+                       if (isset($_POST['cancel'])) doReplicationSets();
+                       else doLockReplicationSet(false);
+                       break;
+               case 'confirm_lock_set':
+                       doLockReplicationSet(true);
+                       break;
+               case 'unlock_set':
+                       if (isset($_POST['cancel'])) doReplicationSets();
+                       else doUnlockReplicationSet(false);
+                       break;
+               case 'confirm_unlock_set':
+                       doUnlockReplicationSet(true);
+                       break;
+               case 'save_merge_set':
+                       if (isset($_POST['cancel'])) doReplicationSet();
+                       else doMergeReplicationSet(false);
+                       break;
+               case 'merge_set':
+                       doMergeReplicationSet(true);
+                       break;
+               case 'save_move_set':
+                       if (isset($_POST['cancel'])) doReplicationSet();
+                       else doMoveReplicationSet(false);
+                       break;
+               case 'move_set':
+                       doMoveReplicationSet(true);
+                       break;
+               case 'save_execute_set':
+                       if (isset($_POST['cancel'])) doReplicationSet();
+                       else doExecuteReplicationSet(false);
+                       break;
+               case 'execute_set':
+                       doExecuteReplicationSet(true);
+                       break;
+               case 'tables_properties':
+                       doTables();
+                       break;
+               case 'add_table':
+                       if (isset($_REQUEST['cancel'])) doTables();
+                       else doAddTable($_REQUEST['stage']);
+                       break;
+               case 'drop_table':
+                       if (isset($_POST['cancel'])) doTables();
+                       else doRemoveTable(false);
+                       break;
+               case 'confirm_drop_table':
+                       doRemoveTable(true);
+                       break;
+               case 'move_table':
+                       if (isset($_REQUEST['cancel'])) doTables();
+                       else doMoveTable($_REQUEST['stage']);
+                       break;
+               case 'sequences_properties':
+                       doSequences();
+                       break;
+               case 'add_sequence':
+                       if (isset($_REQUEST['cancel'])) doSequences();
+                       else doAddSequence($_REQUEST['stage']);
+                       break;
+               case 'drop_sequence':
+                       if (isset($_POST['cancel'])) doSequences();
+                       else doRemoveSequence(false);
+                       break;
+               case 'confirm_drop_sequence':
+                       doRemoveSequence(true);
+                       break;
+               case 'move_sequence':
+                       if (isset($_REQUEST['cancel'])) doSequences();
+                       else doMoveSequence($_REQUEST['stage']);
+                       break;
+               case 'subscriptions_properties':
+                       doSubscriptions();
+                       break;
+               case 'subscription_properties':
+                       doSubscription();
+                       break;
+               case 'clusters_properties':
+                       doClusters();
+                       break;
+               case 'cluster_properties':
+                       doCluster();
+                       break;
+               default:
+                       // Shouldn't happen
+       }
+       
+       $misc->printFooter();
+
+?>
index b5b27d1b8ed35e82ab84d40267f954b20ba05514..aa40dfa4bb5642ceca33effc334cdfc1e8477f2b 100644 (file)
@@ -3,7 +3,7 @@
        /**
         * Manage sequences in a database
         *
-        * $Id: sequences.php,v 1.28 2005/05/02 15:47:24 chriskl Exp $
+        * $Id: sequences.php,v 1.29 2005/06/16 14:40:11 chriskl Exp $
         */
        
        // Include application functions
                        echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=reset&amp;{$misc->href}&amp;sequence=", urlencode($sequence->f['seqname']), "\">{$lang['strreset']}</a> |\n";
                        echo "<a class=\"navlink\" href=\"{$PHP_SELF}?{$misc->href}\">{$lang['strshowallsequences']}</a></p>\n";
                }
-               else echo "<p>{$lang['strnodata']}.</p>\n";
+               else echo "<p>{$lang['strnodata']}</p>\n";
        }
 
        /**
diff --git a/sql.php b/sql.php
index a144582b329ef964327a40a9126d9e9bc1431228..54df2429e660b229667c21dea861ac959610be45 100644 (file)
--- a/sql.php
+++ b/sql.php
@@ -6,7 +6,7 @@
         * how many SQL statements have been strung together with semi-colons
         * @param $query The SQL query string to execute
         *
-        * $Id: sql.php,v 1.31 2005/06/01 10:38:14 soranzo Exp $
+        * $Id: sql.php,v 1.32 2005/06/16 14:40:11 chriskl Exp $
         */
 
        // Prevent timeouts on large exports (non-safe mode only)
        }
        
        echo "<p>{$lang['strsqlexecuted']}</p>\n";
-       
-       echo "<p><a class=\"navlink\" href=\"database.php?{$misc->href}",
-               "&amp;action=sql&amp;query=", urlencode($_POST['query']), "\">{$lang['streditsql']}</a>";
+
+       echo "<p><a class=\"navlink\" href=\"database.php?database=", urlencode($_REQUEST['database']),
+               "&amp;server=", urlencode($_REQUEST['server']), "&amp;action=sql&amp;query=", urlencode($_POST['query']), "\">{$lang['streditsql']}</a>";
        if ($conf['show_reports'] && isset($rs) && is_object($rs) && $rs->recordCount() > 0) {
                echo " | <a class=\"navlink\" href=\"reports.php?{$misc->href}&amp;action=create&amp;report_sql=",
                        urlencode($_POST['query']), "\">{$lang['strcreatereport']}</a>";
diff --git a/sql/plugins/slony1_base.sql b/sql/plugins/slony1_base.sql
new file mode 100755 (executable)
index 0000000..ce35e49
--- /dev/null
@@ -0,0 +1,387 @@
+-- ----------------------------------------------------------------------\r
+-- slony1_base.sql\r
+--\r
+--    Declaration of the basic replication schema.\r
+--\r
+--     Copyright (c) 2003-2004, PostgreSQL Global Development Group\r
+--     Author: Jan Wieck, Afilias USA INC.\r
+--\r
+-- $Id: slony1_base.sql,v 1.2 2005/06/16 14:40:14 chriskl Exp $\r
+-- ----------------------------------------------------------------------\r
+\r
+\r
+-- **********************************************************************\r
+-- * Tables\r
+-- **********************************************************************\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_node\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_node (\r
+       no_id                           int4,\r
+       no_active                       bool,\r
+       no_comment                      text,\r
+\r
+       CONSTRAINT "sl_node-pkey"\r
+               PRIMARY KEY (no_id)\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_set\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_set (\r
+       set_id                          int4,\r
+       set_origin                      int4,\r
+       set_locked                      @NAMESPACE@.xxid,\r
+       set_comment                     text,\r
+\r
+       CONSTRAINT "sl_set-pkey"\r
+               PRIMARY KEY (set_id),\r
+       CONSTRAINT "set_origin-no_id-ref"\r
+               FOREIGN KEY (set_origin)\r
+               REFERENCES @NAMESPACE@.sl_node (no_id)\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_setsync\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_setsync (\r
+       ssy_setid                       int4,\r
+       ssy_origin                      int4,\r
+       ssy_seqno                       int8,\r
+       ssy_minxid                      @NAMESPACE@.xxid,\r
+       ssy_maxxid                      @NAMESPACE@.xxid,\r
+       ssy_xip                         text,\r
+       ssy_action_list         text,\r
+\r
+       CONSTRAINT "sl_setsync-pkey"\r
+               PRIMARY KEY (ssy_setid),\r
+       CONSTRAINT "ssy_setid-set_id-ref"\r
+               FOREIGN KEY (ssy_setid)\r
+               REFERENCES @NAMESPACE@.sl_set (set_id),\r
+       CONSTRAINT "ssy_origin-no_id-ref"\r
+               FOREIGN KEY (ssy_origin)\r
+               REFERENCES @NAMESPACE@.sl_node (no_id)\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_table\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_table (\r
+       tab_id                          int4,\r
+       tab_reloid                      oid UNIQUE NOT NULL,\r
+       tab_set                         int4,\r
+       tab_idxname                     name NOT NULL,\r
+       tab_altered                     boolean NOT NULL,\r
+       tab_comment                     text,\r
+\r
+       CONSTRAINT "sl_table-pkey"\r
+               PRIMARY KEY (tab_id),\r
+       CONSTRAINT "tab_set-set_id-ref"\r
+               FOREIGN KEY (tab_set)\r
+               REFERENCES @NAMESPACE@.sl_set (set_id)\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_trigger\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_trigger (\r
+       trig_tabid                      int4,\r
+       trig_tgname                     name,\r
+\r
+       CONSTRAINT "sl_trigger-pkey"\r
+               PRIMARY KEY (trig_tabid, trig_tgname),\r
+       CONSTRAINT "trig_tabid-tab_id-ref"\r
+               FOREIGN KEY (trig_tabid)\r
+               REFERENCES @NAMESPACE@.sl_table (tab_id)\r
+               ON DELETE CASCADE\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_sequence\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_sequence (\r
+       seq_id                          int4,\r
+       seq_reloid                      oid UNIQUE NOT NULL,\r
+       seq_set                         int4,\r
+       seq_comment                     text,\r
+\r
+       CONSTRAINT "sl_sequence-pkey"\r
+               PRIMARY KEY (seq_id),\r
+       CONSTRAINT "seq_set-set_id-ref"\r
+               FOREIGN KEY (seq_set)\r
+               REFERENCES @NAMESPACE@.sl_set (set_id)\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_path\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_path (\r
+       pa_server                       int4,\r
+       pa_client                       int4,\r
+       pa_conninfo                     text NOT NULL,\r
+       pa_connretry            int4,\r
+\r
+       CONSTRAINT "sl_path-pkey"\r
+               PRIMARY KEY (pa_server, pa_client),\r
+       CONSTRAINT "pa_server-no_id-ref"\r
+               FOREIGN KEY (pa_server)\r
+               REFERENCES @NAMESPACE@.sl_node (no_id),\r
+       CONSTRAINT "pa_client-no_id-ref"\r
+               FOREIGN KEY (pa_client)\r
+               REFERENCES @NAMESPACE@.sl_node (no_id)\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_listen\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_listen (\r
+       li_origin                       int4,\r
+       li_provider                     int4,\r
+       li_receiver                     int4,\r
+\r
+       CONSTRAINT "sl_listen-pkey"\r
+               PRIMARY KEY (li_origin, li_provider, li_receiver),\r
+       CONSTRAINT "li_origin-no_id-ref"\r
+               FOREIGN KEY (li_origin)\r
+               REFERENCES @NAMESPACE@.sl_node (no_id),\r
+       CONSTRAINT "sl_listen-sl_path-ref"\r
+               FOREIGN KEY (li_provider, li_receiver)\r
+               REFERENCES @NAMESPACE@.sl_path (pa_server, pa_client)\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_subscribe\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_subscribe (\r
+       sub_set                         int4,\r
+       sub_provider            int4,\r
+       sub_receiver            int4,\r
+       sub_forward                     bool,\r
+       sub_active                      bool,\r
+\r
+       CONSTRAINT "sl_subscribe-pkey"\r
+               PRIMARY KEY (sub_receiver, sub_set),\r
+       CONSTRAINT "sl_subscribe-sl_path-ref"\r
+               FOREIGN KEY (sub_provider, sub_receiver)\r
+               REFERENCES @NAMESPACE@.sl_path (pa_server, pa_client),\r
+       CONSTRAINT "sub_set-set_id-ref"\r
+               FOREIGN KEY (sub_set)\r
+               REFERENCES @NAMESPACE@.sl_set (set_id)\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_event\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_event (\r
+       ev_origin                       int4,\r
+       ev_seqno                        int8,\r
+       ev_timestamp            timestamp,\r
+       ev_minxid                       @NAMESPACE@.xxid,\r
+       ev_maxxid                       @NAMESPACE@.xxid,\r
+       ev_xip                          text,\r
+       ev_type                         text,\r
+       ev_data1                        text,\r
+       ev_data2                        text,\r
+       ev_data3                        text,\r
+       ev_data4                        text,\r
+       ev_data5                        text,\r
+       ev_data6                        text,\r
+       ev_data7                        text,\r
+       ev_data8                        text,\r
+\r
+       CONSTRAINT "sl_event-pkey"\r
+               PRIMARY KEY (ev_origin, ev_seqno)\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_confirm\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_confirm (\r
+       con_origin                      int4,\r
+       con_received            int4,\r
+       con_seqno                       int8,\r
+       con_timestamp           timestamp DEFAULT timeofday()::timestamp\r
+);\r
+create index sl_confirm_idx1 on @NAMESPACE@.sl_confirm\r
+       (con_origin, con_received, con_seqno);\r
+create index sl_confirm_idx2 on @NAMESPACE@.sl_confirm\r
+       (con_received, con_seqno);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_seqlog\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_seqlog (\r
+       seql_seqid                      int4,\r
+       seql_origin                     int4,\r
+       seql_ev_seqno           int8,\r
+       seql_last_value         int8\r
+);\r
+create index sl_seqlog_idx on @NAMESPACE@.sl_seqlog\r
+       (seql_origin, seql_ev_seqno, seql_seqid);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION sequenceLastValue (seqname)\r
+--\r
+--     Support function used in sl_seqlastvalue view\r
+-- ----------------------------------------------------------------------\r
+create function @NAMESPACE@.sequenceLastValue(text) returns int8\r
+as '\r
+declare\r
+       p_seqname       alias for $1;\r
+       v_seq_row       record;\r
+begin\r
+       for v_seq_row in execute ''select last_value from '' || p_seqname\r
+       loop\r
+               return v_seq_row.last_value;\r
+       end loop;\r
+\r
+       -- not reached\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- VIEW sl_seqlastvalue\r
+-- ----------------------------------------------------------------------\r
+create view @NAMESPACE@.sl_seqlastvalue as\r
+       select SQ.seq_id, SQ.seq_set, SQ.seq_reloid,\r
+                       S.set_origin as seq_origin,\r
+                       @NAMESPACE@.sequenceLastValue(\r
+                                       "pg_catalog".quote_ident(PGN.nspname) || '.' ||\r
+                                       "pg_catalog".quote_ident(PGC.relname)) as seq_last_value\r
+               from @NAMESPACE@.sl_sequence SQ, @NAMESPACE@.sl_set S,\r
+                       "pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN\r
+               where S.set_id = SQ.seq_set\r
+                       and PGC.oid = SQ.seq_reloid and PGN.oid = PGC.relnamespace;\r
+               \r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_log_1\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_log_1 (\r
+       log_origin                      int4,\r
+       log_xid                         @NAMESPACE@.xxid,\r
+       log_tableid                     int4,\r
+       log_actionseq           int8,\r
+       log_cmdtype                     char,\r
+       log_cmddata                     text\r
+);\r
+create index sl_log_1_idx1 on @NAMESPACE@.sl_log_1\r
+       (log_origin, log_xid @NAMESPACE@.xxid_ops, log_actionseq);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_log_2\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_log_2 (\r
+       log_origin                      int4,\r
+       log_xid                         @NAMESPACE@.xxid,\r
+       log_tableid                     int4,\r
+       log_actionseq           int8,\r
+       log_cmdtype                     char,\r
+       log_cmddata                     text\r
+);\r
+create index sl_log_2_idx1 on @NAMESPACE@.sl_log_2\r
+       (log_origin, log_xid @NAMESPACE@.xxid_ops, log_actionseq);\r
+\r
+\r
+-- **********************************************************************\r
+-- * Sequences\r
+-- **********************************************************************\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- SEQUENCE sl_local_node_id\r
+--\r
+--     The local node ID is initialized to -1, meaning that this node\r
+--     is not initialized yet.\r
+-- ----------------------------------------------------------------------\r
+create sequence @NAMESPACE@.sl_local_node_id\r
+       MINVALUE -1;\r
+SELECT setval('@NAMESPACE@.sl_local_node_id', -1);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- SEQUENCE sl_event_seq\r
+--\r
+--     The sequence for numbering events originating from this node.\r
+-- ----------------------------------------------------------------------\r
+create sequence @NAMESPACE@.sl_event_seq;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- SEQUENCE sl_action_seq\r
+--\r
+--     The sequence to number statements in the transaction logs, so that\r
+--     the replication engines can figure out the "agreeable" order of\r
+--     statements.\r
+-- ----------------------------------------------------------------------\r
+create sequence @NAMESPACE@.sl_action_seq;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- SEQUENCE sl_rowid_seq\r
+--\r
+--     Application tables that do not have a natural primary key must\r
+--     be modified and an int8 column added that serves as a rowid for us.\r
+--     The values are assigned with a default from this sequence.\r
+-- ----------------------------------------------------------------------\r
+create sequence @NAMESPACE@.sl_rowid_seq;\r
+grant select, update on @NAMESPACE@.sl_rowid_seq to public;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- SEQUENCE sl_log_status\r
+--\r
+--     Bit 0x01 determines the currently active log table\r
+--     Bit 0x02 tells if the engine needs to read both logs\r
+--     after switching until the old log is clean and truncated.\r
+--\r
+--     Possible values:\r
+--             0               sl_log_1 active, sl_log_2 clean\r
+--             1               sl_log_2 active, sl_log_1 clean\r
+--             2               sl_log_1 active, sl_log_2 unknown - cleanup\r
+--             3               sl_log_2 active, sl_log_1 unknown - cleanup\r
+-- ----------------------------------------------------------------------\r
+create sequence @NAMESPACE@.sl_log_status\r
+       MINVALUE 0 MAXVALUE 3;\r
+SELECT setval('@NAMESPACE@.sl_log_status', 0);\r
+\r
+\r
+-- **********************************************************************\r
+-- * Misc\r
+-- **********************************************************************\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- TABLE sl_config_lock\r
+--\r
+--     This table exists solely to prevent overlapping execution of\r
+--     configuration change procedures and the resulting possible\r
+--     deadlocks.\r
+-- ----------------------------------------------------------------------\r
+create table @NAMESPACE@.sl_config_lock (\r
+       dummy                           integer\r
+);\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- Last but not least grant USAGE to the replication schema objects.\r
+-- ----------------------------------------------------------------------\r
+grant usage on schema @NAMESPACE@ to public;\r
+\r
+\r
diff --git a/sql/plugins/slony1_base.v73.sql b/sql/plugins/slony1_base.v73.sql
new file mode 100755 (executable)
index 0000000..26a1cf1
--- /dev/null
@@ -0,0 +1,12 @@
+-- ----------------------------------------------------------------------\r
+-- slony1_base.v73.sql\r
+--\r
+--    Version 7.3 specific parts of the basic replication schema.\r
+--\r
+--     Copyright (c) 2003-2004, PostgreSQL Global Development Group\r
+--     Author: Jan Wieck, Afilias USA INC.\r
+--\r
+-- $Id: slony1_base.v73.sql,v 1.2 2005/06/16 14:40:14 chriskl Exp $\r
+-- ----------------------------------------------------------------------\r
+\r
+\r
diff --git a/sql/plugins/slony1_base.v74.sql b/sql/plugins/slony1_base.v74.sql
new file mode 100755 (executable)
index 0000000..fee05c2
--- /dev/null
@@ -0,0 +1,12 @@
+-- ----------------------------------------------------------------------\r
+-- slony1_base.v73.sql\r
+--\r
+--    Version 7.3 specific parts of the basic replication schema.\r
+--\r
+--     Copyright (c) 2003-2004, PostgreSQL Global Development Group\r
+--     Author: Jan Wieck, Afilias USA INC.\r
+--\r
+-- $Id: slony1_base.v74.sql,v 1.2 2005/06/16 14:40:14 chriskl Exp $\r
+-- ----------------------------------------------------------------------\r
+\r
+\r
diff --git a/sql/plugins/slony1_funcs.sql b/sql/plugins/slony1_funcs.sql
new file mode 100755 (executable)
index 0000000..0008466
--- /dev/null
@@ -0,0 +1,4271 @@
+-- ----------------------------------------------------------------------\r
+-- slony1_funcs.sql\r
+--\r
+--    Declaration of replication support functions.\r
+--\r
+--     Copyright (c) 2003-2004, PostgreSQL Global Development Group\r
+--     Author: Jan Wieck, Afilias USA INC.\r
+--\r
+-- $Id: slony1_funcs.sql,v 1.2 2005/06/16 14:40:14 chriskl Exp $\r
+-- ----------------------------------------------------------------------\r
+\r
+\r
+-- **********************************************************************\r
+-- * C functions in src/backend/slony1_base.c\r
+-- **********************************************************************\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION createEvent (cluster_name, ev_type [, ev_data [...]])\r
+--\r
+--     Create an sl_event entry\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.createEvent (name, text)\r
+       returns bigint\r
+       as '$libdir/slony1_funcs', '_Slony_I_createEvent'\r
+       language C\r
+       called on null input;\r
+\r
+create or replace function @NAMESPACE@.createEvent (name, text, text)\r
+       returns bigint\r
+       as '$libdir/slony1_funcs', '_Slony_I_createEvent'\r
+       language C\r
+       called on null input;\r
+\r
+create or replace function @NAMESPACE@.createEvent (name, text, text, text)\r
+       returns bigint\r
+       as '$libdir/slony1_funcs', '_Slony_I_createEvent'\r
+       language C\r
+       called on null input;\r
+\r
+create or replace function @NAMESPACE@.createEvent (name, text, text, text, text)\r
+       returns bigint\r
+       as '$libdir/slony1_funcs', '_Slony_I_createEvent'\r
+       language C\r
+       called on null input;\r
+\r
+create or replace function @NAMESPACE@.createEvent (name, text, text, text, text, text)\r
+       returns bigint\r
+       as '$libdir/slony1_funcs', '_Slony_I_createEvent'\r
+       language C\r
+       called on null input;\r
+\r
+create or replace function @NAMESPACE@.createEvent (name, text, text, text, text, text, text)\r
+       returns bigint\r
+       as '$libdir/slony1_funcs', '_Slony_I_createEvent'\r
+       language C\r
+       called on null input;\r
+\r
+create or replace function @NAMESPACE@.createEvent (name, text, text, text, text, text, text, text)\r
+       returns bigint\r
+       as '$libdir/slony1_funcs', '_Slony_I_createEvent'\r
+       language C\r
+       called on null input;\r
+\r
+create or replace function @NAMESPACE@.createEvent (name, text, text, text, text, text, text, text, text)\r
+       returns bigint\r
+       as '$libdir/slony1_funcs', '_Slony_I_createEvent'\r
+       language C\r
+       called on null input;\r
+\r
+create or replace function @NAMESPACE@.createEvent (name, text, text, text, text, text, text, text, text, text)\r
+       returns bigint\r
+       as '$libdir/slony1_funcs', '_Slony_I_createEvent'\r
+       language C\r
+       called on null input;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION denyAccess (cluster_name)\r
+--\r
+--     Trigger function to prevent modifications to a table on\r
+--     a subscriber.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.denyAccess ()\r
+       returns trigger\r
+       as '$libdir/slony1_funcs', '_Slony_I_denyAccess'\r
+       language C\r
+       security definer;\r
+grant execute on function @NAMESPACE@.denyAccess () to public;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION lockedSet (cluster_name)\r
+--\r
+--     Trigger function to prevent modifications to a table before\r
+--     and after a moveSet().\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.lockedSet ()\r
+       returns trigger\r
+       as '$libdir/slony1_funcs', '_Slony_I_lockedSet'\r
+       language C;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION getLocalNodeId (name)\r
+--\r
+--     \r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.getLocalNodeId (name) returns int4\r
+    as '$libdir/slony1_funcs', '_Slony_I_getLocalNodeId'\r
+       language C\r
+       security definer;\r
+grant execute on function @NAMESPACE@.getLocalNodeId (name) to public;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION getModuleVersion ()\r
+--\r
+--     Returns the compiled in version number of the Slony-I shared\r
+--     object.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.getModuleVersion () returns text\r
+    as '$libdir/slony1_funcs', '_Slony_I_getModuleVersion'\r
+       language C\r
+       security definer;\r
+grant execute on function @NAMESPACE@.getModuleVersion () to public;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setSessionRole (name, role)\r
+--\r
+--     \r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setSessionRole (name, text) returns text\r
+    as '$libdir/slony1_funcs', '_Slony_I_setSessionRole'\r
+       language C\r
+       security definer;\r
+grant execute on function @NAMESPACE@.setSessionRole (name, text) to public;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION getSessionRole (name, role)\r
+--\r
+--     \r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.getSessionRole (name) returns text\r
+    as '$libdir/slony1_funcs', '_Slony_I_getSessionRole'\r
+       language C\r
+       security definer;\r
+grant execute on function @NAMESPACE@.getSessionRole (name) to public;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION logTrigger ()\r
+--\r
+--     \r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.logTrigger () returns trigger\r
+    as '$libdir/slony1_funcs', '_Slony_I_logTrigger'\r
+       language C\r
+       security definer;\r
+grant execute on function @NAMESPACE@.logTrigger () to public;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION terminateNodeConnections (name)\r
+--\r
+--     \r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.terminateNodeConnections (name) returns int4\r
+    as '$libdir/slony1_funcs', '_Slony_I_terminateNodeConnections'\r
+       language C;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION cleanupListener ()\r
+--\r
+--     \r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.cleanupListener () returns int4\r
+    as '$libdir/slony1_funcs', '_Slony_I_cleanupListener'\r
+       language C;\r
+\r
+\r
+-- **********************************************************************\r
+-- * PL/pgSQL functions for administrative tasks\r
+-- **********************************************************************\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION slonyVersionMajor()\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.slonyVersionMajor()\r
+returns int4\r
+as '\r
+begin\r
+       return 1;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION slonyVersionMinor()\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.slonyVersionMinor()\r
+returns int4\r
+as '\r
+begin\r
+       return 0;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION slonyVersionPatchlevel()\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.slonyVersionPatchlevel()\r
+returns int4\r
+as '\r
+begin\r
+       return 5;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION slonyVersion()\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.slonyVersion()\r
+returns text\r
+as '\r
+begin\r
+       return ''''     || @NAMESPACE@.slonyVersionMajor() || ''.''\r
+                               || @NAMESPACE@.slonyVersionMinor() || ''.''\r
+                               || @NAMESPACE@.slonyVersionPatchlevel();\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION initializeLocalNode (no_id, no_comment)\r
+--\r
+--     Initializes a new node.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.initializeLocalNode (int4, text)\r
+returns int4\r
+as '\r
+declare\r
+       p_local_node_id         alias for $1;\r
+       p_comment                       alias for $2;\r
+       v_old_node_id           int4;\r
+       v_first_log_no          int4;\r
+       v_event_seq                     int8;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Make sure this node is uninitialized or got reset\r
+       -- ----\r
+       select last_value::int4 into v_old_node_id from @NAMESPACE@.sl_local_node_id;\r
+       if v_old_node_id != -1 then\r
+               raise exception ''Slony-I: This node is already initialized'';\r
+       end if;\r
+\r
+       -- ----\r
+       -- Set sl_local_node_id to the requested value and add our\r
+       -- own system to sl_node.\r
+       -- ----\r
+       perform setval(''@NAMESPACE@.sl_local_node_id'', p_local_node_id);\r
+       perform setval(''@NAMESPACE@.sl_rowid_seq'', \r
+                       p_local_node_id::int8 * ''1000000000000000''::int8);\r
+       perform @NAMESPACE@.storeNode_int (p_local_node_id, p_comment);\r
+       \r
+       return p_local_node_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION storeNode (no_id, no_comment)\r
+--\r
+--     Generate the STORE_NODE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.storeNode (int4, text)\r
+returns bigint\r
+as '\r
+declare\r
+       p_no_id                 alias for $1;\r
+       p_no_comment    alias for $2;\r
+begin\r
+       perform @NAMESPACE@.storeNode_int (p_no_id, p_no_comment);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''STORE_NODE'',\r
+                                                                       p_no_id, p_no_comment);\r
+end;\r
+' language plpgsql\r
+       called on null input;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION storeNode_int (no_id, no_comment)\r
+--\r
+--     Process the STORE_NODE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.storeNode_int (int4, text)\r
+returns int4\r
+as '\r
+declare\r
+       p_no_id                 alias for $1;\r
+       p_no_comment    alias for $2;\r
+       v_old_row               record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check if the node exists\r
+       -- ----\r
+       select * into v_old_row\r
+                       from @NAMESPACE@.sl_node\r
+                       where no_id = p_no_id\r
+                       for update;\r
+       if found then \r
+               -- ----\r
+               -- Node exists, update the existing row.\r
+               -- ----\r
+               update @NAMESPACE@.sl_node\r
+                               set no_comment = p_no_comment\r
+                               where no_id = p_no_id;\r
+       else\r
+               -- ----\r
+               -- New node, insert the sl_node row\r
+               -- ----\r
+               insert into @NAMESPACE@.sl_node\r
+                               (no_id, no_active, no_comment) values\r
+                               (p_no_id, ''f'', p_no_comment);\r
+       end if;\r
+\r
+       return p_no_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION enableNode (no_id)\r
+--\r
+--     Generate the ENABLE_NODE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.enableNode (int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_no_id                 alias for $1;\r
+       v_local_node_id int4;\r
+       v_node_row              record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that we are the node to activate and that we are\r
+       -- currently disabled.\r
+       -- ----\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+       select * into v_node_row\r
+                       from @NAMESPACE@.sl_node\r
+                       where no_id = p_no_id\r
+                       for update;\r
+       if not found then \r
+               raise exception ''Slony-I: node % not found'', p_no_id;\r
+       end if;\r
+       if v_node_row.no_active then\r
+               raise exception ''Slony-I: node % is already active'', p_no_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Activate this node and generate the ENABLE_NODE event\r
+       -- ----\r
+       perform @NAMESPACE@.enableNode_int (p_no_id);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''ENABLE_NODE'',\r
+                                                                       p_no_id);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION enableNode_int (no_id)\r
+--\r
+--     Process the ENABLE_NODE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.enableNode_int (int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_no_id                 alias for $1;\r
+       v_local_node_id int4;\r
+       v_node_row              record;\r
+       v_sub_row               record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that the node is inactive\r
+       -- ----\r
+       select * into v_node_row\r
+                       from @NAMESPACE@.sl_node\r
+                       where no_id = p_no_id\r
+                       for update;\r
+       if not found then \r
+               raise exception ''Slony-I: node % not found'', p_no_id;\r
+       end if;\r
+       if v_node_row.no_active then\r
+               return p_no_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Activate the node and generate sl_confirm status rows for it.\r
+       -- ----\r
+       update @NAMESPACE@.sl_node\r
+                       set no_active = ''t''\r
+                       where no_id = p_no_id;\r
+       insert into @NAMESPACE@.sl_confirm\r
+                       (con_origin, con_received, con_seqno)\r
+                       select no_id, p_no_id, 0 from @NAMESPACE@.sl_node\r
+                               where no_id != p_no_id\r
+                               and no_active;\r
+       insert into @NAMESPACE@.sl_confirm\r
+                       (con_origin, con_received, con_seqno)\r
+                       select p_no_id, no_id, 0 from @NAMESPACE@.sl_node\r
+                               where no_id != p_no_id\r
+                               and no_active;\r
+\r
+       -- ----\r
+       -- Generate ENABLE_SUBSCRIPTION events for all sets that\r
+       -- origin here and are subscribed by the just enabled node.\r
+       -- ----\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+       for v_sub_row in select SUB.sub_set, SUB.sub_provider from\r
+                       @NAMESPACE@.sl_set S,\r
+                       @NAMESPACE@.sl_subscribe SUB\r
+                       where S.set_origin = v_local_node_id\r
+                       and S.set_id = SUB.sub_set\r
+                       and SUB.sub_receiver = p_no_id\r
+                       for update of S\r
+       loop\r
+               perform @NAMESPACE@.enableSubscription (v_sub_row.sub_set,,\r
+                               v_sub_row.sub_provider, p_no_id);\r
+       end loop;\r
+\r
+       return p_no_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION disableNode (no_id)\r
+--\r
+--     Generate the DISABLE_NODE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.disableNode (int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_no_id                 alias for $1;\r
+begin\r
+       -- **** TODO ****\r
+       raise exception ''Slony-I: disableNode() not implemented'';\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION disableNode_int (no_id)\r
+--\r
+--     Process the DISABLE_NODE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.disableNode_int (int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_no_id                 alias for $1;\r
+begin\r
+       -- **** TODO ****\r
+       raise exception ''Slony-I: disableNode_int() not implemented'';\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION dropNode (no_id)\r
+--\r
+--     Generate the DROP_NODE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.dropNode (int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_no_id                 alias for $1;\r
+       v_node_row              record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that this got called on a different node\r
+       -- ----\r
+       if p_no_id = @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: DROP_NODE cannot initiate on the dropped node'';\r
+       end if;\r
+\r
+       select * into v_node_row from @NAMESPACE@.sl_node\r
+                       where no_id = p_no_id\r
+                       for update;\r
+       if not found then\r
+               raise exception ''Slony-I: unknown node ID %'', p_no_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Make sure we do not break other nodes subscriptions with this\r
+       -- ----\r
+       if exists (select true from @NAMESPACE@.sl_subscribe\r
+                       where sub_provider = p_no_id)\r
+       then\r
+               raise exception ''Slony-I: Node % is still configured as data provider'',\r
+                               p_no_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Make sure no set originates there any more\r
+       -- ----\r
+       if exists (select true from @NAMESPACE@.sl_set\r
+                       where set_origin = p_no_id)\r
+       then\r
+               raise exception ''Slony-I: Node % is still origin of one or more sets'',\r
+                               p_no_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Call the internal drop functionality and generate the event\r
+       -- ----\r
+       perform @NAMESPACE@.dropNode_int(p_no_id);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''DROP_NODE'',\r
+                                                                       p_no_id);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION dropNode_int (no_id)\r
+--\r
+--     Process the DROP_NODE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.dropNode_int (int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_no_id                 alias for $1;\r
+       v_tab_row               record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- If the dropped node is a remote node, clean the configuration\r
+       -- from all traces for it.\r
+       -- ----\r
+       if p_no_id <> @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               delete from @NAMESPACE@.sl_subscribe\r
+                               where sub_receiver = p_no_id;\r
+               delete from @NAMESPACE@.sl_listen\r
+                               where li_origin = p_no_id\r
+                                       or li_provider = p_no_id\r
+                                       or li_receiver = p_no_id;\r
+               delete from @NAMESPACE@.sl_path\r
+                               where pa_server = p_no_id\r
+                                       or pa_client = p_no_id;\r
+               delete from @NAMESPACE@.sl_confirm\r
+                               where con_origin = p_no_id\r
+                                       or con_received = p_no_id;\r
+               delete from @NAMESPACE@.sl_event\r
+                               where ev_origin = p_no_id;\r
+               delete from @NAMESPACE@.sl_node\r
+                               where no_id = p_no_id;\r
+\r
+               return p_no_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- This is us ... deactivate the node for now, the daemon\r
+       -- will call uninstallNode() in a separate transaction.\r
+       -- ----\r
+       update @NAMESPACE@.sl_node\r
+                       set no_active = false\r
+                       where no_id = p_no_id;\r
+\r
+       return p_no_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION failedNode (failed_node, backup_node)\r
+--\r
+--     Initiate a failover. This function must be called on all nodes\r
+--     and then waited for the restart of all node deamons.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.failedNode(int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_failed_node           alias for $1;\r
+       p_backup_node           alias for $2;\r
+       v_row                           record;\r
+       v_row2                          record;\r
+       v_n                                     int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- All consistency checks first\r
+       -- Check that every system that has a path to the failed node\r
+       -- also has a path to the backup node.\r
+       -- ----\r
+       for v_row in select P.pa_client\r
+                       from @NAMESPACE@.sl_path P\r
+                       where P.pa_server = p_failed_node\r
+                               and P.pa_client <> p_backup_node\r
+                               and not exists (select true from @NAMESPACE@.sl_path PP\r
+                                                       where PP.pa_server = p_backup_node\r
+                                                               and PP.pa_client = P.pa_client)\r
+       loop\r
+               raise exception ''Slony-I: cannot failover - node % has no path to the backup node'',\r
+                               v_row.pa_client;\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Check all sets originating on the failed node\r
+       -- ----\r
+       for v_row in select set_id\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_origin = p_failed_node\r
+       loop\r
+               -- ----\r
+               -- Check that the backup node is subscribed to all sets\r
+               -- that origin on the failed node\r
+               -- ----\r
+               select into v_row2 sub_forward, sub_active\r
+                               from @NAMESPACE@.sl_subscribe\r
+                               where sub_set = v_row.set_id\r
+                                       and sub_receiver = p_backup_node;\r
+               if not found then\r
+                       raise exception ''Slony-I: cannot failover - node % is not subscribed to set %'',\r
+                                       p_backup_node, v_row.set_id;\r
+               end if;\r
+\r
+               -- ----\r
+               -- Check that the subscription is active\r
+               -- ----\r
+               if not v_row2.sub_active then\r
+                       raise exception ''Slony-I: cannot failover - subscription for set % is not active'',\r
+                                       v_row.set_id;\r
+               end if;\r
+\r
+               -- ----\r
+               -- If there are other subscribers, the backup node needs to\r
+               -- be a forwarder too.\r
+               -- ----\r
+               select into v_n count(*)\r
+                               from @NAMESPACE@.sl_subscribe\r
+                               where sub_set = v_row.set_id\r
+                                       and sub_receiver <> p_backup_node;\r
+               if v_n > 0 and not v_row2.sub_forward then\r
+                       raise exception ''Slony-I: cannot failover - node % is not a forwarder of set %'',\r
+                                       p_backup_node, v_row.set_id;\r
+               end if;\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Terminate all connections of the failed node the hard way\r
+       -- ----\r
+       perform @NAMESPACE@.terminateNodeConnections(\r
+                       ''_@CLUSTERNAME@_Node_'' || p_failed_node);\r
+\r
+       -- ----\r
+       -- Let every node that listens for something on the failed node\r
+       -- listen for that on the backup node instead.\r
+       -- ----\r
+       for v_row in select * from @NAMESPACE@.sl_listen\r
+                       where li_provider = p_failed_node\r
+                               and li_receiver <> p_backup_node\r
+       loop\r
+               perform @NAMESPACE@.storeListen_int(v_row.li_origin,\r
+                               p_backup_node, v_row.li_receiver);\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Let the backup node listen for all events where the\r
+       -- failed node did listen for it.\r
+       -- ----\r
+       for v_row in select li_origin, li_provider\r
+                       from @NAMESPACE@.sl_listen\r
+                       where li_receiver = p_failed_node\r
+                               and li_provider <> p_backup_node\r
+       loop\r
+               perform @NAMESPACE@.storeListen_int(v_row.li_origin,\r
+                               v_row.li_provider, p_backup_node);\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Remove all sl_listen entries that receive anything from the\r
+       -- failed node.\r
+       -- ----\r
+       delete from @NAMESPACE@.sl_listen\r
+                       where li_provider = p_failed_node\r
+                               or li_receiver = p_failed_node;\r
+\r
+       -- ----\r
+       -- Move the sets\r
+       -- ----\r
+       for v_row in select S.set_id, (select count(*)\r
+                                       from @NAMESPACE@.sl_subscribe SUB\r
+                                       where S.set_id = SUB.sub_set\r
+                                               and SUB.sub_receiver <> p_backup_node\r
+                                               and SUB.sub_provider = p_failed_node)\r
+                                       as num_direct_receivers \r
+                       from @NAMESPACE@.sl_set S\r
+                       where S.set_origin = p_failed_node\r
+                       for update\r
+       loop\r
+               -- ----\r
+               -- If the backup node is the only direct subscriber ...\r
+               -- ----\r
+               if v_row.num_direct_receivers = 0 then\r
+raise notice ''failedNode: set % has no other direct receivers - move now'', v_row.set_id;\r
+                       -- ----\r
+                       -- backup_node is the only direct subscriber, move the set\r
+                       -- right now. On the backup node itself that includes restoring\r
+                       -- all user mode triggers, removing the protection trigger,\r
+                       -- adding the log trigger, removing the subscription and the\r
+                       -- obsolete setsync status.\r
+                       -- ----\r
+                       if p_backup_node = @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+                               for v_row2 in select * from @NAMESPACE@.sl_table\r
+                                               where tab_set = v_row.set_id\r
+                               loop\r
+                                       perform @NAMESPACE@.alterTableRestore(v_row2.tab_id);\r
+                               end loop;\r
+                       end if;\r
+\r
+                       update @NAMESPACE@.sl_set set set_origin = p_backup_node\r
+                                       where set_id = v_row.set_id;\r
+\r
+                       if p_backup_node = @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+                               delete from @NAMESPACE@.sl_setsync\r
+                                               where ssy_setid = v_row.set_id;\r
+\r
+                               for v_row2 in select * from @NAMESPACE@.sl_table\r
+                                               where tab_set = v_row.set_id\r
+                               loop\r
+                                       perform @NAMESPACE@.alterTableForReplication(v_row2.tab_id);\r
+                               end loop;\r
+                       end if;\r
+\r
+                       delete from @NAMESPACE@.sl_subscribe\r
+                                       where sub_set = v_row.set_id\r
+                                               and sub_receiver = p_backup_node;\r
+               else\r
+raise notice ''failedNode: set % has other direct receivers - change providers only'', v_row.set_id;\r
+                       -- ----\r
+                       -- Backup node is not the only direct subscriber. This\r
+                       -- means that at this moment, we redirect all direct\r
+                       -- subscribers to receive from the backup node, and the\r
+                       -- backup node itself to receive from another one.\r
+                       -- The admin utility will wait for the slon engine to\r
+                       -- restart and then call failedNode2() on the node with\r
+                       -- the highest SYNC and redirect this to it on\r
+                       -- backup node later.\r
+                       -- ----\r
+                       update @NAMESPACE@.sl_subscribe\r
+                                       set sub_provider = (select min(SS.sub_receiver)\r
+                                                       from @NAMESPACE@.sl_subscribe SS\r
+                                                       where SS.sub_set = v_row.set_id\r
+                                                               and SS.sub_provider = p_failed_node\r
+                                                               and SS.sub_receiver <> p_backup_node\r
+                                                               and SS.sub_forward)\r
+                                       where sub_set = v_row.set_id\r
+                                               and sub_receiver = p_backup_node;\r
+                       update @NAMESPACE@.sl_subscribe\r
+                                       set sub_provider = p_backup_node\r
+                                       where sub_set = v_row.set_id\r
+                                               and sub_provider = p_failed_node\r
+                                               and sub_receiver <> p_backup_node;\r
+               end if;\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Make sure the node daemon will restart\r
+       -- ----\r
+       notify "_@CLUSTERNAME@_Restart";\r
+\r
+       -- ----\r
+       -- That is it - so far.\r
+       -- ----\r
+       return p_failed_node;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION failedNode2 (failed_node, backup_node, set_id, ev_seqno, ev_seqfake)\r
+--\r
+--     On the node that has the highest sequence number of the failed node,\r
+--     fake the FAILED_NODE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.failedNode2 (int4, int4, int4, int8, int8)\r
+returns bigint\r
+as '\r
+declare\r
+       p_failed_node           alias for $1;\r
+       p_backup_node           alias for $2;\r
+       p_set_id                        alias for $3;\r
+       p_ev_seqno                      alias for $4;\r
+       p_ev_seqfake            alias for $5;\r
+       v_row                           record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       select * into v_row\r
+                       from @NAMESPACE@.sl_event\r
+                       where ev_origin = p_failed_node\r
+                       and ev_seqno = p_ev_seqno;\r
+       if not found then\r
+               raise exception ''Slony-I: event %,% not found'',\r
+                               p_failed_node, p_ev_seqno;\r
+       end if;\r
+\r
+       insert into @NAMESPACE@.sl_event\r
+                       (ev_origin, ev_seqno, ev_timestamp,\r
+                       ev_minxid, ev_maxxid, ev_xip,\r
+                       ev_type, ev_data1, ev_data2, ev_data3)\r
+                       values \r
+                       (p_failed_node, p_ev_seqfake, CURRENT_TIMESTAMP,\r
+                       v_row.ev_minxid, v_row.ev_maxxid, v_row.ev_xip,\r
+                       ''FAILOVER_SET'', p_failed_node::text, p_backup_node::text,\r
+                       p_set_id::text);\r
+       insert into @NAMESPACE@.sl_confirm\r
+                       (con_origin, con_received, con_seqno, con_timestamp)\r
+                       values\r
+                       (p_failed_node, @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@''),\r
+                       p_ev_seqfake, CURRENT_TIMESTAMP);\r
+       notify "_@CLUSTERNAME@_Event";\r
+       notify "_@CLUSTERNAME@_Confirm";\r
+       notify "_@CLUSTERNAME@_Restart";\r
+\r
+       perform @NAMESPACE@.failoverSet_int(p_failed_node,\r
+                       p_backup_node, p_set_id);\r
+\r
+       return p_ev_seqfake;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION failoverSet_int (failed_node, backup_node, set_id)\r
+--\r
+--     Finish failover for one set.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.failoverSet_int (int4, int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_failed_node           alias for $1;\r
+       p_backup_node           alias for $2;\r
+       p_set_id                        alias for $3;\r
+       v_row                           record;\r
+       v_last_sync                     int8;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Change the origin of the set now to the backup node.\r
+       -- On the backup node this includes changing all the\r
+       -- trigger and protection stuff\r
+       -- ----\r
+       if p_backup_node = @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               for v_row in select * from @NAMESPACE@.sl_table\r
+                               where tab_set = p_set_id\r
+               loop\r
+                       perform @NAMESPACE@.alterTableRestore(v_row.tab_id);\r
+               end loop;\r
+\r
+               delete from @NAMESPACE@.sl_setsync\r
+                               where ssy_setid = p_set_id;\r
+               delete from @NAMESPACE@.sl_subscribe\r
+                               where sub_set = p_set_id\r
+                                       and sub_receiver = p_backup_node;\r
+               update @NAMESPACE@.sl_set\r
+                               set set_origin = p_backup_node\r
+                               where set_id = p_set_id;\r
+\r
+               for v_row in select * from @NAMESPACE@.sl_table\r
+                               where tab_set = p_set_id\r
+               loop\r
+                       perform @NAMESPACE@.alterTableForReplication(v_row.tab_id);\r
+               end loop;\r
+       else\r
+               delete from @NAMESPACE@.sl_subscribe\r
+                               where sub_set = p_set_id\r
+                                       and sub_receiver = p_backup_node;\r
+               update @NAMESPACE@.sl_set\r
+                               set set_origin = p_backup_node\r
+                               where set_id = p_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- If we are a subscriber of the set ourself, change our\r
+       -- setsync status to reflect the new set origin.\r
+       -- ----\r
+       if exists (select true from @NAMESPACE@.sl_subscribe\r
+                       where sub_set = p_set_id\r
+                               and sub_receiver = @NAMESPACE@.getLocalNodeId(\r
+                                               ''_@CLUSTERNAME@''))\r
+       then\r
+               delete from @NAMESPACE@.sl_setsync\r
+                               where ssy_setid = p_set_id;\r
+\r
+               select coalesce(max(ev_seqno), 0) into v_last_sync\r
+                               from @NAMESPACE@.sl_event\r
+                               where ev_origin = p_backup_node\r
+                                       and ev_type = ''SYNC'';\r
+               if v_last_sync > 0 then\r
+                       insert into @NAMESPACE@.sl_setsync\r
+                                       (ssy_setid, ssy_origin, ssy_seqno,\r
+                                       ssy_minxid, ssy_maxxid, ssy_xip, ssy_action_list)\r
+                                       select p_set_id, p_backup_node, v_last_sync,\r
+                                       ev_minxid, ev_maxxid, ev_xip, NULL\r
+                                       from @NAMESPACE@.sl_event\r
+                                       where ev_origin = p_backup_node\r
+                                               and ev_seqno = v_last_sync;\r
+               else\r
+                       insert into @NAMESPACE@.sl_setsync\r
+                                       (ssy_setid, ssy_origin, ssy_seqno,\r
+                                       ssy_minxid, ssy_maxxid, ssy_xip, ssy_action_list)\r
+                                       values (p_set_id, p_backup_node, ''0'',\r
+                                       ''0'', ''0'', '''', NULL);\r
+               end if;\r
+                               \r
+       end if;\r
+\r
+       return p_failed_node;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION uninstallNode ()\r
+--\r
+--     Reset the whole database to standalone by removing the whole\r
+--     replication system.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.uninstallNode ()\r
+returns int4\r
+as '\r
+declare\r
+       v_tab_row               record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- This is us ... time for suicide! Restore all tables to\r
+       -- their original status.\r
+       -- ----\r
+       for v_tab_row in select * from @NAMESPACE@.sl_table loop\r
+               perform @NAMESPACE@.alterTableRestore(v_tab_row.tab_id);\r
+               perform @NAMESPACE@.tableDropKey(v_tab_row.tab_id);\r
+       end loop;\r
+\r
+       raise notice ''Slony-I: Please drop schema "_@CLUSTERNAME@"'';\r
+       return 0;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION storePath (pa_server, pa_client, pa_conninfo, pa_connretry)\r
+--\r
+--     Generate the STORE_PATH event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.storePath (int4, int4, text, int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_pa_server             alias for $1;\r
+       p_pa_client             alias for $2;\r
+       p_pa_conninfo   alias for $3;\r
+       p_pa_connretry  alias for $4;\r
+begin\r
+       perform @NAMESPACE@.storePath_int(p_pa_server, p_pa_client,\r
+                       p_pa_conninfo, p_pa_connretry);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''STORE_PATH'', \r
+                       p_pa_server, p_pa_client, p_pa_conninfo, p_pa_connretry);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION storePath_int (pa_server, pa_client, pa_conninfo, pa_connretry)\r
+--\r
+--     Process the STORE_PATH event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.storePath_int (int4, int4, text, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_pa_server             alias for $1;\r
+       p_pa_client             alias for $2;\r
+       p_pa_conninfo   alias for $3;\r
+       p_pa_connretry  alias for $4;\r
+       v_dummy                 int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check if the path already exists\r
+       -- ----\r
+       select 1 into v_dummy\r
+                       from @NAMESPACE@.sl_path\r
+                       where pa_server = p_pa_server\r
+                       and pa_client = p_pa_client\r
+                       for update;\r
+       if found then\r
+               -- ----\r
+               -- Path exists, update pa_conninfo\r
+               -- ----\r
+               update @NAMESPACE@.sl_path\r
+                               set pa_conninfo = p_pa_conninfo,\r
+                                       pa_connretry = p_pa_connretry\r
+                               where pa_server = p_pa_server\r
+                               and pa_client = p_pa_client;\r
+       else\r
+               -- ----\r
+               -- New path\r
+               --\r
+               -- In case we receive STORE_PATH events before we know\r
+               -- about the nodes involved in this, we generate those nodes\r
+               -- as pending.\r
+               -- ----\r
+               if not exists (select 1 from @NAMESPACE@.sl_node\r
+                                               where no_id = p_pa_server) then\r
+                       perform @NAMESPACE@.storeNode_int (p_pa_server, ''<event pending>'');\r
+               end if;\r
+               if not exists (select 1 from @NAMESPACE@.sl_node\r
+                                               where no_id = p_pa_client) then\r
+                       perform @NAMESPACE@.storeNode_int (p_pa_client, ''<event pending>'');\r
+               end if;\r
+               insert into @NAMESPACE@.sl_path\r
+                               (pa_server, pa_client, pa_conninfo, pa_connretry) values\r
+                               (p_pa_server, p_pa_client, p_pa_conninfo, p_pa_connretry);\r
+       end if;\r
+\r
+       return 0;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION dropPath (pa_server, pa_client)\r
+--\r
+--     Generate the DROP_PATH event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.dropPath (int4, int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_pa_server             alias for $1;\r
+       p_pa_client             alias for $2;\r
+       v_row                   record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- There should be no existing subscriptions. Auto unsubscribing\r
+       -- is considered too dangerous. \r
+       -- ----\r
+       for v_row in select sub_set, sub_provider, sub_receiver\r
+                       from @NAMESPACE@.sl_subscribe\r
+                       where sub_provider = p_pa_server\r
+                       and sub_receiver = p_pa_client\r
+       loop\r
+               raise exception \r
+                       ''Slony-I: Path cannot be dropped, subscription of set % needs it'',\r
+                       v_row.sub_set;\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Drop all sl_listen entries that depend on this path\r
+       -- ----\r
+       for v_row in select li_origin, li_provider, li_receiver\r
+                       from @NAMESPACE@.sl_listen\r
+                       where li_provider = p_pa_server\r
+                       and li_receiver = p_pa_client\r
+       loop\r
+               perform @NAMESPACE@.dropListen(\r
+                               v_row.li_origin, v_row.li_provider, v_row.li_receiver);\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Now drop the path and create the event\r
+       -- ----\r
+       perform @NAMESPACE@.dropPath_int(p_pa_server, p_pa_client);\r
+       return  @NAMESPACE@.createEvent (''_@CLUSTERNAME@'', ''DROP_PATH'',\r
+                       p_pa_server, p_pa_client);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION dropPath_int (pa_server, pa_client)\r
+--\r
+--     Process the DROP_NODE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.dropPath_int (int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_pa_server             alias for $1;\r
+       p_pa_client             alias for $2;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Remove any dangling sl_listen entries with the server\r
+       -- as provider and the client as receiver. This must have\r
+       -- been cleared out before, but obviously was not.\r
+       -- ----\r
+       delete from @NAMESPACE@.sl_listen\r
+                       where li_provider = p_pa_server\r
+                       and li_receiver = p_pa_client;\r
+\r
+       delete from @NAMESPACE@.sl_path\r
+                       where pa_server = p_pa_server\r
+                       and pa_client = p_pa_client;\r
+\r
+       if found then\r
+               return 1;\r
+       else\r
+               return 0;\r
+       end if;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION storeListen (li_origin, li_provider, li_receiver)\r
+--\r
+--     Generate the STORE_LISTEN event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.storeListen (int4, int4, int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_li_origin             alias for $1;\r
+       p_li_provider   alias for $2;\r
+       p_li_receiver   alias for $3;\r
+begin\r
+       perform @NAMESPACE@.storeListen_int (p_li_origin, p_li_provider, p_li_receiver);\r
+       return  @NAMESPACE@.createEvent (''_@CLUSTERNAME@'', ''STORE_LISTEN'',\r
+                       p_li_origin, p_li_provider, p_li_receiver);\r
+end;\r
+' language plpgsql\r
+       called on null input;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION storeListen_int (li_origin, li_provider, li_receiver)\r
+--\r
+--     Process the STORE_LISTEN event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.storeListen_int (int4, int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_li_origin             alias for $1;\r
+       p_li_provider   alias for $2;\r
+       p_li_receiver   alias for $3;\r
+       v_exists                int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       select 1 into v_exists\r
+                       from @NAMESPACE@.sl_listen\r
+                       where li_origin = p_li_origin\r
+                       and li_provider = p_li_provider\r
+                       and li_receiver = p_li_receiver;\r
+       if not found then\r
+               -- ----\r
+               -- In case we receive STORE_LISTEN events before we know\r
+               -- about the nodes involved in this, we generate those nodes\r
+               -- as pending.\r
+               -- ----\r
+               if not exists (select 1 from @NAMESPACE@.sl_node\r
+                                               where no_id = p_li_origin) then\r
+                       perform @NAMESPACE@.storeNode_int (p_li_origin, ''<event pending>'');\r
+               end if;\r
+               if not exists (select 1 from @NAMESPACE@.sl_node\r
+                                               where no_id = p_li_provider) then\r
+                       perform @NAMESPACE@.storeNode_int (p_li_provider, ''<event pending>'');\r
+               end if;\r
+               if not exists (select 1 from @NAMESPACE@.sl_node\r
+                                               where no_id = p_li_receiver) then\r
+                       perform @NAMESPACE@.storeNode_int (p_li_receiver, ''<event pending>'');\r
+               end if;\r
+\r
+               insert into @NAMESPACE@.sl_listen\r
+                               (li_origin, li_provider, li_receiver) values\r
+                               (p_li_origin, p_li_provider, p_li_receiver);\r
+       end if;\r
+\r
+       return 0;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION dropListen (li_origin, li_provider, li_receiver)\r
+--\r
+--     Generate the DROP_LISTEN event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.dropListen (int4, int4, int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_li_origin             alias for $1;\r
+       p_li_provider   alias for $2;\r
+       p_li_receiver   alias for $3;\r
+begin\r
+       perform @NAMESPACE@.dropListen_int(p_li_origin, \r
+                       p_li_provider, p_li_receiver);\r
+       \r
+       return  @NAMESPACE@.createEvent (''_@CLUSTERNAME@'', ''DROP_LISTEN'',\r
+                       p_li_origin, p_li_provider, p_li_receiver);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION dropListen_int (li_origin, li_provider, li_receiver)\r
+--\r
+--     Process the DROP_LISTEN event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.dropListen_int (int4, int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_li_origin             alias for $1;\r
+       p_li_provider   alias for $2;\r
+       p_li_receiver   alias for $3;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       delete from @NAMESPACE@.sl_listen\r
+                       where li_origin = p_li_origin\r
+                       and li_provider = p_li_provider\r
+                       and li_receiver = p_li_receiver;\r
+       if found then\r
+               return 1;\r
+       else\r
+               return 0;\r
+       end if;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION storeSet (set_id, set_comment)\r
+--\r
+--     Generate the STORE_SET event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.storeSet (int4, text)\r
+returns bigint\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_set_comment           alias for $2;\r
+       v_local_node_id         int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+\r
+       insert into @NAMESPACE@.sl_set\r
+                       (set_id, set_origin, set_comment) values\r
+                       (p_set_id, v_local_node_id, p_set_comment);\r
+\r
+       return @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''STORE_SET'', \r
+                       p_set_id, v_local_node_id, p_set_comment);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION storeSet_int (set_id, set_origin, set_comment)\r
+--\r
+--     Process the STORE_SET event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.storeSet_int (int4, int4, text)\r
+returns int4\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_set_origin            alias for $2;\r
+       p_set_comment           alias for $3;\r
+       v_dummy                         int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       select 1 into v_dummy\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id\r
+                       for update;\r
+       if found then \r
+               update @NAMESPACE@.sl_set\r
+                               set set_comment = p_set_comment\r
+                               where set_id = p_set_id;\r
+       else\r
+               if not exists (select 1 from @NAMESPACE@.sl_node\r
+                                               where no_id = p_set_origin) then\r
+                       perform @NAMESPACE@.storeNode_int (p_set_origin, ''<event pending>'');\r
+               end if;\r
+               insert into @NAMESPACE@.sl_set\r
+                               (set_id, set_origin, set_comment) values\r
+                               (p_set_id, p_set_origin, p_set_comment);\r
+       end if;\r
+\r
+       return p_set_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION lockSet (set_id)\r
+--\r
+--     Add a special trigger to all tables of a set that disables\r
+--     access to it.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.lockSet (int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       v_local_node_id         int4;\r
+       v_set_row                       record;\r
+       v_tab_row                       record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that the set exists and that we are the origin\r
+       -- and that it is not already locked.\r
+       -- ----\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+       select * into v_set_row from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id\r
+                       for update;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_set_id;\r
+       end if;\r
+       if v_set_row.set_origin <> v_local_node_id then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               p_set_id;\r
+       end if;\r
+       if v_set_row.set_locked notnull then\r
+               raise exception ''Slony-I: set % is already locked'', p_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Place the lockedSet trigger on all tables in the set.\r
+       -- ----\r
+       for v_tab_row in select T.tab_id,\r
+                       "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                       "pg_catalog".quote_ident(PGC.relname) as tab_fqname\r
+                       from @NAMESPACE@.sl_table T,\r
+                               "pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN\r
+                       where T.tab_set = p_set_id\r
+                               and T.tab_reloid = PGC.oid\r
+                               and PGC.relnamespace = PGN.oid\r
+                       order by tab_id\r
+       loop\r
+               execute ''create trigger "_@CLUSTERNAME@_lockedset_'' || \r
+                               v_tab_row.tab_id || \r
+                               ''" before insert or update or delete on '' ||\r
+                               v_tab_row.tab_fqname || '' for each row execute procedure\r
+                               @NAMESPACE@.lockedSet (''''_@CLUSTERNAME@'''');'';\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Remember our snapshots xmax as for the set locking\r
+       -- ----\r
+       update @NAMESPACE@.sl_set\r
+                       set set_locked = @NAMESPACE@.getMaxXid()\r
+                       where set_id = p_set_id;\r
+\r
+       return p_set_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION unlockSet (set_id)\r
+--\r
+--     Remove the special trigger from all tables of a set that disables\r
+--     access to it.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.unlockSet (int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       v_local_node_id         int4;\r
+       v_set_row                       record;\r
+       v_tab_row                       record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that the set exists and that we are the origin\r
+       -- and that it is not already locked.\r
+       -- ----\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+       select * into v_set_row from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id\r
+                       for update;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_set_id;\r
+       end if;\r
+       if v_set_row.set_origin <> v_local_node_id then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               p_set_id;\r
+       end if;\r
+       if v_set_row.set_locked isnull then\r
+               raise exception ''Slony-I: set % is not locked'', p_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Drop the lockedSet trigger from all tables in the set.\r
+       -- ----\r
+       for v_tab_row in select T.tab_id,\r
+                       "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                       "pg_catalog".quote_ident(PGC.relname) as tab_fqname\r
+                       from @NAMESPACE@.sl_table T,\r
+                               "pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN\r
+                       where T.tab_set = p_set_id\r
+                               and T.tab_reloid = PGC.oid\r
+                               and PGC.relnamespace = PGN.oid\r
+                       order by tab_id\r
+       loop\r
+               execute ''drop trigger "_@CLUSTERNAME@_lockedset_'' || \r
+                               v_tab_row.tab_id || ''" on '' || v_tab_row.tab_fqname;\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Clear out the set_locked field\r
+       -- ----\r
+       update @NAMESPACE@.sl_set\r
+                       set set_locked = NULL\r
+                       where set_id = p_set_id;\r
+\r
+       return p_set_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION moveSet (set_id, new_origin)\r
+--\r
+--     Generate the MOVE_SET event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.moveSet (int4, int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_new_origin            alias for $2;\r
+       v_local_node_id         int4;\r
+       v_set_row                       record;\r
+       v_sub_row                       record;\r
+       v_sync_seqno            int8;\r
+       v_lv_row                        record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that the set is locked and that this locking\r
+       -- happened long enough ago.\r
+       -- ----\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+       select * into v_set_row from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id\r
+                       for update;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_set_id;\r
+       end if;\r
+       if v_set_row.set_origin <> v_local_node_id then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               p_set_id;\r
+       end if;\r
+       if v_set_row.set_locked isnull then\r
+               raise exception ''Slony-I: set % is not locked'', p_set_id;\r
+       end if;\r
+       if v_set_row.set_locked > @NAMESPACE@.getMinXid() then\r
+               raise exception ''Slony-I: cannot move set % yet, transactions < % are still in progress'',\r
+                               p_set_id, v_set_row.set_locked;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Unlock the set\r
+       -- ----\r
+       perform @NAMESPACE@.unlockSet(p_set_id);\r
+\r
+       -- ----\r
+       -- Check that the new_origin is an active subscriber of the set\r
+       -- ----\r
+       select * into v_sub_row from @NAMESPACE@.sl_subscribe\r
+                       where sub_set = p_set_id\r
+                       and sub_receiver = p_new_origin;\r
+       if not found then\r
+               raise exception ''Slony-I: set % is not subscribed by node %'',\r
+                               p_set_id, p_new_origin;\r
+       end if;\r
+       if not v_sub_row.sub_active then\r
+               raise exception ''Slony-I: subsctiption of node % for set % is inactive'',\r
+                               p_new_origin, p_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Reconfigure everything\r
+       -- ----\r
+       perform @NAMESPACE@.moveSet_int(p_set_id, v_local_node_id,\r
+                       p_new_origin);\r
+\r
+       -- ----\r
+       -- At this time we hold access exclusive locks for every table\r
+       -- in the set. But we did move the set to the new origin, so the\r
+       -- createEvent() we are doing now will not record the sequences.\r
+       -- ----\r
+       v_sync_seqno := @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SYNC'');\r
+       insert into @NAMESPACE@.sl_seqlog \r
+                       (seql_seqid, seql_origin, seql_ev_seqno, seql_last_value)\r
+                       select seq_id, v_local_node_id, v_sync_seqno, seq_last_value\r
+                       from @NAMESPACE@.sl_seqlastvalue\r
+                       where seq_set = p_set_id;\r
+                                       \r
+       -- ----\r
+       -- Finally we generate the real event\r
+       -- ----\r
+       return @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''MOVE_SET'', \r
+                       p_set_id, v_local_node_id, p_new_origin);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION moveSet_int (set_id, old_origin, new_origin)\r
+--\r
+--     Process the MOVE_SET event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.moveSet_int (int4, int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_old_origin            alias for $2;\r
+       p_new_origin            alias for $3;\r
+       v_local_node_id         int4;\r
+       v_tab_row                       record;\r
+       v_sub_row                       record;\r
+       v_sub_node                      int4;\r
+       v_sub_last                      int4;\r
+       v_sub_next                      int4;\r
+       v_last_sync                     int8;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Get our local node ID\r
+       -- ----\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+\r
+       -- ----\r
+       -- If we are the old or new origin of the set, we need to\r
+       -- remove the log trigger from all tables first.\r
+       -- ----\r
+       if v_local_node_id = p_old_origin or v_local_node_id = p_new_origin then\r
+               for v_tab_row in select tab_id from @NAMESPACE@.sl_table\r
+                               where tab_set = p_set_id\r
+                               order by tab_id\r
+               loop\r
+                       perform @NAMESPACE@.alterTableRestore(v_tab_row.tab_id);\r
+               end loop;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Next we have to reverse the subscription path\r
+       -- ----\r
+       v_sub_last = p_new_origin;\r
+       select sub_provider into v_sub_node\r
+                       from @NAMESPACE@.sl_subscribe\r
+                       where sub_receiver = p_new_origin;\r
+       if not found then\r
+               raise exception ''Slony-I: subscription path broken in moveSet_int'';\r
+       end if;\r
+       while v_sub_node <> p_old_origin loop\r
+               -- ----\r
+               -- Tracing node by node, the old receiver is now in\r
+               -- v_sub_last and the old provider is in v_sub_node.\r
+               -- ----\r
+\r
+               -- ----\r
+               -- Get the current provider of this node as next\r
+               -- and change the provider to the previous one in\r
+               -- the reverse chain.\r
+               -- ----\r
+               select sub_provider into v_sub_next\r
+                               from @NAMESPACE@.sl_subscribe\r
+                               where sub_set = p_set_id\r
+                                       and sub_receiver = v_sub_node\r
+                               for update;\r
+               if not found then\r
+                       raise exception ''Slony-I: subscription path broken in moveSet_int'';\r
+               end if;\r
+               update @NAMESPACE@.sl_subscribe\r
+                               set sub_provider = v_sub_last\r
+                               where sub_set = p_set_id\r
+                                       and sub_receiver = v_sub_node;\r
+\r
+               v_sub_last = v_sub_node;\r
+               v_sub_node = v_sub_next;\r
+       end loop;\r
+\r
+       -- ----\r
+       -- This includes creating a subscription for the old origin\r
+       -- ----\r
+       insert into @NAMESPACE@.sl_subscribe\r
+                       (sub_set, sub_provider, sub_receiver,\r
+                       sub_forward, sub_active)\r
+                       values (p_set_id, v_sub_last, p_old_origin, true, true);\r
+       if v_local_node_id = p_old_origin then\r
+               select coalesce(max(ev_seqno), 0) into v_last_sync \r
+                               from @NAMESPACE@.sl_event\r
+                               where ev_origin = p_new_origin\r
+                                       and ev_type = ''SYNC'';\r
+               if v_last_sync > 0 then\r
+                       insert into @NAMESPACE@.sl_setsync\r
+                                       (ssy_setid, ssy_origin, ssy_seqno,\r
+                                       ssy_minxid, ssy_maxxid, ssy_xip, ssy_action_list)\r
+                                       select p_set_id, p_new_origin, v_last_sync,\r
+                                       ev_minxid, ev_maxxid, ev_xip, NULL\r
+                                       from @NAMESPACE@.sl_event\r
+                                       where ev_origin = p_new_origin\r
+                                               and ev_seqno = v_last_sync;\r
+               else\r
+                       insert into @NAMESPACE@.sl_setsync\r
+                                       (ssy_setid, ssy_origin, ssy_seqno,\r
+                                       ssy_minxid, ssy_maxxid, ssy_xip, ssy_action_list)\r
+                                       values (p_set_id, p_new_origin, ''0'',\r
+                                       ''0'', ''0'', '''', NULL);\r
+               end if;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Now change the ownership of the set.\r
+       -- ----\r
+       update @NAMESPACE@.sl_set\r
+                       set set_origin = p_new_origin\r
+                       where set_id = p_set_id;\r
+\r
+       -- ----\r
+       -- On the new origin, delete the obsolete setsync information\r
+       -- and the subscription.\r
+       -- ----\r
+       if v_local_node_id = p_new_origin then\r
+               delete from @NAMESPACE@.sl_setsync\r
+                               where ssy_setid = p_set_id;\r
+       else\r
+               if v_local_node_id <> p_old_origin then\r
+                       --\r
+                       -- On every other node, change the setsync so that it will\r
+                       -- pick up from the new origins last known sync.\r
+                       --\r
+                       delete from @NAMESPACE@.sl_setsync\r
+                                       where ssy_setid = p_set_id;\r
+                       select coalesce(max(ev_seqno), 0) into v_last_sync\r
+                                       from @NAMESPACE@.sl_event\r
+                                       where ev_origin = p_new_origin\r
+                                               and ev_type = ''SYNC'';\r
+                       if v_last_sync > 0 then\r
+                               insert into @NAMESPACE@.sl_setsync\r
+                                               (ssy_setid, ssy_origin, ssy_seqno,\r
+                                               ssy_minxid, ssy_maxxid, ssy_xip, ssy_action_list)\r
+                                               select p_set_id, p_new_origin, v_last_sync,\r
+                                               ev_minxid, ev_maxxid, ev_xip, NULL\r
+                                               from @NAMESPACE@.sl_event\r
+                                               where ev_origin = p_new_origin\r
+                                                       and ev_seqno = v_last_sync;\r
+                       else\r
+                               insert into @NAMESPACE@.sl_setsync\r
+                                               (ssy_setid, ssy_origin, ssy_seqno,\r
+                                               ssy_minxid, ssy_maxxid, ssy_xip, ssy_action_list)\r
+                                               values (p_set_id, p_new_origin, ''0'',\r
+                                               ''0'', ''0'', '''', NULL);\r
+                       end if;\r
+               end if;\r
+       end if;\r
+       delete from @NAMESPACE@.sl_subscribe\r
+                       where sub_set = p_set_id\r
+                       and sub_receiver = p_new_origin;\r
+\r
+       -- ----\r
+       -- If we are the new or old origin, we have to\r
+       -- put all the tables into altered state again.\r
+       -- ----\r
+       if v_local_node_id = p_old_origin or v_local_node_id = p_new_origin then\r
+               for v_tab_row in select tab_id from @NAMESPACE@.sl_table\r
+                               where tab_set = p_set_id\r
+                               order by tab_id\r
+               loop\r
+                       perform @NAMESPACE@.alterTableForReplication(v_tab_row.tab_id);\r
+               end loop;\r
+       end if;\r
+\r
+       return p_set_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION dropSet (set_id)\r
+--\r
+--     Generate the DROP_SET event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.dropSet (int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       v_origin                        int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+       \r
+       -- ----\r
+       -- Check that the set exists and originates here\r
+       -- ----\r
+       select set_origin into v_origin from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_set_id;\r
+       end if;\r
+       if v_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               p_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Call the internal drop set functionality and generate the event\r
+       -- ----\r
+       perform @NAMESPACE@.dropSet_int(p_set_id);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''DROP_SET'', \r
+                       p_set_id);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION dropSet_int (set_id)\r
+--\r
+--     Process the DROP_SET event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.dropSet_int (int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       v_tab_row                       record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+       \r
+       -- ----\r
+       -- Restore all tables original triggers and rules and remove\r
+       -- our replication stuff.\r
+       -- ----\r
+       for v_tab_row in select tab_id from @NAMESPACE@.sl_table\r
+                       where tab_set = p_set_id\r
+                       order by tab_id\r
+       loop\r
+               perform @NAMESPACE@.alterTableRestore(v_tab_row.tab_id);\r
+               perform @NAMESPACE@.tableDropKey(v_tab_row.tab_id);\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Remove all traces of the set configuration\r
+       -- ----\r
+       delete from @NAMESPACE@.sl_sequence\r
+                       where seq_set = p_set_id;\r
+       delete from @NAMESPACE@.sl_table\r
+                       where tab_set = p_set_id;\r
+       delete from @NAMESPACE@.sl_subscribe\r
+                       where sub_set = p_set_id;\r
+       delete from @NAMESPACE@.sl_setsync\r
+                       where ssy_setid = p_set_id;\r
+       delete from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id;\r
+\r
+       return p_set_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION mergeSet (set_id, add_id)\r
+--\r
+--     Generate the MERGE_SET event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.mergeSet (int4, int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_add_id                        alias for $2;\r
+       v_origin                        int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+       \r
+       -- ----\r
+       -- Check that both sets exist and originate here\r
+       -- ----\r
+       if p_set_id = p_add_id then\r
+               raise exception ''Slony-I: merged set ids cannot be identical'';\r
+       end if;\r
+       select set_origin into v_origin from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_set_id;\r
+       end if;\r
+       if v_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               p_set_id;\r
+       end if;\r
+\r
+       select set_origin into v_origin from @NAMESPACE@.sl_set\r
+                       where set_id = p_add_id;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_add_id;\r
+       end if;\r
+       if v_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               p_add_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Check that both sets are subscribed by the same set of nodes\r
+       -- ----\r
+       if exists (select true from @NAMESPACE@.sl_subscribe SUB1\r
+                               where SUB1.sub_set = p_set_id\r
+                               and SUB1.sub_receiver not in (select SUB2.sub_receiver\r
+                                               from @NAMESPACE@.sl_subscribe SUB2\r
+                                               where SUB2.sub_set = p_add_id))\r
+       then\r
+               raise exception ''Slony-I: subscriber lists of set % and % are different'',\r
+                               p_set_id, p_add_id;\r
+       end if;\r
+\r
+       if exists (select true from @NAMESPACE@.sl_subscribe SUB1\r
+                               where SUB1.sub_set = p_add_id\r
+                               and SUB1.sub_receiver not in (select SUB2.sub_receiver\r
+                                               from @NAMESPACE@.sl_subscribe SUB2\r
+                                               where SUB2.sub_set = p_set_id))\r
+       then\r
+               raise exception ''Slony-I: subscriber lists of set % and % are different'',\r
+                               p_add_id, p_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Also check that there are no unconfirmed enable subscriptions\r
+       -- still lingering (prevents bug 896)\r
+       -- ----\r
+       if exists (select true from @NAMESPACE@.sl_event\r
+                               where ev_origin = v_origin\r
+                               and ev_type = ''ENABLE_SUBSCRIPTION''\r
+                               and ev_data1 = p_set_id\r
+                               and ev_seqno > (select min(max_con_seqno) from\r
+                                       (select con_received, max(con_seqno) as max_con_seqno\r
+                                                       from @NAMESPACE@.sl_confirm\r
+                                                       where con_origin = v_origin\r
+                                                       group by con_received) as CON\r
+                                       )\r
+                               )\r
+       then\r
+               raise exception ''Slony-I: set % cannot be merged because of pending subscription'',\r
+                               p_set_id;\r
+       end if;\r
+\r
+       if exists (select true from @NAMESPACE@.sl_event\r
+                               where ev_origin = v_origin\r
+                               and ev_type = ''ENABLE_SUBSCRIPTION''\r
+                               and ev_data1 = p_add_id\r
+                               and ev_seqno > (select min(max_con_seqno) from\r
+                                       (select con_received, max(con_seqno) as max_con_seqno\r
+                                                       from @NAMESPACE@.sl_confirm\r
+                                                       where con_origin = v_origin\r
+                                                       group by con_received) as CON\r
+                                       )\r
+                               )\r
+       then\r
+               raise exception ''Slony-I: set % cannot be merged because of pending subscription'',\r
+                               p_add_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Create a SYNC event, merge the sets, create a MERGE_SET event\r
+       -- ----\r
+       perform @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SYNC'', NULL);\r
+       perform @NAMESPACE@.mergeSet_int(p_set_id, p_add_id);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''MERGE_SET'', \r
+                       p_set_id, p_add_id);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION mergeSet_int (set_id, add_id)\r
+--\r
+--     Process the MERGE_SET event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.mergeSet_int (int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_add_id                        alias for $2;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+       \r
+       update @NAMESPACE@.sl_sequence\r
+                       set seq_set = p_set_id\r
+                       where seq_set = p_add_id;\r
+       update @NAMESPACE@.sl_table\r
+                       set tab_set = p_set_id\r
+                       where tab_set = p_add_id;\r
+       delete from @NAMESPACE@.sl_subscribe\r
+                       where sub_set = p_add_id;\r
+       delete from @NAMESPACE@.sl_setsync\r
+                       where ssy_setid = p_add_id;\r
+       delete from @NAMESPACE@.sl_set\r
+                       where set_id = p_add_id;\r
+\r
+       return p_set_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setAddTable (set_id, tab_id, tab_fqname, tab_idxname,\r
+--                                     tab_comment)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setAddTable(int4, int4, text, name, text)\r
+returns bigint\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_tab_id                        alias for $2;\r
+       p_fqname                        alias for $3;\r
+       p_tab_idxname           alias for $4;\r
+       p_tab_comment           alias for $5;\r
+       v_set_origin            int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that we are the origin of the set\r
+       -- ----\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: setAddTable(): set % not found'', p_set_id;\r
+       end if;\r
+       if v_set_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: setAddTable(): set % has remote origin'', p_set_id;\r
+       end if;\r
+\r
+       if exists (select true from @NAMESPACE@.sl_subscribe\r
+                       where sub_set = p_set_id)\r
+       then\r
+               raise exception ''Slony-I: cannot add table to currently subscribed set %'',\r
+                               p_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Add the table to the set and generate the SET_ADD_TABLE event\r
+       -- ----\r
+       perform @NAMESPACE@.setAddTable_int(p_set_id, p_tab_id, p_fqname,\r
+                       p_tab_idxname, p_tab_comment);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SET_ADD_TABLE'',\r
+                       p_set_id, p_tab_id, p_fqname,\r
+                       p_tab_idxname, p_tab_comment);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setAddTable_int (set_id, tab_id, tab_fqname, tab_idxname,\r
+--                                             tab_comment)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setAddTable_int(int4, int4, text, name, text)\r
+returns int4\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_tab_id                        alias for $2;\r
+       p_fqname                        alias for $3;\r
+       p_tab_idxname           alias for $4;\r
+       p_tab_comment           alias for $5;\r
+       v_local_node_id         int4;\r
+       v_set_origin            int4;\r
+       v_sub_provider          int4;\r
+       v_relkind                       char;\r
+       v_tab_reloid            oid;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- For sets with a remote origin, check that we are subscribed \r
+       -- to that set. Otherwise we ignore the table because it might \r
+       -- not even exist in our database.\r
+       -- ----\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: setAddTable_int(): set % not found'',\r
+                               p_set_id;\r
+       end if;\r
+       if v_set_origin != v_local_node_id then\r
+               select sub_provider into v_sub_provider\r
+                               from @NAMESPACE@.sl_subscribe\r
+                               where sub_set = p_set_id\r
+                               and sub_receiver = @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+               if not found then\r
+                       return 0;\r
+               end if;\r
+       end if;\r
+       \r
+       -- ----\r
+       -- Get the tables OID and check that it is a real table\r
+       -- ----\r
+       select PGC.oid, PGC.relkind into v_tab_reloid, v_relkind\r
+                       from "pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN\r
+                       where PGC.relnamespace = PGN.oid\r
+                       and p_fqname = "pg_catalog".quote_ident(PGN.nspname) ||\r
+                                       ''.'' || "pg_catalog".quote_ident(PGC.relname);\r
+       if not found then\r
+               raise exception ''Slony-I: setAddTable(): table % not found'', \r
+                               p_fqname;\r
+       end if;\r
+       if v_relkind != ''r'' then\r
+               raise exception ''Slony-I: setAddTable(): % is not a regular table'',\r
+                               p_fqname;\r
+       end if;\r
+\r
+       if not exists (select indexrelid\r
+                       from "pg_catalog".pg_index PGX, "pg_catalog".pg_class PGC\r
+                       where PGX.indrelid = v_tab_reloid\r
+                               and PGX.indexrelid = PGC.oid\r
+                               and PGC.relname = p_tab_idxname)\r
+       then\r
+               raise exception ''Slony-I: setAddTable(): table % has no index %'',\r
+                               p_fqname, p_tab_idxname;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Add the table to sl_table and create the trigger on it.\r
+       -- ----\r
+       insert into @NAMESPACE@.sl_table\r
+                       (tab_id, tab_reloid, tab_set, tab_idxname, \r
+                       tab_altered, tab_comment) values\r
+                       (p_tab_id, v_tab_reloid, p_set_id, p_tab_idxname,\r
+                       false, p_tab_comment);\r
+       perform @NAMESPACE@.alterTableForReplication(p_tab_id);\r
+\r
+       return p_tab_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setDropTable (tab_id)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setDropTable(int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_tab_id                alias for $1;\r
+       v_set_id                int4;\r
+       v_set_origin            int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+        -- ----\r
+       -- Determine the set_id\r
+        -- ----\r
+       select tab_set into v_set_id from @NAMESPACE@.sl_table where tab_id = p_tab_id;\r
+\r
+       -- ----\r
+       -- Ensure table exists\r
+       -- ----\r
+       if not found then\r
+               raise exception ''Slony-I: setDropTable_int(): table % not found'',\r
+                       p_tab_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Check that we are the origin of the set\r
+       -- ----\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = v_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: setDropTable(): set % not found'', v_set_id;\r
+       end if;\r
+       if v_set_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: setDropTable(): set % has remote origin'', v_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Drop the table from the set and generate the SET_ADD_TABLE event\r
+       -- ----\r
+       perform @NAMESPACE@.setDropTable_int(p_tab_id);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SET_DROP_TABLE'', p_tab_id);\r
+end;\r
+' language plpgsql;\r
+comment on function @NAMESPACE@.setDropTable(int4) is\r
+'setDropTable (tab_id)\r
+\r
+Drop table tab_id from set on origin node, and generate SET_DROP_TABLE\r
+event to allow this to propagate to other nodes.';\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setDropTable_int (tab_id)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setDropTable_int(int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_tab_id                alias for $1;\r
+       v_set_id                int4;\r
+       v_local_node_id         int4;\r
+       v_set_origin            int4;\r
+       v_sub_provider          int4;\r
+       v_tab_reloid            oid;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+        -- ----\r
+       -- Determine the set_id\r
+        -- ----\r
+       select tab_set into v_set_id from @NAMESPACE@.sl_table where tab_id = p_tab_id;\r
+\r
+       -- ----\r
+       -- Ensure table exists\r
+       -- ----\r
+       if not found then\r
+               return 0;\r
+       end if;\r
+\r
+       -- ----\r
+       -- For sets with a remote origin, check that we are subscribed \r
+       -- to that set. Otherwise we ignore the table because it might \r
+       -- not even exist in our database.\r
+       -- ----\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = v_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: setDropTable_int(): set % not found'',\r
+                               v_set_id;\r
+       end if;\r
+       if v_set_origin != v_local_node_id then\r
+               select sub_provider into v_sub_provider\r
+                               from @NAMESPACE@.sl_subscribe\r
+                               where sub_set = v_set_id\r
+                               and sub_receiver = @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+               if not found then\r
+                       return 0;\r
+               end if;\r
+       end if;\r
+       \r
+       -- ----\r
+       -- Drop the table from sl_table and drop trigger from it.\r
+       -- ----\r
+       perform @NAMESPACE@.alterTableRestore(p_tab_id);\r
+       perform @NAMESPACE@.tableDropKey(p_tab_id);\r
+       delete from @NAMESPACE@.sl_table where tab_id = p_tab_id;\r
+       return p_tab_id;\r
+end;\r
+' language plpgsql;\r
+comment on function @NAMESPACE@.setDropTable_int(int4) is\r
+'setDropTable_int (tab_id)\r
+\r
+This function processes the SET_DROP_TABLE event on remote nodes,\r
+dropping a table from replication if the remote node is subscribing to\r
+its replication set.';\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setAddSequence (set_id, seq_id, seq_fqname, seq_comment)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setAddSequence (int4, int4, text, text)\r
+returns bigint\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_seq_id                        alias for $2;\r
+       p_fqname                        alias for $3;\r
+       p_seq_comment           alias for $4;\r
+       v_set_origin            int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that we are the origin of the set\r
+       -- ----\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: setAddSequence(): set % not found'', p_set_id;\r
+       end if;\r
+       if v_set_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: setAddSequence(): set % has remote origin'', p_set_id;\r
+       end if;\r
+\r
+       if exists (select true from @NAMESPACE@.sl_subscribe\r
+                       where sub_set = p_set_id)\r
+       then\r
+               raise exception ''Slony-I: cannot add sequence to currently subscribed set %'',\r
+                               p_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Add the sequence to the set and generate the SET_ADD_SEQUENCE event\r
+       -- ----\r
+       perform @NAMESPACE@.setAddSequence_int(p_set_id, p_seq_id, p_fqname,\r
+                       p_seq_comment);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SET_ADD_SEQUENCE'',\r
+                       p_set_id, p_seq_id, p_fqname, p_seq_comment);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setAddSequence_int (set_id, seq_id, seq_fqname, seq_comment\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setAddSequence_int(int4, int4, text, text)\r
+returns int4\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_seq_id                        alias for $2;\r
+       p_fqname                        alias for $3;\r
+       p_seq_comment           alias for $4;\r
+       v_local_node_id         int4;\r
+       v_set_origin            int4;\r
+       v_sub_provider          int4;\r
+       v_relkind                       char;\r
+       v_seq_reloid            oid;\r
+       v_sync_row                      record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- For sets with a remote origin, check that we are subscribed \r
+       -- to that set. Otherwise we ignore the sequence because it might \r
+       -- not even exist in our database.\r
+       -- ----\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: setAddSequence_int(): set % not found'',\r
+                               p_set_id;\r
+       end if;\r
+       if v_set_origin != v_local_node_id then\r
+               select sub_provider into v_sub_provider\r
+                               from @NAMESPACE@.sl_subscribe\r
+                               where sub_set = p_set_id\r
+                               and sub_receiver = @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+               if not found then\r
+                       return 0;\r
+               end if;\r
+       end if;\r
+       \r
+       -- ----\r
+       -- Get the sequences OID and check that it is a sequence\r
+       -- ----\r
+       select PGC.oid, PGC.relkind into v_seq_reloid, v_relkind\r
+                       from "pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN\r
+                       where PGC.relnamespace = PGN.oid\r
+                       and p_fqname = "pg_catalog".quote_ident(PGN.nspname) ||\r
+                                       ''.'' || "pg_catalog".quote_ident(PGC.relname);\r
+       if not found then\r
+               raise exception ''Slony-I: setAddSequence_int(): sequence % not found'', \r
+                               p_fqname;\r
+       end if;\r
+       if v_relkind != ''S'' then\r
+               raise exception ''Slony-I: setAddSequence_int(): % is not a sequence'',\r
+                               p_fqname;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Add the sequence to sl_sequence\r
+       -- ----\r
+       insert into @NAMESPACE@.sl_sequence\r
+                       (seq_id, seq_reloid, seq_set, seq_comment) values\r
+                       (p_seq_id, v_seq_reloid, p_set_id, p_seq_comment);\r
+\r
+       -- ----\r
+       -- On the set origin, fake a sl_seqlog row for the last sync event\r
+       -- ----\r
+       if v_set_origin = v_local_node_id then\r
+               for v_sync_row in select coalesce (max(ev_seqno), 0) as ev_seqno\r
+                               from @NAMESPACE@.sl_event\r
+                               where ev_origin = v_local_node_id\r
+                                       and ev_type = ''SYNC''\r
+               loop\r
+                       insert into @NAMESPACE@.sl_seqlog\r
+                                       (seql_seqid, seql_origin, seql_ev_seqno, \r
+                                       seql_last_value) values\r
+                                       (p_seq_id, v_local_node_id, v_sync_row.ev_seqno,\r
+                                       @NAMESPACE@.sequenceLastValue(p_fqname));\r
+               end loop;\r
+       end if;\r
+\r
+       return p_seq_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setDropSequence (seq_id)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setDropSequence (int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_seq_id                alias for $1;\r
+       v_set_id                int4;\r
+       v_set_origin            int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Determine set id for this sequence\r
+       -- ----\r
+       select seq_set into v_set_id from @NAMESPACE@.sl_sequence where seq_id = p_seq_id;\r
+\r
+       -- ----\r
+       -- Ensure sequence exists\r
+       -- ----\r
+       if not found then\r
+               raise exception ''Slony-I: setDropSequence_int(): sequence % not found'',\r
+                       p_seq_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Check that we are the origin of the set\r
+       -- ----\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = v_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: setDropSequence(): set % not found'', v_set_id;\r
+       end if;\r
+       if v_set_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: setDropSequence(): set % has remote origin'', v_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Add the sequence to the set and generate the SET_ADD_SEQUENCE event\r
+       -- ----\r
+       perform @NAMESPACE@.setDropSequence_int(p_seq_id);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SET_DROP_SEQUENCE'',\r
+                       p_seq_id);\r
+end;\r
+' language plpgsql;\r
+comment on function @NAMESPACE@.setDropSequence (int4) is\r
+'setDropSequence (seq_id)\r
+\r
+On the origin node for the set, drop sequence seq_id from replication\r
+set, and raise SET_DROP_SEQUENCE to cause this to replicate to\r
+subscriber nodes.';\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setDropSequence_int (seq_id)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setDropSequence_int(int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_seq_id                alias for $1;\r
+       v_set_id                int4;\r
+       v_local_node_id         int4;\r
+       v_set_origin            int4;\r
+       v_sub_provider          int4;\r
+       v_relkind                       char;\r
+       v_sync_row                      record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Determine set id for this sequence\r
+       -- ----\r
+       select seq_set into v_set_id from @NAMESPACE@.sl_sequence where seq_id = p_seq_id;\r
+\r
+       -- ----\r
+       -- Ensure sequence exists\r
+       -- ----\r
+       if not found then\r
+               return 0;\r
+       end if;\r
+\r
+       -- ----\r
+       -- For sets with a remote origin, check that we are subscribed \r
+       -- to that set. Otherwise we ignore the sequence because it might \r
+       -- not even exist in our database.\r
+       -- ----\r
+       v_local_node_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = v_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: setDropSequence_int(): set % not found'',\r
+                               v_set_id;\r
+       end if;\r
+       if v_set_origin != v_local_node_id then\r
+               select sub_provider into v_sub_provider\r
+                               from @NAMESPACE@.sl_subscribe\r
+                               where sub_set = v_set_id\r
+                               and sub_receiver = @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+               if not found then\r
+                       return 0;\r
+               end if;\r
+       end if;\r
+\r
+       -- ----\r
+       -- drop the sequence from sl_sequence, sl_seqlog\r
+       -- ----\r
+       delete from @NAMESPACE@.sl_seqlog where seql_seqid = p_seq_id;\r
+       delete from @NAMESPACE@.sl_sequence where seq_id = p_seq_id;\r
+\r
+       return p_seq_id;\r
+end;\r
+' language plpgsql;\r
+comment on function @NAMESPACE@.setDropSequence_int(int4) is\r
+'setDropSequence_int (seq_id)\r
+\r
+This processes the SET_DROP_SEQUENCE event.  On remote nodes that\r
+subscribe to the set containing sequence seq_id, drop the sequence\r
+from the replication set.';\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setMoveTable (tab_id, new_set_id)\r
+--\r
+--     Generate the SET_MOVE_TABLE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setMoveTable (int4, int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_tab_id                        alias for $1;\r
+       p_new_set_id            alias for $2;\r
+       v_old_set_id            int4;\r
+       v_origin                        int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Get the tables current set\r
+       -- ----\r
+       select tab_set into v_old_set_id from @NAMESPACE@.sl_table\r
+                       where tab_id = p_tab_id;\r
+       if not found then\r
+               raise exception ''Slony-I: table %d not found'', p_tab_id;\r
+       end if;\r
+       \r
+       -- ----\r
+       -- Check that both sets exist and originate here\r
+       -- ----\r
+       if p_new_set_id = v_old_set_id then\r
+               raise exception ''Slony-I: set ids cannot be identical'';\r
+       end if;\r
+       select set_origin into v_origin from @NAMESPACE@.sl_set\r
+                       where set_id = p_new_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_new_set_id;\r
+       end if;\r
+       if v_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               p_new_set_id;\r
+       end if;\r
+\r
+       select set_origin into v_origin from @NAMESPACE@.sl_set\r
+                       where set_id = v_old_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', v_old_set_id;\r
+       end if;\r
+       if v_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               v_old_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Check that both sets are subscribed by the same set of nodes\r
+       -- ----\r
+       if exists (select true from @NAMESPACE@.sl_subscribe SUB1\r
+                               where SUB1.sub_set = p_new_set_id\r
+                               and SUB1.sub_receiver not in (select SUB2.sub_receiver\r
+                                               from @NAMESPACE@.sl_subscribe SUB2\r
+                                               where SUB2.sub_set = v_old_set_id))\r
+       then\r
+               raise exception ''Slony-I: subscriber lists of set % and % are different'',\r
+                               p_new_set_id, v_old_set_id;\r
+       end if;\r
+\r
+       if exists (select true from @NAMESPACE@.sl_subscribe SUB1\r
+                               where SUB1.sub_set = v_old_set_id\r
+                               and SUB1.sub_receiver not in (select SUB2.sub_receiver\r
+                                               from @NAMESPACE@.sl_subscribe SUB2\r
+                                               where SUB2.sub_set = p_new_set_id))\r
+       then\r
+               raise exception ''Slony-I: subscriber lists of set % and % are different'',\r
+                               v_old_set_id, p_new_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Also check that there are no unconfirmed enable subscriptions\r
+       -- still lingering (prevents bug 896)\r
+       -- ----\r
+       if exists (select true from @NAMESPACE@.sl_event\r
+                               where ev_origin = v_origin\r
+                               and ev_type = ''ENABLE_SUBSCRIPTION''\r
+                               and ev_data1 = v_old_set_id\r
+                               and ev_seqno > (select min(max_con_seqno) from\r
+                                       (select con_received, max(con_seqno) as max_con_seqno\r
+                                                       from @NAMESPACE@.sl_confirm\r
+                                                       where con_origin = v_origin\r
+                                                       group by con_received) as CON\r
+                                       )\r
+                               )\r
+       then\r
+               raise exception ''Slony-I: table cannot be moved because of pending subscription'';\r
+       end if;\r
+\r
+       if exists (select true from @NAMESPACE@.sl_event\r
+                               where ev_origin = v_origin\r
+                               and ev_type = ''ENABLE_SUBSCRIPTION''\r
+                               and ev_data1 = p_new_set_id\r
+                               and ev_seqno > (select min(max_con_seqno) from\r
+                                       (select con_received, max(con_seqno) as max_con_seqno\r
+                                                       from @NAMESPACE@.sl_confirm\r
+                                                       where con_origin = v_origin\r
+                                                       group by con_received) as CON\r
+                                       )\r
+                               )\r
+       then\r
+               raise exception ''Slony-I: table cannot be moved because of pending subscription'';\r
+       end if;\r
+\r
+       -- ----\r
+       -- Change the set the table belongs to\r
+       -- ----\r
+       perform @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SYNC'', NULL);\r
+       perform @NAMESPACE@.setMoveTable_int(p_tab_id, p_new_set_id);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SET_MOVE_TABLE'', \r
+                       p_tab_id, p_new_set_id);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setMoveTable_int (tab_id, new_set_id)\r
+--\r
+--     Process the SET_MOVE_TABLE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setMoveTable_int (int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_tab_id                        alias for $1;\r
+       p_new_set_id            alias for $2;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+       \r
+       -- ----\r
+       -- Move the table to the new set\r
+       -- ----\r
+       update @NAMESPACE@.sl_table\r
+                       set tab_set = p_new_set_id\r
+                       where tab_id = p_tab_id;\r
+\r
+       return p_tab_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setMoveSequence (seq_id, new_set_id)\r
+--\r
+--     Generate the SET_MOVE_SEQUENCE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setMoveSequence (int4, int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_seq_id                        alias for $1;\r
+       p_new_set_id            alias for $2;\r
+       v_old_set_id            int4;\r
+       v_origin                        int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Get the sequences current set\r
+       -- ----\r
+       select seq_set into v_old_set_id from @NAMESPACE@.sl_sequence\r
+                       where seq_id = p_seq_id;\r
+       if not found then\r
+               raise exception ''Slony-I: sequence %d not found'', p_seq_id;\r
+       end if;\r
+       \r
+       -- ----\r
+       -- Check that both sets exist and originate here\r
+       -- ----\r
+       if p_new_set_id = v_old_set_id then\r
+               raise exception ''Slony-I: set ids cannot be identical'';\r
+       end if;\r
+       select set_origin into v_origin from @NAMESPACE@.sl_set\r
+                       where set_id = p_new_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_new_set_id;\r
+       end if;\r
+       if v_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               p_new_set_id;\r
+       end if;\r
+\r
+       select set_origin into v_origin from @NAMESPACE@.sl_set\r
+                       where set_id = v_old_set_id;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', v_old_set_id;\r
+       end if;\r
+       if v_origin != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               v_old_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Check that both sets are subscribed by the same set of nodes\r
+       -- ----\r
+       if exists (select true from @NAMESPACE@.sl_subscribe SUB1\r
+                               where SUB1.sub_set = p_new_set_id\r
+                               and SUB1.sub_receiver not in (select SUB2.sub_receiver\r
+                                               from @NAMESPACE@.sl_subscribe SUB2\r
+                                               where SUB2.sub_set = v_old_set_id))\r
+       then\r
+               raise exception ''Slony-I: subscriber lists of set % and % are different'',\r
+                               p_new_set_id, v_old_set_id;\r
+       end if;\r
+\r
+       if exists (select true from @NAMESPACE@.sl_subscribe SUB1\r
+                               where SUB1.sub_set = v_old_set_id\r
+                               and SUB1.sub_receiver not in (select SUB2.sub_receiver\r
+                                               from @NAMESPACE@.sl_subscribe SUB2\r
+                                               where SUB2.sub_set = p_new_set_id))\r
+       then\r
+               raise exception ''Slony-I: subscriber lists of set % and % are different'',\r
+                               v_old_set_id, p_new_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Also check that there are no unconfirmed enable subscriptions\r
+       -- still lingering (prevents bug 896)\r
+       -- ----\r
+       if exists (select true from @NAMESPACE@.sl_event\r
+                               where ev_origin = v_origin\r
+                               and ev_type = ''ENABLE_SUBSCRIPTION''\r
+                               and ev_data1 = v_old_set_id\r
+                               and ev_seqno > (select min(max_con_seqno) from\r
+                                       (select con_received, max(con_seqno) as max_con_seqno\r
+                                                       from @NAMESPACE@.sl_confirm\r
+                                                       where con_origin = v_origin\r
+                                                       group by con_received) as CON\r
+                                       )\r
+                               )\r
+       then\r
+               raise exception ''Slony-I: sequence cannot be moved because of pending subscription'';\r
+       end if;\r
+\r
+       if exists (select true from @NAMESPACE@.sl_event\r
+                               where ev_origin = v_origin\r
+                               and ev_type = ''ENABLE_SUBSCRIPTION''\r
+                               and ev_data1 = p_new_set_id\r
+                               and ev_seqno > (select min(max_con_seqno) from\r
+                                       (select con_received, max(con_seqno) as max_con_seqno\r
+                                                       from @NAMESPACE@.sl_confirm\r
+                                                       where con_origin = v_origin\r
+                                                       group by con_received) as CON\r
+                                       )\r
+                               )\r
+       then\r
+               raise exception ''Slony-I: sequence cannot be moved because of pending subscription'';\r
+       end if;\r
+\r
+       -- ----\r
+       -- Change the set the sequence belongs to\r
+       -- ----\r
+       perform @NAMESPACE@.setMoveSequence_int(p_seq_id, p_new_set_id);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SET_MOVE_SEQUENCE'', \r
+                       p_seq_id, p_new_set_id);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION setMoveSequence_int (seq_id, new_set_id)\r
+--\r
+--     Process the SET_MOVE_SEQUENCE event.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.setMoveSequence_int (int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_seq_id                        alias for $1;\r
+       p_new_set_id            alias for $2;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+       \r
+       -- ----\r
+       -- Move the sequence to the new set\r
+       -- ----\r
+       update @NAMESPACE@.sl_sequence\r
+                       set seq_set = p_new_set_id\r
+                       where seq_id = p_seq_id;\r
+\r
+       return p_seq_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION sequenceSetValue (seq_id, seq_origin, ev_seqno, last_value)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.sequenceSetValue(int4, int4, int8, int8) returns int4\r
+as '\r
+declare\r
+       p_seq_id                        alias for $1;\r
+       p_seq_origin            alias for $2;\r
+       p_ev_seqno                      alias for $3;\r
+       p_last_value            alias for $4;\r
+       v_fqname                        text;\r
+begin\r
+       -- ----\r
+       -- Get the sequences fully qualified name\r
+       -- ----\r
+       select "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                       "pg_catalog".quote_ident(PGC.relname) into v_fqname\r
+               from @NAMESPACE@.sl_sequence SQ,\r
+                       "pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN\r
+               where SQ.seq_id = p_seq_id\r
+                       and SQ.seq_reloid = PGC.oid\r
+                       and PGC.relnamespace = PGN.oid;\r
+       if not found then\r
+               raise exception ''Slony-I: sequence % not found'', p_seq_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Update it to the new value\r
+       -- ----\r
+       execute ''select setval('''''' || v_fqname ||\r
+                       '''''', '''''' || p_last_value || '''''')'';\r
+\r
+       insert into @NAMESPACE@.sl_seqlog\r
+                       (seql_seqid, seql_origin, seql_ev_seqno, seql_last_value)\r
+                       values (p_seq_id, p_seq_origin, p_ev_seqno, p_last_value);\r
+\r
+       return p_seq_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION storeTrigger (trig_tabid, trig_tgname)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.storeTrigger (int4, name)\r
+returns bigint\r
+as '\r
+declare\r
+       p_trig_tabid            alias for $1;\r
+       p_trig_tgname           alias for $2;\r
+begin\r
+       perform @NAMESPACE@.storeTrigger_int(p_trig_tabid, p_trig_tgname);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''STORE_TRIGGER'',\r
+                       p_trig_tabid, p_trig_tgname);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION storeTrigger_int (trig_tabid, trig_tgname)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.storeTrigger_int (int4, name)\r
+returns int4\r
+as '\r
+declare\r
+       p_trig_tabid            alias for $1;\r
+       p_trig_tgname           alias for $2;\r
+       v_tab_altered           boolean;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Get the current table status (altered or not)\r
+       -- ----\r
+       select tab_altered into v_tab_altered\r
+                       from @NAMESPACE@.sl_table where tab_id = p_trig_tabid;\r
+       if not found then\r
+               -- ----\r
+               -- Not found is no hard error here, because that might\r
+               -- mean that we are not subscribed to that set\r
+               -- ----\r
+               return 0;\r
+       end if;\r
+\r
+       -- ----\r
+       -- If the table is modified for replication, restore the original state\r
+       -- ----\r
+       if v_tab_altered then\r
+               perform @NAMESPACE@.alterTableRestore(p_trig_tabid);\r
+       end if;\r
+\r
+       -- ----\r
+       -- Make sure that an entry for this trigger exists\r
+       -- ----\r
+       delete from @NAMESPACE@.sl_trigger\r
+                       where trig_tabid = p_trig_tabid\r
+                         and trig_tgname = p_trig_tgname;\r
+       insert into @NAMESPACE@.sl_trigger (\r
+                               trig_tabid, trig_tgname\r
+                       ) values (\r
+                               p_trig_tabid, p_trig_tgname\r
+                       );\r
+\r
+       -- ----\r
+       -- Put the table back into replicated state if it was\r
+       -- ----\r
+       if v_tab_altered then\r
+               perform @NAMESPACE@.alterTableForReplication(p_trig_tabid);\r
+       end if;\r
+\r
+       return p_trig_tabid;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION dropTrigger (trig_tabid, trig_tgname)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.dropTrigger (int4, name)\r
+returns bigint\r
+as '\r
+declare\r
+       p_trig_tabid            alias for $1;\r
+       p_trig_tgname           alias for $2;\r
+begin\r
+       perform @NAMESPACE@.dropTrigger_int(p_trig_tabid, p_trig_tgname);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''DROP_TRIGGER'',\r
+                       p_trig_tabid, p_trig_tgname);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION dropTrigger_int (trig_tabid, trig_tgname)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.dropTrigger_int (int4, name)\r
+returns int4\r
+as '\r
+declare\r
+       p_trig_tabid            alias for $1;\r
+       p_trig_tgname           alias for $2;\r
+       v_tab_altered           boolean;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Get the current table status (altered or not)\r
+       -- ----\r
+       select tab_altered into v_tab_altered\r
+                       from @NAMESPACE@.sl_table where tab_id = p_trig_tabid;\r
+       if not found then\r
+               -- ----\r
+               -- Not found is no hard error here, because that might\r
+               -- mean that we are not subscribed to that set\r
+               -- ----\r
+               return 0;\r
+       end if;\r
+\r
+       -- ----\r
+       -- If the table is modified for replication, restore the original state\r
+       -- ----\r
+       if v_tab_altered then\r
+               perform @NAMESPACE@.alterTableRestore(p_trig_tabid);\r
+       end if;\r
+\r
+       -- ----\r
+       -- Remove the entry from sl_trigger\r
+       -- ----\r
+       delete from @NAMESPACE@.sl_trigger\r
+                       where trig_tabid = p_trig_tabid\r
+                         and trig_tgname = p_trig_tgname;\r
+\r
+       -- ----\r
+       -- Put the table back into replicated state if it was\r
+       -- ----\r
+       if v_tab_altered then\r
+               perform @NAMESPACE@.alterTableForReplication(p_trig_tabid);\r
+       end if;\r
+\r
+       return p_trig_tabid;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION ddlScript (set_id, script)\r
+--\r
+--     Generate the DDL_SCRIPT event\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.ddlScript (int4, text)\r
+returns bigint\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_script                        alias for $2;\r
+       v_set_origin            int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that the set exists and originates here\r
+       -- ----\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id\r
+                       for update;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_set_id;\r
+       end if;\r
+       if v_set_origin <> @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: set % does not originate on local node'',\r
+                               p_set_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Create a SYNC event, run the script and generate the DDL_SCRIPT event\r
+       -- ----\r
+       perform @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SYNC'', NULL);\r
+       perform @NAMESPACE@.ddlScript_int(p_set_id, p_script);\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''DDL_SCRIPT'', \r
+                       p_set_id, p_script);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION ddlScript_int (set_id, script)\r
+--\r
+--     Process the DDL_SCRIPT event\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.ddlScript_int (int4, text)\r
+returns int4\r
+as '\r
+declare\r
+       p_set_id                        alias for $1;\r
+       p_script                        alias for $2;\r
+       v_set_origin            int4;\r
+       v_no_id                         int4;\r
+       v_row                           record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that we either are the set origin or a current\r
+       -- subscriber of the set.\r
+       -- ----\r
+       v_no_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = p_set_id\r
+                       for update;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_set_id;\r
+       end if;\r
+       if v_set_origin <> v_no_id\r
+                       and not exists (select 1 from @NAMESPACE@.sl_subscribe\r
+                                               where sub_set = p_set_id\r
+                                               and sub_receiver = v_no_id)\r
+       then\r
+               return 0;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Restore all original triggers and rules\r
+       -- ----\r
+       for v_row in select * from @NAMESPACE@.sl_table\r
+                       where tab_set = p_set_id\r
+       loop\r
+               perform @NAMESPACE@.alterTableRestore(v_row.tab_id);\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Run the script\r
+       -- ----\r
+       execute p_script;\r
+\r
+       -- ----\r
+       -- Put all tables back into replicated mode\r
+       -- ----\r
+       for v_row in select * from @NAMESPACE@.sl_table\r
+                       where tab_set = p_set_id\r
+       loop\r
+               perform @NAMESPACE@.alterTableForReplication(v_row.tab_id);\r
+       end loop;\r
+\r
+       return p_set_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION alterTableForReplication (tab_id)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.alterTableForReplication (int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_tab_id                        alias for $1;\r
+       v_no_id                         int4;\r
+       v_tab_row                       record;\r
+       v_tab_fqname            text;\r
+       v_tab_attkind           text;\r
+       v_n                                     int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Get our local node ID\r
+       -- ----\r
+       v_no_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+\r
+       -- ----\r
+       -- Get the sl_table row and the current tables origin. Check\r
+       -- that the table currently is NOT in altered state.\r
+       -- ----\r
+       select T.tab_reloid, T.tab_set, T.tab_idxname, T.tab_altered,\r
+                       S.set_origin, PGX.indexrelid,\r
+                       "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                       "pg_catalog".quote_ident(PGC.relname) as tab_fqname\r
+                       into v_tab_row\r
+                       from @NAMESPACE@.sl_table T, @NAMESPACE@.sl_set S,\r
+                               "pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN,\r
+                               "pg_catalog".pg_index PGX, "pg_catalog".pg_class PGXC\r
+                       where T.tab_id = p_tab_id\r
+                               and T.tab_set = S.set_id\r
+                               and T.tab_reloid = PGC.oid\r
+                               and PGC.relnamespace = PGN.oid\r
+                               and PGX.indrelid = T.tab_reloid\r
+                               and PGX.indexrelid = PGXC.oid\r
+                               and PGXC.relname = T.tab_idxname\r
+                               for update;\r
+       if not found then\r
+               raise exception ''Slony-I: Table with id % not found'', p_tab_id;\r
+       end if;\r
+       v_tab_fqname = v_tab_row.tab_fqname;\r
+       if v_tab_row.tab_altered then\r
+               raise exception ''Slony-I: Table % is already in altered state'',\r
+                               v_tab_fqname;\r
+       end if;\r
+\r
+       v_tab_attkind := @NAMESPACE@.determineAttKindUnique(v_tab_row.tab_fqname, \r
+                                               v_tab_row.tab_idxname);\r
+\r
+       execute ''lock table '' || v_tab_fqname || '' in access exclusive mode'';\r
+\r
+       -- ----\r
+       -- Procedures are different on origin and subscriber\r
+       -- ----\r
+       if v_no_id = v_tab_row.set_origin then\r
+               -- ----\r
+               -- On the Origin we add the log trigger to the table and done\r
+               -- ----\r
+               execute ''create trigger "_@CLUSTERNAME@_logtrigger_'' || \r
+                               p_tab_id || ''" after insert or update or delete on '' ||\r
+                               v_tab_fqname || '' for each row execute procedure\r
+                               @NAMESPACE@.logTrigger (''''_@CLUSTERNAME@'''', '''''' || \r
+                                       p_tab_id || '''''', '''''' || \r
+                                       v_tab_attkind || '''''');'';\r
+       else\r
+               -- ----\r
+               -- On the subscriber the thing is a bit more difficult. We want\r
+               -- to disable all user- and foreign key triggers and rules.\r
+               -- ----\r
+\r
+\r
+               -- ----\r
+               -- Disable all existing triggers\r
+               -- ----\r
+               update "pg_catalog".pg_trigger\r
+                               set tgrelid = v_tab_row.indexrelid\r
+                               where tgrelid = v_tab_row.tab_reloid\r
+                               and not exists (\r
+                                               select true from @NAMESPACE@.sl_table TAB,\r
+                                                               @NAMESPACE@.sl_trigger TRIG\r
+                                                               where TAB.tab_reloid = tgrelid\r
+                                                               and TAB.tab_id = TRIG.trig_tabid\r
+                                                               and TRIG.trig_tgname = tgname\r
+                                       );\r
+               get diagnostics v_n = row_count;\r
+               if v_n > 0 then\r
+                       update "pg_catalog".pg_class\r
+                                       set reltriggers = reltriggers - v_n\r
+                                       where oid = v_tab_row.tab_reloid;\r
+               end if;\r
+\r
+               -- ----\r
+               -- Disable all existing rules\r
+               -- ----\r
+               update "pg_catalog".pg_rewrite\r
+                               set ev_class = v_tab_row.indexrelid\r
+                               where ev_class = v_tab_row.tab_reloid;\r
+               get diagnostics v_n = row_count;\r
+               if v_n > 0 then\r
+                       update "pg_catalog".pg_class\r
+                                       set relhasrules = false\r
+                                       where oid = v_tab_row.tab_reloid;\r
+               end if;\r
+\r
+               -- ----\r
+               -- Add the trigger that denies write access to replicated tables\r
+               -- ----\r
+               execute ''create trigger "_@CLUSTERNAME@_denyaccess_'' || \r
+                               p_tab_id || ''" before insert or update or delete on '' ||\r
+                               v_tab_fqname || '' for each row execute procedure\r
+                               @NAMESPACE@.denyAccess (''''_@CLUSTERNAME@'''');'';\r
+       end if;\r
+\r
+       -- ----\r
+       -- Mark the table altered in our configuration\r
+       -- ----\r
+       update @NAMESPACE@.sl_table\r
+                       set tab_altered = true where tab_id = p_tab_id;\r
+\r
+       return p_tab_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION alterTableRestore (tab_id)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.alterTableRestore (int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_tab_id                        alias for $1;\r
+       v_no_id                         int4;\r
+       v_tab_row                       record;\r
+       v_tab_fqname            text;\r
+       v_n                                     int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Get our local node ID\r
+       -- ----\r
+       v_no_id := @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'');\r
+\r
+       -- ----\r
+       -- Get the sl_table row and the current tables origin. Check\r
+       -- that the table currently IS in altered state.\r
+       -- ----\r
+       select T.tab_reloid, T.tab_set, T.tab_altered,\r
+                       S.set_origin, PGX.indexrelid,\r
+                       "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                       "pg_catalog".quote_ident(PGC.relname) as tab_fqname\r
+                       into v_tab_row\r
+                       from @NAMESPACE@.sl_table T, @NAMESPACE@.sl_set S,\r
+                               "pg_catalog".pg_class PGC, "pg_catalog".pg_namespace PGN,\r
+                               "pg_catalog".pg_index PGX, "pg_catalog".pg_class PGXC\r
+                       where T.tab_id = p_tab_id\r
+                               and T.tab_set = S.set_id\r
+                               and T.tab_reloid = PGC.oid\r
+                               and PGC.relnamespace = PGN.oid\r
+                               and PGX.indrelid = T.tab_reloid\r
+                               and PGX.indexrelid = PGXC.oid\r
+                               and PGXC.relname = T.tab_idxname\r
+                               for update;\r
+       if not found then\r
+               raise exception ''Slony-I: Table with id % not found'', p_tab_id;\r
+       end if;\r
+       v_tab_fqname = v_tab_row.tab_fqname;\r
+       if not v_tab_row.tab_altered then\r
+               raise exception ''Slony-I: Table % is not in altered state'',\r
+                               v_tab_fqname;\r
+       end if;\r
+\r
+       execute ''lock table '' || v_tab_fqname || '' in access exclusive mode'';\r
+\r
+       -- ----\r
+       -- Procedures are different on origin and subscriber\r
+       -- ----\r
+       if v_no_id = v_tab_row.set_origin then\r
+               -- ----\r
+               -- On the Origin we just drop the trigger we originally added\r
+               -- ----\r
+               execute ''drop trigger "_@CLUSTERNAME@_logtrigger_'' || \r
+                               p_tab_id || ''" on '' || v_tab_fqname;\r
+       else\r
+               -- ----\r
+               -- On the subscriber drop the denyAccess trigger\r
+               -- ----\r
+               execute ''drop trigger "_@CLUSTERNAME@_denyaccess_'' || \r
+                               p_tab_id || ''" on '' || v_tab_fqname;\r
+                               \r
+               -- ----\r
+               -- Restore all original triggers\r
+               -- ----\r
+               update "pg_catalog".pg_trigger\r
+                               set tgrelid = v_tab_row.tab_reloid\r
+                               where tgrelid = v_tab_row.indexrelid;\r
+               get diagnostics v_n = row_count;\r
+               if v_n > 0 then\r
+                       update "pg_catalog".pg_class\r
+                                       set reltriggers = reltriggers + v_n\r
+                                       where oid = v_tab_row.tab_reloid;\r
+               end if;\r
+\r
+               -- ----\r
+               -- Restore all original rewrite rules\r
+               -- ----\r
+               update "pg_catalog".pg_rewrite\r
+                               set ev_class = v_tab_row.tab_reloid\r
+                               where ev_class = v_tab_row.indexrelid;\r
+               get diagnostics v_n = row_count;\r
+               if v_n > 0 then\r
+                       update "pg_catalog".pg_class\r
+                                       set relhasrules = true\r
+                                       where oid = v_tab_row.tab_reloid;\r
+               end if;\r
+\r
+       end if;\r
+\r
+       -- ----\r
+       -- Mark the table not altered in our configuration\r
+       -- ----\r
+       update @NAMESPACE@.sl_table\r
+                       set tab_altered = false where tab_id = p_tab_id;\r
+\r
+       return p_tab_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION subscribeSet (sub_set, sub_provider, sub_receiver, sub_forward)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.subscribeSet (int4, int4, int4, bool)\r
+returns bigint\r
+as '\r
+declare\r
+       p_sub_set                       alias for $1;\r
+       p_sub_provider          alias for $2;\r
+       p_sub_receiver          alias for $3;\r
+       p_sub_forward           alias for $4;\r
+       v_set_origin            int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that this is called on the receiver node\r
+       -- ----\r
+       if p_sub_receiver != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: subscribeSet() must be called on receiver'';\r
+       end if;\r
+\r
+       -- ----\r
+       -- Check that the origin and provider of the set are remote\r
+       -- ----\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = p_sub_set;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_sub_set;\r
+       end if;\r
+       if v_set_origin = p_sub_receiver then\r
+               raise exception \r
+                               ''Slony-I: set origin and receiver cannot be identical'';\r
+       end if;\r
+       if p_sub_receiver = p_sub_provider then\r
+               raise exception \r
+                               ''Slony-I: set provider and receiver cannot be identical'';\r
+       end if;\r
+\r
+       -- ----\r
+       -- Call the internal procedure to store the subscription\r
+       -- ----\r
+       perform @NAMESPACE@.subscribeSet_int(p_sub_set, p_sub_provider,\r
+                       p_sub_receiver, p_sub_forward);\r
+\r
+       -- ----\r
+       -- Create the SUBSCRIBE_SET event\r
+       -- ----\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''SUBSCRIBE_SET'', \r
+                       p_sub_set, p_sub_provider, p_sub_receiver, \r
+                       case p_sub_forward when true then ''t'' else ''f'' end);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION subscribeSet_int (sub_set, sub_provider, sub_receiver, sub_forward)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.subscribeSet_int (int4, int4, int4, bool)\r
+returns int4\r
+as '\r
+declare\r
+       p_sub_set                       alias for $1;\r
+       p_sub_provider          alias for $2;\r
+       p_sub_receiver          alias for $3;\r
+       p_sub_forward           alias for $4;\r
+       v_set_origin            int4;\r
+       v_sub_row                       record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Provider change is only allowed for active sets\r
+       -- ----\r
+       if p_sub_receiver = @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               select sub_active into v_sub_row from @NAMESPACE@.sl_subscribe\r
+                               where sub_set = p_sub_set\r
+                               and sub_receiver = p_sub_receiver;\r
+               if found then\r
+                       if not v_sub_row.sub_active then\r
+                               raise exception ''Slony-I: set % is not active, cannot change provider'',\r
+                                               p_sub_set;\r
+                       end if;\r
+               end if;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Try to change provider and/or forward for an existing subscription\r
+       -- ----\r
+       update @NAMESPACE@.sl_subscribe\r
+                       set sub_provider = p_sub_provider,\r
+                               sub_forward = p_sub_forward\r
+                       where sub_set = p_sub_set\r
+                       and sub_receiver = p_sub_receiver;\r
+       if found then\r
+               return p_sub_set;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Not found, insert a new one\r
+       -- ----\r
+       if not exists (select true from @NAMESPACE@.sl_path\r
+                       where pa_server = p_sub_provider\r
+                       and pa_client = p_sub_receiver)\r
+       then\r
+               insert into @NAMESPACE@.sl_path\r
+                               (pa_server, pa_client, pa_conninfo, pa_connretry)\r
+                               values \r
+                               (p_sub_provider, p_sub_receiver, \r
+                               ''<event pending>'', 10);\r
+       end if;\r
+       insert into @NAMESPACE@.sl_subscribe\r
+                       (sub_set, sub_provider, sub_receiver, sub_forward, sub_active)\r
+                       values (p_sub_set, p_sub_provider, p_sub_receiver,\r
+                               p_sub_forward, false);\r
+\r
+       -- ----\r
+       -- If the set origin is here, then enable the subscription\r
+       -- ----\r
+       select set_origin into v_set_origin\r
+                       from @NAMESPACE@.sl_set\r
+                       where set_id = p_sub_set;\r
+       if not found then\r
+               raise exception ''Slony-I: set % not found'', p_sub_set;\r
+       end if;\r
+\r
+       if v_set_origin = @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               perform @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''ENABLE_SUBSCRIPTION'', \r
+                               p_sub_set, p_sub_provider, p_sub_receiver, \r
+                               case p_sub_forward when true then ''t'' else ''f'' end);\r
+               perform @NAMESPACE@.enableSubscription(p_sub_set, \r
+                               p_sub_provider, p_sub_receiver);\r
+       end if;\r
+\r
+       return p_sub_set;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION unsubscribeSet (sub_set, sub_receiver)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.unsubscribeSet (int4, int4)\r
+returns bigint\r
+as '\r
+declare\r
+       p_sub_set                       alias for $1;\r
+       p_sub_receiver          alias for $2;\r
+       v_tab_row                       record;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Check that this is called on the receiver node\r
+       -- ----\r
+       if p_sub_receiver != @NAMESPACE@.getLocalNodeId(''_@CLUSTERNAME@'') then\r
+               raise exception ''Slony-I: unsubscribeSet() must be called on receiver'';\r
+       end if;\r
+\r
+       -- ----\r
+       -- Check that this does not break any chains\r
+       -- ----\r
+       if exists (select true from @NAMESPACE@.sl_subscribe\r
+                       where sub_set = p_sub_set\r
+                               and sub_provider = p_sub_receiver)\r
+       then\r
+               raise exception ''Slony-I: Cannot unsubscibe set % while being provider'',\r
+                               p_sub_set;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Restore all tables original triggers and rules and remove\r
+       -- our replication stuff.\r
+       -- ----\r
+       for v_tab_row in select tab_id from @NAMESPACE@.sl_table\r
+                       where tab_set = p_sub_set\r
+                       order by tab_id\r
+       loop\r
+               perform @NAMESPACE@.alterTableRestore(v_tab_row.tab_id);\r
+               perform @NAMESPACE@.tableDropKey(v_tab_row.tab_id);\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Remove the setsync status. This will also cause the\r
+       -- worker thread to ignore the set and stop replicating\r
+       -- right now.\r
+       -- ----\r
+       delete from @NAMESPACE@.sl_setsync\r
+                       where ssy_setid = p_sub_set;\r
+\r
+       -- ----\r
+       -- Remove all sl_table and sl_sequence entries for this set.\r
+       -- Should we ever subscribe again, the initial data\r
+       -- copy process will create new ones.\r
+       -- ----\r
+       delete from @NAMESPACE@.sl_table\r
+                       where tab_set = p_sub_set;\r
+       delete from @NAMESPACE@.sl_sequence\r
+                       where seq_set = p_sub_set;\r
+\r
+       -- ----\r
+       -- Call the internal procedure to drop the subscription\r
+       -- ----\r
+       perform @NAMESPACE@.unsubscribeSet_int(p_sub_set, p_sub_receiver);\r
+\r
+       -- ----\r
+       -- Create the UNSUBSCRIBE_SET event\r
+       -- ----\r
+       return  @NAMESPACE@.createEvent(''_@CLUSTERNAME@'', ''UNSUBSCRIBE_SET'', \r
+                       p_sub_set, p_sub_receiver);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION unsubscribeSet_int (sub_set, sub_receiver)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.unsubscribeSet_int (int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_sub_set                       alias for $1;\r
+       p_sub_receiver          alias for $2;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- All the real work is done before event generation on the\r
+       -- subscriber.\r
+       -- ----\r
+       delete from @NAMESPACE@.sl_subscribe\r
+                       where sub_set = p_sub_set\r
+                               and sub_receiver = p_sub_receiver;\r
+\r
+       return p_sub_set;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION enableSubscription (sub_set, sub_provider, sub_receiver)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.enableSubscription (int4, int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_sub_set                       alias for $1;\r
+       p_sub_provider          alias for $2;\r
+       p_sub_receiver          alias for $3;\r
+begin\r
+       return  @NAMESPACE@.enableSubscription_int (p_sub_set, \r
+                       p_sub_provider, p_sub_receiver);\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION enableSubscription_int (sub_set, sub_provider, sub_receiver)\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.enableSubscription_int (int4, int4, int4)\r
+returns int4\r
+as '\r
+declare\r
+       p_sub_set                       alias for $1;\r
+       p_sub_provider          alias for $2;\r
+       p_sub_receiver          alias for $3;\r
+       v_n                                     int4;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- The real work is done in the replication engine. All\r
+       -- we have to do here is remembering that it happened.\r
+       -- ----\r
+       update @NAMESPACE@.sl_subscribe\r
+                       set sub_active = ''t''\r
+                       where sub_set = p_sub_set\r
+                       and sub_receiver = p_sub_receiver;\r
+       get diagnostics v_n = row_count;\r
+       if v_n = 0 then\r
+               insert into @NAMESPACE@.sl_subscribe\r
+                               (sub_set, sub_provider, sub_receiver,\r
+                               sub_forward, sub_active)\r
+                               values\r
+                               (p_sub_set, p_sub_provider, p_sub_receiver,\r
+                               false, true);\r
+       end if;\r
+\r
+       return p_sub_set;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION forwardConfirm ()\r
+--\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.forwardConfirm (int4, int4, int8, timestamp)\r
+returns bigint\r
+as '\r
+declare\r
+       p_con_origin    alias for $1;\r
+       p_con_received  alias for $2;\r
+       p_con_seqno             alias for $3;\r
+       p_con_timestamp alias for $4;\r
+       v_max_seqno             bigint;\r
+begin\r
+       select into v_max_seqno coalesce(max(con_seqno), 0)\r
+                       from @NAMESPACE@.sl_confirm\r
+                       where con_origin = p_con_origin\r
+                       and con_received = p_con_received;\r
+       if v_max_seqno < p_con_seqno then\r
+               insert into @NAMESPACE@.sl_confirm \r
+                               (con_origin, con_received, con_seqno, con_timestamp)\r
+                               values (p_con_origin, p_con_received, p_con_seqno,\r
+                                       p_con_timestamp);\r
+               notify "_@CLUSTERNAME@_Confirm";\r
+               v_max_seqno = p_con_seqno;\r
+       end if;\r
+\r
+       return v_max_seqno;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION cleanupEvent ()\r
+--\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.cleanupEvent ()\r
+returns int4\r
+as '\r
+declare\r
+       v_max_row       record;\r
+       v_min_row       record;\r
+       v_max_sync      int8;\r
+begin\r
+       -- ----\r
+       -- First remove all but the oldest confirm row per origin,receiver pair\r
+       -- ----\r
+       delete from @NAMESPACE@.sl_confirm\r
+                               where con_origin not in (select no_id from @NAMESPACE@.sl_node);\r
+       delete from @NAMESPACE@.sl_confirm\r
+                               where con_received not in (select no_id from @NAMESPACE@.sl_node);\r
+       -- ----\r
+       -- Next remove all but the oldest confirm row per origin,receiver pair.\r
+       -- Ignore confirmations that are younger than 10 minutes. We currently\r
+       -- have an not confirmed suspicion that a possibly lost transaction due\r
+       -- to a server crash might have been visible to another session, and\r
+       -- that this led to log data that is needed again got removed.\r
+       -- ----\r
+       for v_max_row in select con_origin, con_received, max(con_seqno) as con_seqno\r
+                               from @NAMESPACE@.sl_confirm\r
+                               where con_timestamp < (CURRENT_TIMESTAMP - ''10 min''::interval)\r
+                               group by con_origin, con_received\r
+       loop\r
+               delete from @NAMESPACE@.sl_confirm\r
+                               where con_origin = v_max_row.con_origin\r
+                               and con_received = v_max_row.con_received\r
+                               and con_seqno < v_max_row.con_seqno;\r
+       end loop;\r
+\r
+       -- ----\r
+       -- Then remove all events that are confirmed by all nodes in the\r
+       -- whole cluster up to the last SYNC\r
+       -- ----\r
+       for v_min_row in select con_origin, min(con_seqno) as con_seqno\r
+                               from @NAMESPACE@.sl_confirm\r
+                               group by con_origin\r
+       loop\r
+               select coalesce(max(ev_seqno), 0) into v_max_sync\r
+                               from @NAMESPACE@.sl_event\r
+                               where ev_origin = v_min_row.con_origin\r
+                               and ev_seqno <= v_min_row.con_seqno\r
+                               and ev_type = ''SYNC'';\r
+               if v_max_sync > 0 then\r
+                       delete from @NAMESPACE@.sl_event\r
+                                       where ev_origin = v_min_row.con_origin\r
+                                       and ev_seqno < v_max_sync;\r
+               end if;\r
+       end loop;\r
+\r
+       return 0;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION tableAddKey (tab_fqname)\r
+--\r
+--     If the specified table does not have a column \r
+--     "_Slony-I_<clustername>_rowID", then add it as a bigint\r
+--     with default nextval('"_<clustername>".sl_rowid_seq').\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.tableAddKey(text) returns text\r
+as '\r
+declare\r
+       p_tab_fqname    alias for $1;\r
+       v_attkind               text default '''';\r
+       v_attrow                record;\r
+       v_have_serial   bool default ''f'';\r
+begin\r
+       --\r
+       -- Loop over the attributes of this relation\r
+       -- and add a "v" for every user column, and a "k"\r
+       -- if we find the Slony-I special serial column.\r
+       --\r
+       for v_attrow in select PGA.attnum, PGA.attname\r
+                       from "pg_catalog".pg_class PGC,\r
+                           "pg_catalog".pg_namespace PGN,\r
+                               "pg_catalog".pg_attribute PGA\r
+                       where "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                           "pg_catalog".quote_ident(PGC.relname) = p_tab_fqname\r
+                               and PGN.oid = PGC.relnamespace\r
+                               and PGA.attrelid = PGC.oid\r
+                               and not PGA.attisdropped\r
+                               and PGA.attnum > 0\r
+                       order by attnum\r
+       loop\r
+               if v_attrow.attname = ''_Slony-I_@CLUSTERNAME@_rowID'' then\r
+                   v_attkind := v_attkind || ''k'';\r
+                       v_have_serial := ''t'';\r
+               else\r
+                       v_attkind := v_attkind || ''v'';\r
+               end if;\r
+       end loop;\r
+       \r
+       --\r
+       -- A table must have at least one attribute, so not finding\r
+       -- anything means the table does not exist.\r
+       --\r
+       if not found then\r
+               raise exception ''Slony-I: table % not found'', p_tab_fqname;\r
+       end if;\r
+\r
+       --\r
+       -- If it does not have the special serial column, we\r
+       -- have to add it. This will be only half way done.\r
+       -- The function to add the table to the set must finish\r
+       -- these definitions with NOT NULL and UNIQUE after\r
+       -- updating all existing rows.\r
+       --\r
+       if not v_have_serial then\r
+               execute ''lock table '' || p_tab_fqname ||\r
+                       '' in access exclusive mode'';\r
+               execute ''alter table only '' || p_tab_fqname ||\r
+                       '' add column "_Slony-I_@CLUSTERNAME@_rowID" bigint;'';\r
+               execute ''alter table only '' || p_tab_fqname ||\r
+                       '' alter column "_Slony-I_@CLUSTERNAME@_rowID" '' ||\r
+                       '' set default "pg_catalog".nextval(''''@NAMESPACE@.sl_rowid_seq'''');'';\r
+\r
+               v_attkind := v_attkind || ''k'';\r
+       end if;\r
+\r
+       --\r
+       -- Return the resulting Slony-I attkind\r
+       --\r
+       return v_attkind;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION tableDropKey (tab_id)\r
+--\r
+--     If the specified table does not have a column \r
+--     "_Slony-I_<clustername>_rowID", then add it as a bigint\r
+--     with default nextval('"_<clustername>".sl_rowid_seq').\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.tableDropKey(int4) returns int4\r
+as '\r
+declare\r
+       p_tab_id                alias for $1;\r
+       v_tab_fqname    text;\r
+       v_tab_oid               oid;\r
+begin\r
+       -- ----\r
+       -- Grab the central configuration lock\r
+       -- ----\r
+       lock table @NAMESPACE@.sl_config_lock;\r
+\r
+       -- ----\r
+       -- Construct the tables fully qualified name and get its oid\r
+       -- ----\r
+       select "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                               "pg_catalog".quote_ident(PGC.relname),\r
+                               PGC.oid into v_tab_fqname, v_tab_oid\r
+                       from @NAMESPACE@.sl_table T,\r
+                               "pg_catalog".pg_class PGC,\r
+                               "pg_catalog".pg_namespace PGN\r
+                       where T.tab_id = p_tab_id\r
+                               and T.tab_reloid = PGC.oid\r
+                               and PGC.relnamespace = PGN.oid;\r
+       if not found then\r
+               raise exception ''Slony-I: table with ID % not found'', p_tab_id;\r
+       end if;\r
+\r
+       -- ----\r
+       -- Drop the special serial ID column if the table has it\r
+       -- ----\r
+       if exists (select true from "pg_catalog".pg_attribute\r
+                       where attrelid = v_tab_oid\r
+                               and attname = ''_Slony-I_@CLUSTERNAME@_rowID'')\r
+       then\r
+               execute ''lock table '' || v_tab_fqname ||\r
+                               '' in access exclusive mode'';\r
+               execute ''alter table '' || v_tab_fqname ||\r
+                               '' drop column "_Slony-I_@CLUSTERNAME@_rowID"'';\r
+       end if;\r
+\r
+       return p_tab_id;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION determineIdxnameUnique (tab_fqname, indexname)\r
+--\r
+--     Given a tablename, check that a unique index exists or return\r
+--     the tables primary key index name.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.determineIdxnameUnique(text, name) returns name\r
+as '\r
+declare\r
+       p_tab_fqname    alias for $1;\r
+       p_idx_name              alias for $2;\r
+       v_idxrow                record;\r
+begin\r
+       --\r
+       -- Lookup the tables primary key or the specified unique index\r
+       --\r
+       if p_idx_name isnull then\r
+               select PGXC.relname\r
+                               into v_idxrow\r
+                               from "pg_catalog".pg_class PGC,\r
+                                       "pg_catalog".pg_namespace PGN,\r
+                                       "pg_catalog".pg_index PGX,\r
+                                       "pg_catalog".pg_class PGXC\r
+                               where "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                                       "pg_catalog".quote_ident(PGC.relname) = p_tab_fqname\r
+                                       and PGN.oid = PGC.relnamespace\r
+                                       and PGX.indrelid = PGC.oid\r
+                                       and PGX.indexrelid = PGXC.oid\r
+                                       and PGX.indisprimary;\r
+               if not found then\r
+                       raise exception ''Slony-I: table % has no primary key'',\r
+                                       p_tab_fqname;\r
+               end if;\r
+       else\r
+               select PGXC.relname\r
+                               into v_idxrow\r
+                               from "pg_catalog".pg_class PGC,\r
+                                       "pg_catalog".pg_namespace PGN,\r
+                                       "pg_catalog".pg_index PGX,\r
+                                       "pg_catalog".pg_class PGXC\r
+                               where "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                                       "pg_catalog".quote_ident(PGC.relname) = p_tab_fqname\r
+                                       and PGN.oid = PGC.relnamespace\r
+                                       and PGX.indrelid = PGC.oid\r
+                                       and PGX.indexrelid = PGXC.oid\r
+                                       and PGX.indisunique\r
+                                       and PGXC.relname = p_idx_name;\r
+               if not found then\r
+                       raise exception ''Slony-I: table % has no unique index %'',\r
+                                       p_tab_fqname, p_idx_name;\r
+               end if;\r
+       end if;\r
+\r
+       --\r
+       -- Return the found index name\r
+       --\r
+       return v_idxrow.relname;\r
+end;\r
+' language plpgsql called on null input;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION determineIdxnameSerial (tab_fqname)\r
+--\r
+--     Given a tablename, construct the serial columns index name\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.determineIdxnameSerial(text) returns name\r
+as '\r
+declare\r
+       p_tab_fqname    alias for $1;\r
+       v_row                   record;\r
+begin\r
+       --\r
+       -- Lookup the table name alone\r
+       --\r
+       select PGC.relname\r
+                       into v_row\r
+                       from "pg_catalog".pg_class PGC,\r
+                               "pg_catalog".pg_namespace PGN\r
+                       where "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                               "pg_catalog".quote_ident(PGC.relname) = p_tab_fqname\r
+                               and PGN.oid = PGC.relnamespace;\r
+       if not found then\r
+               raise exception ''Slony-I: table % not found'',\r
+                               p_tab_fqname;\r
+       end if;\r
+\r
+       --\r
+       -- Return the found index name\r
+       --\r
+       return v_row.relname || ''__Slony-I_@CLUSTERNAME@_rowID_key'';\r
+end;\r
+' language plpgsql called on null input;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION determineAttKindUnique (tab_fqname, indexname)\r
+--\r
+--     Given a tablename, return the Slony-I specific attkind (used for\r
+--     the log trigger) of the table. Use the specified unique index or\r
+--     the primary key (if indexname is NULL).\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.determineAttkindUnique(text, name) returns text\r
+as '\r
+declare\r
+       p_tab_fqname    alias for $1;\r
+       p_idx_name              alias for $2;\r
+       v_idxrow                record;\r
+       v_attrow                record;\r
+       v_i                             integer;\r
+       v_attno                 int2;\r
+       v_attkind               text default '''';\r
+       v_attfound              bool;\r
+begin\r
+       --\r
+       -- Lookup the tables primary key or the specified unique index\r
+       --\r
+       if p_idx_name isnull then\r
+               raise exception ''Slony-I: index name must be specified'';\r
+       else\r
+               select PGXC.relname, PGX.indexrelid, PGX.indkey\r
+                               into v_idxrow\r
+                               from "pg_catalog".pg_class PGC,\r
+                                       "pg_catalog".pg_namespace PGN,\r
+                                       "pg_catalog".pg_index PGX,\r
+                                       "pg_catalog".pg_class PGXC\r
+                               where "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                                       "pg_catalog".quote_ident(PGC.relname) = p_tab_fqname\r
+                                       and PGN.oid = PGC.relnamespace\r
+                                       and PGX.indrelid = PGC.oid\r
+                                       and PGX.indexrelid = PGXC.oid\r
+                                       and PGX.indisunique\r
+                                       and PGXC.relname = p_idx_name;\r
+               if not found then\r
+                       raise exception ''Slony-I: table % has no unique index %'',\r
+                                       p_tab_fqname, p_idx_name;\r
+               end if;\r
+       end if;\r
+\r
+       --\r
+       -- Loop over the tables attributes and check if they are\r
+       -- index attributes. If so, add a "k" to the return value,\r
+       -- otherwise add a "v".\r
+       --\r
+       for v_attrow in select PGA.attnum, PGA.attname\r
+                       from "pg_catalog".pg_class PGC,\r
+                           "pg_catalog".pg_namespace PGN,\r
+                               "pg_catalog".pg_attribute PGA\r
+                       where "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                           "pg_catalog".quote_ident(PGC.relname) = p_tab_fqname\r
+                               and PGN.oid = PGC.relnamespace\r
+                               and PGA.attrelid = PGC.oid\r
+                               and not PGA.attisdropped\r
+                               and PGA.attnum > 0\r
+                       order by attnum\r
+       loop\r
+               v_attfound = ''f'';\r
+\r
+               v_i := 0;\r
+               loop\r
+                       select indkey[v_i] into v_attno from "pg_catalog".pg_index\r
+                                       where indexrelid = v_idxrow.indexrelid;\r
+                       if v_attno = 0 then\r
+                               exit;\r
+                       end if;\r
+                       if v_attrow.attnum = v_attno then\r
+                               v_attfound = ''t'';\r
+                               exit;\r
+                       end if;\r
+                       v_i := v_i + 1;\r
+               end loop;\r
+\r
+               if v_attfound then\r
+                       v_attkind := v_attkind || ''k'';\r
+               else\r
+                       v_attkind := v_attkind || ''v'';\r
+               end if;\r
+       end loop;\r
+\r
+       --\r
+       -- Return the resulting attkind\r
+       --\r
+       return v_attkind;\r
+end;\r
+' language plpgsql called on null input;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION determineAttKindSerial (tab_fqname)\r
+--\r
+--     A table was that was specified without a primary key is added\r
+--     to the replication. Assume that tableAddKey() was called before\r
+--     and finish the creation of the serial column. The return an\r
+--     attkind according to that.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.determineAttkindSerial(text)\r
+returns text\r
+as '\r
+declare\r
+       p_tab_fqname    alias for $1;\r
+       v_attkind               text default '''';\r
+       v_attrow                record;\r
+       v_have_serial   bool default ''f'';\r
+begin\r
+       --\r
+       -- Loop over the attributes of this relation\r
+       -- and add a "v" for every user column, and a "k"\r
+       -- if we find the Slony-I special serial column.\r
+       --\r
+       for v_attrow in select PGA.attnum, PGA.attname\r
+                       from "pg_catalog".pg_class PGC,\r
+                           "pg_catalog".pg_namespace PGN,\r
+                               "pg_catalog".pg_attribute PGA\r
+                       where "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                           "pg_catalog".quote_ident(PGC.relname) = p_tab_fqname\r
+                               and PGN.oid = PGC.relnamespace\r
+                               and PGA.attrelid = PGC.oid\r
+                               and not PGA.attisdropped\r
+                               and PGA.attnum > 0\r
+                       order by attnum\r
+       loop\r
+               if v_attrow.attname = ''_Slony-I_@CLUSTERNAME@_rowID'' then\r
+                   v_attkind := v_attkind || ''k'';\r
+                       v_have_serial := ''t'';\r
+               else\r
+                       v_attkind := v_attkind || ''v'';\r
+               end if;\r
+       end loop;\r
+       \r
+       --\r
+       -- A table must have at least one attribute, so not finding\r
+       -- anything means the table does not exist.\r
+       --\r
+       if not found then\r
+               raise exception ''Slony-I: table % not found'', p_tab_fqname;\r
+       end if;\r
+\r
+       --\r
+       -- If it does not have the special serial column, we\r
+       -- should not have been called in the first place.\r
+       --\r
+       if not v_have_serial then\r
+               raise exception ''Slony-I: table % does not have the serial key'',\r
+                               p_tab_fqname;\r
+       end if;\r
+\r
+       execute ''update '' || p_tab_fqname ||\r
+               '' set "_Slony-I_@CLUSTERNAME@_rowID" ='' ||\r
+               '' "pg_catalog".nextval(''''@NAMESPACE@.sl_rowid_seq'''');'';\r
+       execute ''alter table only '' || p_tab_fqname ||\r
+               '' add unique ("_Slony-I_@CLUSTERNAME@_rowID");'';\r
+       execute ''alter table only '' || p_tab_fqname ||\r
+               '' alter column "_Slony-I_@CLUSTERNAME@_rowID" '' ||\r
+               '' set not null;'';\r
+\r
+       --\r
+       -- Return the resulting Slony-I attkind\r
+       --\r
+       return v_attkind;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION tableHasSerialKey (tab_fqname)\r
+--\r
+--     Checks if a table has our special serial key column that is\r
+--     used if the table has no natural unique constraint.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.tableHasSerialKey(text) \r
+returns bool\r
+as '\r
+declare\r
+       p_tab_fqname    alias for $1;\r
+       v_attnum                int2;\r
+begin\r
+       select PGA.attnum into v_attnum\r
+                       from "pg_catalog".pg_class PGC,\r
+                               "pg_catalog".pg_namespace PGN,\r
+                               "pg_catalog".pg_attribute PGA\r
+                       where "pg_catalog".quote_ident(PGN.nspname) || ''.'' ||\r
+                               "pg_catalog".quote_ident(PGC.relname) = p_tab_fqname\r
+                               and PGC.relnamespace = PGN.oid\r
+                               and PGA.attrelid = PGC.oid\r
+                               and PGA.attname = ''_Slony-I_@CLUSTERNAME@_rowID''\r
+                               and not PGA.attisdropped;\r
+       return found;\r
+end;\r
+' language plpgsql;\r
+\r
+\r
+-- **********************************************************************\r
+-- * Views\r
+-- **********************************************************************\r
+\r
+\r
+-- ----------------------------------------------------------------------\r
+-- VIEW sl_status\r
+--\r
+--     This view shows the local nodes last event sequence number\r
+--     and how far all remote nodes have processed events.\r
+-- ----------------------------------------------------------------------\r
+create or replace view @NAMESPACE@.sl_status as select\r
+       E.ev_origin as st_origin,\r
+       C.con_received as st_received,\r
+       E.ev_seqno as st_last_event,\r
+       E.ev_timestamp as st_last_event_ts,\r
+       C.con_seqno as st_last_received,\r
+       C.con_timestamp as st_last_received_ts,\r
+       CE.ev_timestamp as st_last_received_event_ts,\r
+       E.ev_seqno - C.con_seqno as st_lag_num_events,\r
+       current_timestamp - CE.ev_timestamp as st_lag_time\r
+       from @NAMESPACE@.sl_event E, @NAMESPACE@.sl_confirm C,\r
+               @NAMESPACE@.sl_event CE\r
+       where E.ev_origin = C.con_origin\r
+       and CE.ev_origin = E.ev_origin\r
+       and CE.ev_seqno = C.con_seqno\r
+       and (E.ev_origin, E.ev_seqno) in \r
+               (select ev_origin, max(ev_seqno)\r
+                       from @NAMESPACE@.sl_event\r
+                       where ev_origin = @NAMESPACE@.getLocalNodeId('_@CLUSTERNAME@')\r
+                       group by 1\r
+               )\r
+       and (C.con_origin, C.con_received, C.con_seqno) in\r
+               (select con_origin, con_received, max(con_seqno)\r
+                       from @NAMESPACE@.sl_confirm\r
+                       where con_origin = @NAMESPACE@.getLocalNodeId('_@CLUSTERNAME@')\r
+                       group by 1, 2\r
+               );\r
+\r
+\r
diff --git a/sql/plugins/slony1_funcs.v73.sql b/sql/plugins/slony1_funcs.v73.sql
new file mode 100755 (executable)
index 0000000..97580e7
--- /dev/null
@@ -0,0 +1,28 @@
+-- ----------------------------------------------------------------------\r
+-- slony1_funcs.v73.sql\r
+--\r
+--    Version 7.3 specific part of the replication support functions.\r
+--\r
+--     Copyright (c) 2003-2004, PostgreSQL Global Development Group\r
+--     Author: Jan Wieck, Afilias USA INC.\r
+--\r
+-- $Id: slony1_funcs.v73.sql,v 1.2 2005/06/16 14:40:15 chriskl Exp $\r
+-- ----------------------------------------------------------------------\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION truncateTable(tab_fqname)\r
+--\r
+--     Remove all content from a table before the subscription\r
+--     content is loaded via COPY.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.truncateTable(text)\r
+returns int4\r
+as '\r
+declare\r
+       p_tab_fqname            alias for $1;\r
+begin\r
+       execute ''delete from only '' || p_tab_fqname;\r
+       return 1;\r
+end;\r
+' language plpgsql;\r
+\r
diff --git a/sql/plugins/slony1_funcs.v74.sql b/sql/plugins/slony1_funcs.v74.sql
new file mode 100755 (executable)
index 0000000..489d9a6
--- /dev/null
@@ -0,0 +1,28 @@
+-- ----------------------------------------------------------------------\r
+-- slony1_funcs.v74.sql\r
+--\r
+--    Version 7.4 specific part of the replication support functions.\r
+--\r
+--     Copyright (c) 2003-2004, PostgreSQL Global Development Group\r
+--     Author: Jan Wieck, Afilias USA INC.\r
+--\r
+-- $Id: slony1_funcs.v74.sql,v 1.2 2005/06/16 14:40:15 chriskl Exp $\r
+-- ----------------------------------------------------------------------\r
+\r
+-- ----------------------------------------------------------------------\r
+-- FUNCTION truncateTable(tab_fqname)\r
+--\r
+--     Remove all content from a table before the subscription\r
+--     content is loaded via COPY.\r
+-- ----------------------------------------------------------------------\r
+create or replace function @NAMESPACE@.truncateTable(text)\r
+returns int4\r
+as '\r
+declare\r
+       p_tab_fqname            alias for $1;\r
+begin\r
+       execute ''delete from only '' || p_tab_fqname;\r
+       return 1;\r
+end;\r
+' language plpgsql;\r
+\r
diff --git a/sql/plugins/xxid.v73.sql b/sql/plugins/xxid.v73.sql
new file mode 100755 (executable)
index 0000000..20cd6d7
--- /dev/null
@@ -0,0 +1,186 @@
+-- ----------\r
+-- xxid.v73.sql.in\r
+--\r
+--     SQL script for loading the transaction ID compatible datatype \r
+--\r
+--     Copyright (c) 2003-2004, PostgreSQL Global Development Group\r
+--     Author: Jan Wieck, Afilias USA INC.\r
+--\r
+-- $Id: xxid.v73.sql,v 1.2 2005/06/16 14:40:15 chriskl Exp $\r
+-- ----------\r
+\r
+--\r
+-- Type specific input and output functions\r
+--\r
+CREATE FUNCTION @NAMESPACE@."xxidin"(cstring) RETURNS @NAMESPACE@."xxid"\r
+       AS '$libdir/xxid', '_Slony_I_xxidin'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidout"(@NAMESPACE@."xxid") RETURNS cstring\r
+       AS '$libdir/xxid', '_Slony_I_xxidout'\r
+       LANGUAGE C;\r
+\r
+\r
+--\r
+-- The data type itself\r
+--\r
+CREATE TYPE @NAMESPACE@."xxid" (\r
+       INPUT = @NAMESPACE@."xxidin",\r
+       OUTPUT = @NAMESPACE@."xxidout",\r
+       EXTERNALLENGTH = 12,\r
+       INTERNALLENGTH = 4,\r
+       PASSEDBYVALUE,\r
+       ALIGNMENT = int4\r
+);\r
+\r
+\r
+--\r
+-- Since our xxid type has special cases for values 0-3, it\r
+-- in fact IS xid, so allow implicit type casting to and from.\r
+--\r
+CREATE CAST (xid AS @NAMESPACE@.xxid)\r
+       WITHOUT FUNCTION AS IMPLICIT;\r
+CREATE CAST (@NAMESPACE@.xxid AS xid)\r
+       WITHOUT FUNCTION AS IMPLICIT;\r
+\r
+\r
+--\r
+-- Comparision functions for the new datatype\r
+--\r
+CREATE FUNCTION @NAMESPACE@."xxideq"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxideq'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidne"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxidne'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidlt"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxidlt'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidle"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxidle'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidgt"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxidgt'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidge"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxidge'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."btxxidcmp"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS int4\r
+       AS '$libdir/xxid', '_Slony_I_btxxidcmp'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@.getCurrentXid() RETURNS @NAMESPACE@."xxid"\r
+       AS '$libdir/xxid', '_Slony_I_getCurrentXid'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@.getMinXid() RETURNS @NAMESPACE@."xxid"\r
+       AS '$libdir/xxid', '_Slony_I_getMinXid'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@.getMaxXid() RETURNS @NAMESPACE@."xxid"\r
+       AS '$libdir/xxid', '_Slony_I_getMaxXid'\r
+       LANGUAGE C;\r
+\r
+\r
+--\r
+-- Operators on these comparision functions\r
+--\r
+CREATE OPERATOR < (\r
+       PROCEDURE = @NAMESPACE@."xxidlt",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = >, NEGATOR = >=,\r
+       RESTRICT = scalarltsel, JOIN = scalarltjoinsel\r
+);\r
+CREATE OPERATOR = (\r
+       PROCEDURE = @NAMESPACE@."xxideq",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = =, NEGATOR = <>,\r
+       RESTRICT = eqsel, JOIN = eqjoinsel,\r
+       SORT1 = <, SORT2 = <,\r
+       HASHES\r
+);\r
+CREATE OPERATOR <> (\r
+       PROCEDURE = @NAMESPACE@."xxidne",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = <>, NEGATOR = =,\r
+       RESTRICT = neqsel, JOIN = neqjoinsel\r
+);\r
+CREATE OPERATOR > (\r
+       PROCEDURE = @NAMESPACE@."xxidgt",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = <, NEGATOR = <=,\r
+       RESTRICT = scalargtsel, JOIN = scalargtjoinsel\r
+);\r
+CREATE OPERATOR <= (\r
+       PROCEDURE = @NAMESPACE@."xxidle",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = >=, NEGATOR = >,\r
+       RESTRICT = scalarltsel, JOIN = scalarltjoinsel\r
+);\r
+CREATE OPERATOR >= (\r
+       PROCEDURE = @NAMESPACE@."xxidge",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = <=, NEGATOR = <,\r
+       RESTRICT = scalargtsel, JOIN = scalargtjoinsel\r
+);\r
+\r
+\r
+--\r
+-- Finally the default operator class so that we can use our\r
+-- new data type in btree indexes.\r
+--\r
+CREATE OPERATOR CLASS @NAMESPACE@."xxid_ops"\r
+       DEFAULT FOR TYPE @NAMESPACE@."xxid" USING btree AS\r
+       OPERATOR 1 <  (@NAMESPACE@."xxid", @NAMESPACE@."xxid"),\r
+       OPERATOR 2 <= (@NAMESPACE@."xxid", @NAMESPACE@."xxid"),\r
+       OPERATOR 3 =  (@NAMESPACE@."xxid", @NAMESPACE@."xxid"),\r
+       OPERATOR 4 >= (@NAMESPACE@."xxid", @NAMESPACE@."xxid"),\r
+       OPERATOR 5 >  (@NAMESPACE@."xxid", @NAMESPACE@."xxid"),\r
+       FUNCTION 1 @NAMESPACE@."btxxidcmp" (@NAMESPACE@."xxid", @NAMESPACE@."xxid");\r
+\r
+\r
+--\r
+-- A special transaction snapshot data type for faster visibility checks\r
+--\r
+CREATE FUNCTION @NAMESPACE@."xxid_snapshot_in"(cstring)\r
+RETURNS @NAMESPACE@."xxid_snapshot"\r
+       AS '$libdir/xxid', '_Slony_I_xxid_snapshot_in'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxid_snapshot_out"(@NAMESPACE@."xxid_snapshot")\r
+RETURNS cstring\r
+       AS '$libdir/xxid', '_Slony_I_xxid_snapshot_out'\r
+       LANGUAGE C;\r
+\r
+\r
+--\r
+-- The data type itself\r
+--\r
+CREATE TYPE @NAMESPACE@."xxid_snapshot" (\r
+       INPUT = @NAMESPACE@."xxid_snapshot_in",\r
+       OUTPUT = @NAMESPACE@."xxid_snapshot_out",\r
+       INTERNALLENGTH = variable,\r
+       ALIGNMENT = int4\r
+);\r
+\r
+\r
+--\r
+-- Special comparision functions used by the remote worker\r
+-- for sync chunk selection\r
+--\r
+CREATE FUNCTION @NAMESPACE@."xxid_lt_snapshot"(\r
+               @NAMESPACE@."xxid",\r
+               @NAMESPACE@."xxid_snapshot")\r
+RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxid_lt_snapshot'\r
+       LANGUAGE C;\r
+\r
+CREATE FUNCTION @NAMESPACE@."xxid_ge_snapshot"(\r
+               @NAMESPACE@."xxid",\r
+               @NAMESPACE@."xxid_snapshot")\r
+RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxid_ge_snapshot'\r
+       LANGUAGE C;\r
+\r
+\r
diff --git a/sql/plugins/xxid.v74.sql b/sql/plugins/xxid.v74.sql
new file mode 100755 (executable)
index 0000000..1563256
--- /dev/null
@@ -0,0 +1,186 @@
+-- ----------\r
+-- xxid.v73.sql.in\r
+--\r
+--     SQL script for loading the transaction ID compatible datatype \r
+--\r
+--     Copyright (c) 2003-2004, PostgreSQL Global Development Group\r
+--     Author: Jan Wieck, Afilias USA INC.\r
+--\r
+-- $Id: xxid.v74.sql,v 1.2 2005/06/16 14:40:15 chriskl Exp $\r
+-- ----------\r
+\r
+--\r
+-- Type specific input and output functions\r
+--\r
+CREATE FUNCTION @NAMESPACE@."xxidin"(cstring) RETURNS @NAMESPACE@."xxid"\r
+       AS '$libdir/xxid', '_Slony_I_xxidin'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidout"(@NAMESPACE@."xxid") RETURNS cstring\r
+       AS '$libdir/xxid', '_Slony_I_xxidout'\r
+       LANGUAGE C;\r
+\r
+\r
+--\r
+-- The data type itself\r
+--\r
+CREATE TYPE @NAMESPACE@."xxid" (\r
+       INPUT = @NAMESPACE@."xxidin",\r
+       OUTPUT = @NAMESPACE@."xxidout",\r
+       EXTERNALLENGTH = 12,\r
+       INTERNALLENGTH = 4,\r
+       PASSEDBYVALUE,\r
+       ALIGNMENT = int4\r
+);\r
+\r
+\r
+--\r
+-- Since our xxid type has special cases for values 0-3, it\r
+-- in fact IS xid, so allow implicit type casting to and from.\r
+--\r
+CREATE CAST (xid AS @NAMESPACE@.xxid)\r
+       WITHOUT FUNCTION AS IMPLICIT;\r
+CREATE CAST (@NAMESPACE@.xxid AS xid)\r
+       WITHOUT FUNCTION AS IMPLICIT;\r
+\r
+\r
+--\r
+-- Comparision functions for the new datatype\r
+--\r
+CREATE FUNCTION @NAMESPACE@."xxideq"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxideq'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidne"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxidne'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidlt"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxidlt'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidle"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxidle'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidgt"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxidgt'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxidge"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxidge'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."btxxidcmp"(@NAMESPACE@."xxid", @NAMESPACE@."xxid") RETURNS int4\r
+       AS '$libdir/xxid', '_Slony_I_btxxidcmp'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@.getCurrentXid() RETURNS @NAMESPACE@."xxid"\r
+       AS '$libdir/xxid', '_Slony_I_getCurrentXid'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@.getMinXid() RETURNS @NAMESPACE@."xxid"\r
+       AS '$libdir/xxid', '_Slony_I_getMinXid'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@.getMaxXid() RETURNS @NAMESPACE@."xxid"\r
+       AS '$libdir/xxid', '_Slony_I_getMaxXid'\r
+       LANGUAGE C;\r
+\r
+\r
+--\r
+-- Operators on these comparision functions\r
+--\r
+CREATE OPERATOR < (\r
+       PROCEDURE = @NAMESPACE@."xxidlt",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = >, NEGATOR = >=,\r
+       RESTRICT = scalarltsel, JOIN = scalarltjoinsel\r
+);\r
+CREATE OPERATOR = (\r
+       PROCEDURE = @NAMESPACE@."xxideq",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = =, NEGATOR = <>,\r
+       RESTRICT = eqsel, JOIN = eqjoinsel,\r
+       SORT1 = <, SORT2 = <,\r
+       HASHES\r
+);\r
+CREATE OPERATOR <> (\r
+       PROCEDURE = @NAMESPACE@."xxidne",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = <>, NEGATOR = =,\r
+       RESTRICT = neqsel, JOIN = neqjoinsel\r
+);\r
+CREATE OPERATOR > (\r
+       PROCEDURE = @NAMESPACE@."xxidgt",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = <, NEGATOR = <=,\r
+       RESTRICT = scalargtsel, JOIN = scalargtjoinsel\r
+);\r
+CREATE OPERATOR <= (\r
+       PROCEDURE = @NAMESPACE@."xxidle",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = >=, NEGATOR = >,\r
+       RESTRICT = scalarltsel, JOIN = scalarltjoinsel\r
+);\r
+CREATE OPERATOR >= (\r
+       PROCEDURE = @NAMESPACE@."xxidge",\r
+       LEFTARG = @NAMESPACE@."xxid",\r
+       RIGHTARG = @NAMESPACE@."xxid",\r
+       COMMUTATOR = <=, NEGATOR = <,\r
+       RESTRICT = scalargtsel, JOIN = scalargtjoinsel\r
+);\r
+\r
+\r
+--\r
+-- Finally the default operator class so that we can use our\r
+-- new data type in btree indexes.\r
+--\r
+CREATE OPERATOR CLASS @NAMESPACE@."xxid_ops"\r
+       DEFAULT FOR TYPE @NAMESPACE@."xxid" USING btree AS\r
+       OPERATOR 1 <  (@NAMESPACE@."xxid", @NAMESPACE@."xxid"),\r
+       OPERATOR 2 <= (@NAMESPACE@."xxid", @NAMESPACE@."xxid"),\r
+       OPERATOR 3 =  (@NAMESPACE@."xxid", @NAMESPACE@."xxid"),\r
+       OPERATOR 4 >= (@NAMESPACE@."xxid", @NAMESPACE@."xxid"),\r
+       OPERATOR 5 >  (@NAMESPACE@."xxid", @NAMESPACE@."xxid"),\r
+       FUNCTION 1 @NAMESPACE@."btxxidcmp" (@NAMESPACE@."xxid", @NAMESPACE@."xxid");\r
+\r
+\r
+--\r
+-- A special transaction snapshot data type for faster visibility checks\r
+--\r
+CREATE FUNCTION @NAMESPACE@."xxid_snapshot_in"(cstring)\r
+RETURNS @NAMESPACE@."xxid_snapshot"\r
+       AS '$libdir/xxid', '_Slony_I_xxid_snapshot_in'\r
+       LANGUAGE C;\r
+CREATE FUNCTION @NAMESPACE@."xxid_snapshot_out"(@NAMESPACE@."xxid_snapshot")\r
+RETURNS cstring\r
+       AS '$libdir/xxid', '_Slony_I_xxid_snapshot_out'\r
+       LANGUAGE C;\r
+\r
+\r
+--\r
+-- The data type itself\r
+--\r
+CREATE TYPE @NAMESPACE@."xxid_snapshot" (\r
+       INPUT = @NAMESPACE@."xxid_snapshot_in",\r
+       OUTPUT = @NAMESPACE@."xxid_snapshot_out",\r
+       INTERNALLENGTH = variable,\r
+       ALIGNMENT = int4\r
+);\r
+\r
+\r
+--\r
+-- Special comparision functions used by the remote worker\r
+-- for sync chunk selection\r
+--\r
+CREATE FUNCTION @NAMESPACE@."xxid_lt_snapshot"(\r
+               @NAMESPACE@."xxid",\r
+               @NAMESPACE@."xxid_snapshot")\r
+RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxid_lt_snapshot'\r
+       LANGUAGE C;\r
+\r
+CREATE FUNCTION @NAMESPACE@."xxid_ge_snapshot"(\r
+               @NAMESPACE@."xxid",\r
+               @NAMESPACE@."xxid_snapshot")\r
+RETURNS boolean\r
+       AS '$libdir/xxid', '_Slony_I_xxid_ge_snapshot'\r
+       LANGUAGE C;\r
+\r
+\r