- 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
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)
(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
/**
* 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}";
/**
* 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');
* 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???
* 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 );
}
/**
* 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);
}
* 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???
* 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 );
}
/**
* 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,
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);
}
* 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');
/**
* 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,
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);
}
/**
* 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');
* 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 );
}
--- /dev/null
+<?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');
+ }
+ }
+
+}
+
+?>
--- /dev/null
+<?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
+
+
+
+}
+
+?>
--- /dev/null
+<?php
+
+ /**
+ * Slony plugin configuration
+ *
+ * $Id: slony.inc.php,v 1.2 2005/06/16 14:40:13 chriskl Exp $
+ */
+
+ $conf['slony_enabled'] = true;
+
+?>
/**
* 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');
* 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
$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';
* 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
$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';
/**
* 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');
$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();
+
}
?>
--- /dev/null
+<?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}&action=cluster_properties&",
+ 'vars' => array()
+ ),
+ 'drop' => array(
+ 'title' => $lang['strdrop'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=confirm_drop_cluster&",
+ '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&{$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}&action=node_properties&",
+ 'vars' => array('no_id' => 'no_id')
+ ),
+ 'drop' => array(
+ 'title' => $lang['strdrop'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=confirm_drop_node&",
+ 'vars' => array('no_id' => 'no_id')
+ )
+ );
+
+ $misc->printTable($nodes, $columns, $actions, $lang['strnonodes']);
+
+ echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=create_node&{$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&{$misc->href}&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}&action=path_properties&",
+ 'vars' => array('no_id' => 'pa_client', 'path_id' => 'no_id')
+ ),
+ 'drop' => array(
+ 'title' => $lang['strdrop'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=confirm_drop_path&",
+ '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&{$misc->href}&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&{$misc->href}&no_id={$_REQUEST['no_id']}&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}&action=listen_properties&",
+ '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}&action=confirm_drop_listen&",
+ '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&{$misc->href}&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&{$misc->href}&no_id={$_REQUEST['no_id']}&listen_id={$_REQUEST['listen_id']}&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}&action=set_properties&",
+ 'vars' => array('set_id' => 'set_id')
+ ),
+ 'drop' => array(
+ 'title' => $lang['strdrop'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=confirm_drop_set&",
+ 'vars' => array('set_id' => 'set_id')
+ ),
+ 'lock' => array(
+ 'title' => $lang['strlock'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=confirm_lock_set&",
+ 'vars' => array('set_id' => 'set_id')
+ ),
+ 'unlock' => array(
+ 'title' => $lang['strunlock'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=confirm_unlock_set&",
+ 'vars' => array('set_id' => 'set_id')
+ ),
+ 'merge' => array(
+ 'title' => $lang['strmerge'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=merge_set&",
+ 'vars' => array('set_id' => 'set_id')
+ ),
+ 'move' => array(
+ 'title' => $lang['strmove'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=move_set&",
+ 'vars' => array('set_id' => 'set_id')
+ ),
+ 'execute' => array(
+ 'title' => $lang['strexecute'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=execute_set&",
+ 'vars' => array('set_id' => 'set_id')
+ )
+ );
+
+ $misc->printTable($sets, $columns, $actions, $lang['strnorepsets']);
+
+ echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=create_set&{$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&{$misc->href}&set_id={$_REQUEST['set_id']}\">{$lang['strdrop']}</a> |\n";
+ echo "<a class=\"navlink\" href=\"{$PHP_SELF}?action=confirm_lock_set&{$misc->href}&set_id={$_REQUEST['set_id']}\">{$lang['strlock']}</a> |\n";
+ echo "<a class=\"navlink\" href=\"{$PHP_SELF}?action=confirm_unlock_set&{$misc->href}&set_id={$_REQUEST['set_id']}\">{$lang['strunlock']}</a> |\n";
+ echo "<a class=\"navlink\" href=\"{$PHP_SELF}?action=merge_set&{$misc->href}&set_id={$_REQUEST['set_id']}\">{$lang['strmerge']}</a> |\n";
+ echo "<a class=\"navlink\" href=\"{$PHP_SELF}?action=move_set&{$misc->href}&set_id={$_REQUEST['set_id']}\">{$lang['strmove']}</a> |\n";
+ echo "<a class=\"navlink\" href=\"{$PHP_SELF}?action=execute_set&{$misc->href}&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&{$misc->href}&",
+ 'vars' => array('table' => 'relname', 'schema' => 'nspname'),
+ ),
+ 'remove' => array(
+ 'title' => $lang['strremove'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=confirm_drop_table&set_id={$_REQUEST['set_id']}&",
+ 'vars' => array('tab_id' => 'tab_id', 'qualname' => 'qualname'),
+ ),
+ 'move' => array(
+ 'title' => $lang['strmove'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=move_table&set_id={$_REQUEST['set_id']}&stage=1&",
+ '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&stage=1&set_id={$_REQUEST['set_id']}&{$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&{$misc->href}&",
+ 'vars' => array('sequence' => 'seqname', 'schema' => 'nspname'),
+ ),
+ 'remove' => array(
+ 'title' => $lang['strremove'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=confirm_drop_sequence&set_id={$_REQUEST['set_id']}&",
+ 'vars' => array('seq_id' => 'seq_id', 'qualname' => 'qualname'),
+ ),
+ 'move' => array(
+ 'title' => $lang['strmove'],
+ 'url' => "plugin_slony.php?{$misc->href}&action=move_sequence&set_id={$_REQUEST['set_id']}&stage=1&",
+ 'vars' => array('seq_id' => 'seq_id'),
+ )
+ );
+
+ $misc->printTable($sequences, $columns, $actions, $lang['strnosequences']);
+
+ echo "<p><a class=\"navlink\" href=\"{$PHP_SELF}?action=add_sequence&stage=1&set_id={$_REQUEST['set_id']}&{$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}&action=subscription_properties&",
+ '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();
+
+?>
/**
* 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&{$misc->href}&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";
}
/**
* 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}",
- "&action=sql&query=", urlencode($_POST['query']), "\">{$lang['streditsql']}</a>";
+
+ echo "<p><a class=\"navlink\" href=\"database.php?database=", urlencode($_REQUEST['database']),
+ "&server=", urlencode($_REQUEST['server']), "&action=sql&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}&action=create&report_sql=",
urlencode($_POST['query']), "\">{$lang['strcreatereport']}</a>";
--- /dev/null
+-- ----------------------------------------------------------------------\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
--- /dev/null
+-- ----------------------------------------------------------------------\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
--- /dev/null
+-- ----------------------------------------------------------------------\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
--- /dev/null
+-- ----------------------------------------------------------------------\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
--- /dev/null
+-- ----------------------------------------------------------------------\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
--- /dev/null
+-- ----------------------------------------------------------------------\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
--- /dev/null
+-- ----------\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
--- /dev/null
+-- ----------\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