diff options
author | Guillaume (ioguix) de Rorthais | 2008-10-28 15:10:45 +0000 |
---|---|---|
committer | Guillaume (ioguix) de Rorthais | 2008-10-28 15:10:45 +0000 |
commit | 914109fb251921a14e450b0cbd273bf92d918f1d (patch) | |
tree | 197dea6268190c83104c7f4984e3c6c2c692a554 | |
parent | fd73c5f02cd6a6980feab6ea04f46ec63fca5f6a (diff) |
Reverse database classes Inheritance
- reverse database classes inheritance
- remove pg 70, 71, 72 support
- refactor alterTable, alterView and alterSequence to call uni-task methods (alter user, comment, schema, ...) from the public one instead of having all the code in the main method.
- php5 migration: lib.inc.php require at least php 5.0
-rwxr-xr-x | classes/database/Connection.php | 40 | ||||
-rwxr-xr-x | classes/database/Postgres.php | 7187 | ||||
-rw-r--r-- | classes/database/Postgres71.php | 511 | ||||
-rw-r--r-- | classes/database/Postgres72.php | 688 | ||||
-rw-r--r-- | classes/database/Postgres73.php | 2509 | ||||
-rw-r--r-- | classes/database/Postgres74.php | 939 | ||||
-rw-r--r-- | classes/database/Postgres80.php | 623 | ||||
-rw-r--r-- | classes/database/Postgres81.php | 672 | ||||
-rw-r--r-- | classes/database/Postgres82.php | 375 | ||||
-rw-r--r-- | classes/database/Postgres83.php | 798 | ||||
-rw-r--r-- | libraries/lib.inc.php | 2 | ||||
-rw-r--r-- | selenium/tests/config.inc.php | 2 |
12 files changed, 5764 insertions, 8582 deletions
diff --git a/classes/database/Connection.php b/classes/database/Connection.php index 935e979e..a0a00dff 100755 --- a/classes/database/Connection.php +++ b/classes/database/Connection.php @@ -79,29 +79,25 @@ class Connection { $description = "PostgreSQL {$version}"; // Detect version and choose appropriate database driver - // If unknown version, then default to latest driver - // All 6.x versions default to oldest driver, even though - // it won't work with those versions. - if ((int)substr($version, 0, 1) < 7) + switch (substr($version,0,3)) { + case '8.3': return 'Postgres'; break; + case '8.2': return 'Postgres82'; break; + case '8.1': return 'Postgres81'; break; + case '8.0': + case '7.5': return 'Postgres80'; break; + case '7.4': return 'Postgres74'; break; + case '7.3': return 'Postgres73'; break; + } + + /* All <7.3 versions are not supported */ + // if major version is 7 or less and wasn't catch in the + // switch/case block, we have an unsupported version. + if ((int)substr($version, 0, 1) < 8) return null; - elseif (strpos($version, '8.2') === 0) - return 'Postgres82'; - elseif (strpos($version, '8.1') === 0) - return 'Postgres81'; - elseif (strpos($version, '8.0') === 0 || strpos($version, '7.5') === 0) - return 'Postgres80'; - elseif (strpos($version, '7.4') === 0) - return 'Postgres74'; - elseif (strpos($version, '7.3') === 0) - return 'Postgres73'; - elseif (strpos($version, '7.2') === 0) - return 'Postgres72'; - elseif (strpos($version, '7.1') === 0) - return 'Postgres71'; - elseif (strpos($version, '7.0') === 0) - return 'Postgres'; - else - return 'Postgres83'; + + // If unknown version, then default to latest driver + return 'Postgres'; + } /** diff --git a/classes/database/Postgres.php b/classes/database/Postgres.php index 51b6ff73..58e481df 100755 --- a/classes/database/Postgres.php +++ b/classes/database/Postgres.php @@ -1,66 +1,32 @@ <?php /** - * A class that implements the DB interface for Postgres - * Note: This class uses ADODB and returns RecordSets. + * A Class that implements the DB Interface for Postgres + * Note: This Class uses ADODB and returns RecordSets. * * $Id: Postgres.php,v 1.320 2008/02/20 20:43:09 ioguix Exp $ */ -// @@@ THOUGHT: What about inherits? ie. use of ONLY??? - include_once('./classes/database/ADODB_base.php'); class Postgres extends ADODB_base { - // Major version. MUST be numerically comparable to other versions. - var $major_version = 7.0; - // Array of allowed type alignments - var $typAligns = array('char', 'int2', 'int4', 'double'); - // The default type alignment - var $typAlignDef = 'int4'; - // Array of allowed type storage attributes - var $typStorages = array('plain', 'external', 'extended', 'main'); - // The default type storage - var $typStorageDef = 'plain'; - // Extra "magic" types - var $extraTypes = array('SERIAL'); - // Array of allowed index types - var $typIndexes = array('BTREE', 'RTREE', 'GIST', 'HASH'); - // Default index type - var $typIndexDef = 'BTREE'; - // Array of allowed trigger events - var $triggerEvents= array('INSERT', 'UPDATE', 'DELETE', 'INSERT OR UPDATE', 'INSERT OR DELETE', - 'DELETE OR UPDATE', 'INSERT OR DELETE OR UPDATE'); - // When to execute the trigger - var $triggerExecTimes = array('BEFORE', 'AFTER'); - // How often to execute the trigger - var $triggerFrequency = array('ROW'); - // Foreign key stuff. First element MUST be the default. - var $fkactions = array('NO ACTION', 'RESTRICT', 'CASCADE', 'SET NULL', 'SET DEFAULT'); - var $fkmatches = array('MATCH SIMPLE', 'MATCH FULL'); - var $fkdeferrable = array('NOT DEFERRABLE', 'DEFERRABLE'); - var $fkinitial = array('INITIALLY IMMEDIATE', 'INITIALLY DEFERRED'); - // Function properties - var $funcprops = array(array('', 'ISCACHABLE')); - var $defaultprops = array(''); - - // Last oid assigned to a system object - var $_lastSystemOID = 18539; - var $_maxNameLen = 31; - - // Name of id column - var $id = 'oid'; - + var $major_version = 8.3; + // Max object name length + var $_maxNameLen = 63; + // Store the current schema + var $_schema; // Map of database encoding names to HTTP encoding names. If a // database encoding does not appear in this list, then its HTTP // encoding name is the same as its database encoding name. var $codemap = array( - 'ALT' => 'CP866', + 'BIG5' => 'BIG5', 'EUC_CN' => 'GB2312', 'EUC_JP' => 'EUC-JP', 'EUC_KR' => 'EUC-KR', 'EUC_TW' => 'EUC-TW', + 'GB18030' => 'GB18030', + 'GBK' => 'GB2312', 'ISO_8859_5' => 'ISO-8859-5', 'ISO_8859_6' => 'ISO-8859-6', 'ISO_8859_7' => 'ISO-8859-7', @@ -71,22 +37,44 @@ class Postgres extends ADODB_base { 'LATIN2' => 'ISO-8859-2', 'LATIN3' => 'ISO-8859-3', 'LATIN4' => 'ISO-8859-4', - // The following encoding map is a known error in PostgreSQL < 7.2 - // See the constructor for Postgres72. - 'LATIN5' => 'ISO-8859-5', + 'LATIN5' => 'ISO-8859-9', 'LATIN6' => 'ISO-8859-10', 'LATIN7' => 'ISO-8859-13', 'LATIN8' => 'ISO-8859-14', 'LATIN9' => 'ISO-8859-15', 'LATIN10' => 'ISO-8859-16', + 'SJIS' => 'SHIFT_JIS', 'SQL_ASCII' => 'US-ASCII', - 'TCVN' => 'CP1258', - 'UNICODE' => 'UTF-8', - 'WIN' => 'CP1251', + 'UHC' => 'WIN949', + 'UTF8' => 'UTF-8', + 'WIN866' => 'CP866', 'WIN874' => 'CP874', - 'WIN1256' => 'CP1256' + 'WIN1250' => 'CP1250', + 'WIN1251' => 'CP1251', + 'WIN1252' => 'CP1252', + 'WIN1256' => 'CP1256', + 'WIN1258' => 'CP1258' ); - + var $defaultprops = array('', '', ''); + // Extra "magic" types. BIGSERIAL was added in PostgreSQL 7.2. + var $extraTypes = array('SERIAL', 'BIGSERIAL'); + // Foreign key stuff. First element MUST be the default. + var $fkactions = array('NO ACTION', 'RESTRICT', 'CASCADE', 'SET NULL', 'SET DEFAULT'); + var $fkdeferrable = array('NOT DEFERRABLE', 'DEFERRABLE'); + var $fkinitial = array('INITIALLY IMMEDIATE', 'INITIALLY DEFERRED'); + var $fkmatches = array('MATCH SIMPLE', 'MATCH FULL'); + // Function properties + var $funcprops = array( array('', 'VOLATILE', 'IMMUTABLE', 'STABLE'), + array('', 'CALLED ON NULL INPUT', 'RETURNS NULL ON NULL INPUT'), + array('', 'SECURITY INVOKER', 'SECURITY DEFINER')); + // Default help URL + var $help_base; + // Help sub pages + var $help_page; + // Name of id column + var $id = 'oid'; + // Supported join operations for use with view wizard + var $joinOps = array('INNER JOIN' => 'INNER JOIN', 'LEFT JOIN' => 'LEFT JOIN', 'RIGHT JOIN' => 'RIGHT JOIN', 'FULL JOIN' => 'FULL JOIN'); // Map of internal language name to syntax highlighting name var $langmap = array( 'sql' => 'SQL', @@ -114,45 +102,66 @@ class Postgres extends ADODB_base { 'plruby' => 'Ruby', 'plrubyu' => 'Ruby' ); - + // Predefined size types + var $predefined_size_types = array('abstime','aclitem','bigserial','boolean','bytea','cid','cidr','circle','date','float4','float8','gtsvector','inet','int2','int4','int8','macaddr','money','oid','path','polygon','refcursor','regclass','regoper','regoperator','regproc','regprocedure','regtype','reltime','serial','smgr','text','tid','tinterval','tsquery','tsvector','varbit','void','xid'); // List of all legal privileges that can be applied to different types // of objects. var $privlist = array( - 'table' => array('SELECT', 'INSERT', 'UPDATE', 'RULE', 'ALL'), - 'view' => array('SELECT', 'INSERT', 'UPDATE', 'RULE', 'ALL'), - 'sequence' => array('SELECT', 'UPDATE', 'ALL') + 'table' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), + 'view' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), + 'sequence' => array('SELECT', 'UPDATE', 'ALL PRIVILEGES'), + 'database' => array('CREATE', 'TEMPORARY', 'CONNECT', 'ALL PRIVILEGES'), + 'function' => array('EXECUTE', 'ALL PRIVILEGES'), + 'language' => array('USAGE', 'ALL PRIVILEGES'), + 'schema' => array('CREATE', 'USAGE', 'ALL PRIVILEGES'), + 'tablespace' => array('CREATE', 'ALL PRIVILEGES') ); - // List of characters in acl lists and the privileges they // refer to. var $privmap = array( 'r' => 'SELECT', 'w' => 'UPDATE', 'a' => 'INSERT', - 'R' => 'RULE' + 'd' => 'DELETE', + 'R' => 'RULE', + 'x' => 'REFERENCES', + 't' => 'TRIGGER', + 'X' => 'EXECUTE', + 'U' => 'USAGE', + 'C' => 'CREATE', + 'T' => 'TEMPORARY', + 'c' => 'CONNECT' ); - // Rule action types var $rule_events = array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); - // Select operators - // Operators of type 'i' are 'infix', eg. a = '1'. Type 'p' means postfix unary, eg. a IS TRUE. - // 'x' is a bracketed subquery form. eg. IN (1,2,3) - var $selectOps = array('=' => 'i', '!=' => 'i', '<' => 'i', '>' => 'i', '<=' => 'i', '>=' => 'i', '<<' => 'i', '>>' => 'i', '<<=' => 'i', '>>=' => 'i', - 'LIKE' => 'i', 'NOT LIKE' => 'i', '~' => 'i', '!~' => 'i', '~*' => 'i', '!~*' => 'i', - 'IS NULL' => 'p', 'IS NOT NULL' => 'p', 'IN' => 'x', 'NOT IN' => 'x'); - - // Supported join operations for use with view wizard - var $joinOps = array('INNER JOIN' => 'INNER JOIN'); - - // Default help URL - var $help_base; - - // Help sub pages - var $help_page; - - // Predefined size types - var $predefined_size_types = array('abstime','aclitem','bigserial','boolean','bytea','cid','cidr','circle','date','float4','float8','gtsvector','inet','int2','int4','int8','macaddr','money','oid','path','polygon','refcursor','regclass','regoper','regoperator','regproc','regprocedure','regtype','reltime','serial','smgr','text','tid','tinterval','tsquery','tsvector','varbit','void','xid'); + var $selectOps = array('=' => 'i', '!=' => 'i', '<' => 'i', '>' => 'i', '<=' => 'i', '>=' => 'i', + '<<' => 'i', '>>' => 'i', '<<=' => 'i', '>>=' => 'i', + 'LIKE' => 'i', 'NOT LIKE' => 'i', 'ILIKE' => 'i', 'NOT ILIKE' => 'i', 'SIMILAR TO' => 'i', + 'NOT SIMILAR TO' => 'i', '~' => 'i', '!~' => 'i', '~*' => 'i', '!~*' => 'i', + 'IS NULL' => 'p', 'IS NOT NULL' => 'p', 'IN' => 'x', 'NOT IN' => 'x', + '@@' => 'i', '@@@' => 'i', '@>' => 'i', '<@' => 'i', + '@@ to_tsquery' => 't', '@@@ to_tsquery' => 't', '@> to_tsquery' => 't', '<@ to_tsquery' => 't', + '@@ plainto_tsquery' => 't', '@@@ plainto_tsquery' => 't', '@> plainto_tsquery' => 't', '<@ plainto_tsquery' => 't'); + // Array of allowed trigger events + var $triggerEvents= array('INSERT', 'UPDATE', 'DELETE', 'INSERT OR UPDATE', 'INSERT OR DELETE', + 'DELETE OR UPDATE', 'INSERT OR DELETE OR UPDATE'); + // When to execute the trigger + var $triggerExecTimes = array('BEFORE', 'AFTER'); + // How often to execute the trigger + var $triggerFrequency = array('ROW','STATEMENT'); + // Array of allowed type alignments + var $typAligns = array('char', 'int2', 'int4', 'double'); + // The default type alignment + var $typAlignDef = 'int4'; + // Default index type + var $typIndexDef = 'BTREE'; + // Array of allowed index types + var $typIndexes = array('BTREE', 'RTREE', 'GIST', 'GIN', 'HASH'); + // Array of allowed type storage attributes + var $typStorages = array('plain', 'external', 'extended', 'main'); + // The default type storage + var $typStorageDef = 'plain'; /** * Constructor @@ -162,35 +171,6 @@ class Postgres extends ADODB_base { $this->ADODB_base($conn); } - // Help functions - - /** - * Fetch a URL (or array of URLs) for a given help page. - */ - function getHelp($help) { - $this->getHelpPages(); - - if (isset($this->help_page[$help])) { - if (is_array($this->help_page[$help])) { - $urls = array(); - foreach ($this->help_page[$help] as $link) { - $urls[] = $this->help_base . $link; - } - return $urls; - } else - return $this->help_base . $this->help_page[$help]; - } else - return null; - } - - /** - * Initialize help pages and return the full list - */ - function getHelpPages() { - include_once('./help/PostgresDoc70.php'); - return $this->help_page; - } - // Formatting functions /** @@ -220,30 +200,30 @@ class Postgres extends ADODB_base { } /** - * Cleans (escapes) an array + * Cleans (escapes) an array of field names * @param $arr The array to clean, by reference * @return The cleaned array */ - function arrayClean(&$arr) { + function fieldArrayClean(&$arr) { foreach ($arr as $k => $v) { if ($v === null) continue; - if (function_exists('pg_escape_string')) - $arr[$k] = pg_escape_string($v); - else - $arr[$k] = addslashes($v); + $arr[$k] = str_replace('"', '""', $v); } return $arr; } /** - * Cleans (escapes) an array of field names + * Cleans (escapes) an array * @param $arr The array to clean, by reference * @return The cleaned array */ - function fieldArrayClean(&$arr) { + function arrayClean(&$arr) { foreach ($arr as $k => $v) { if ($v === null) continue; - $arr[$k] = str_replace('"', '""', $v); + if (function_exists('pg_escape_string')) + $arr[$k] = pg_escape_string($v); + else + $arr[$k] = addslashes($v); } return $arr; } @@ -422,16 +402,46 @@ class Postgres extends ADODB_base { return $temp; } + // Help functions + /** - * Returns the current schema to prepend on object names + * Fetch a URL (or array of URLs) for a given help page. */ - function schema() { - return ''; + function getHelp($help) { + $this->getHelpPages(); + + if (isset($this->help_page[$help])) { + if (is_array($this->help_page[$help])) { + $urls = array(); + foreach ($this->help_page[$help] as $link) { + $urls[] = $this->help_base . $link; + } + return $urls; + } else + return $this->help_base . $this->help_page[$help]; + } else + return null; + } + + function getHelpPages() { + include_once('./help/PostgresDoc83.php'); + return $this->help_page; } // Database functions /** + * Return all information about a particular database + * @param $database The name of the database to retrieve + * @return The database info + */ + function getDatabase($database) { + $this->clean($database); + $sql = "SELECT * FROM pg_database WHERE datname='{$database}'"; + return $this->selectSet($sql); + } + + /** * Return all database available on the server * @return A list of databases, sorted alphabetically */ @@ -443,7 +453,7 @@ class Postgres extends ADODB_base { if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser($server_info['username'])) { $username = $server_info['username']; $this->clean($username); - $clause = " AND pu.usename='{$username}'"; + $clause = " AND pr.rolname='{$username}'"; } else $clause = ''; @@ -453,14 +463,16 @@ class Postgres extends ADODB_base { $orderby = "ORDER BY pdb.datname"; if (!$conf['show_system']) - $where = "AND pdb.datname NOT IN ('template1')"; + $where = ' AND NOT pdb.datistemplate'; else - $where = ''; - - $sql = "SELECT pdb.datname, pu.usename AS datowner, pg_encoding_to_char(encoding) AS datencoding, - (SELECT description FROM pg_description pd WHERE pdb.oid=pd.objoid) AS datcomment - FROM pg_database pdb, pg_user pu - WHERE pdb.datdba = pu.usesysid + $where = ' AND pdb.datallowconn'; + + $sql = "SELECT pdb.datname AS datname, pr.rolname AS datowner, pg_encoding_to_char(encoding) AS datencoding, + (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pdb.oid=pd.objoid) AS datcomment, + (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace, + pg_catalog.pg_database_size(pdb.oid) as dbsize + FROM pg_catalog.pg_database pdb LEFT JOIN pg_catalog.pg_roles pr ON (pdb.datdba = pr.oid) + WHERE true {$where} {$clause} {$orderby}"; @@ -469,24 +481,24 @@ class Postgres extends ADODB_base { } /** - * Return the database owner of a db - * @param string $database the name of the database to get the owner for - * @return recordset of the db owner info + * Return the database comment of a db from the shared description table + * @param string $database the name of the database to get the comment for + * @return recordset of the db comment info */ - function getDatabaseOwner($database) { + function getDatabaseComment($database) { $this->clean($database); - $sql = "SELECT usename FROM pg_user, pg_database WHERE pg_user.usesysid = pg_database.datdba AND pg_database.datname = '{$database}' "; + $sql = "SELECT description FROM pg_catalog.pg_database JOIN pg_catalog.pg_shdescription ON (oid=objoid) WHERE pg_database.datname = '{$database}' "; return $this->selectSet($sql); } /** - * Return all information about a particular database - * @param $database The name of the database to retrieve - * @return The database info + * Return the database owner of a db + * @param string $database the name of the database to get the owner for + * @return recordset of the db owner info */ - function getDatabase($database) { + function getDatabaseOwner($database) { $this->clean($database); - $sql = "SELECT * FROM pg_database WHERE datname='{$database}'"; + $sql = "SELECT usename FROM pg_user, pg_database WHERE pg_user.usesysid = pg_database.datdba AND pg_database.datname = '{$database}' "; return $this->selectSet($sql); } @@ -507,12 +519,19 @@ class Postgres extends ADODB_base { } /** - * Sets the client encoding - * @param $encoding The encoding to for the client - * @return 0 success + * Returns the current default_with_oids setting + * @return default_with_oids setting */ - function setClientEncoding($encoding) { - return -99; + function getDefaultWithOid() { + // Try to avoid a query if at all possible (5) + if (function_exists('pg_parameter_status')) { + $default = pg_parameter_status($this->conn->_connectionID, 'default_with_oids'); + if ($default !== false) return $default; + } + + $sql = "SHOW default_with_oids"; + + return $this->selectField($sql, 'default_with_oids'); } /** @@ -547,7 +566,25 @@ class Postgres extends ADODB_base { } return 0; + } + /** + * Renames a database, note that this operation cannot be + * performed on a database that is currently being connected to + * @param string $oldName name of database to rename + * @param string $newName new name of database + * @return int 0 on success + */ + function alterDatabaseRename($oldName, $newName) { + $this->clean($oldName); + $this->clean($newName); + + if ($oldName != $newName) { + $sql = "ALTER DATABASE \"{$oldName}\" RENAME TO \"{$newName}\""; + return $this->execute($sql); + } + else //just return success, we're not going to do anything + return 0; } /** @@ -561,419 +598,617 @@ class Postgres extends ADODB_base { return $this->execute($sql); } - // Schema functions - /** - * Sets the current working schema. This is a do nothing method for - * < 7.3 and is just here for polymorphism's sake. - * @param $schema The the name of the schema to work in - * @return 0 success + * Changes ownership of a database + * This can only be done by a superuser or the owner of the database + * @param string $dbName database to change ownership of + * @param string $newOwner user that will own the database + * @return int 0 on success */ - function setSchema($schema) { - return 0; - } + function alterDatabaseOwner($dbName, $newOwner) { + $this->clean($dbName); + $this->clean($newOwner); - // Inheritance functions + $sql = "ALTER DATABASE \"{$dbName}\" OWNER TO \"{$newOwner}\""; + return $this->execute($sql); + } /** - * Finds the names and schemas of parent tables (in order) - * @param $table The table to find the parents for - * @return A recordset + * Alters a database + * the multiple return vals are for postgres 8+ which support more functionality in alter database + * @param $dbName The name of the database + * @param $newName new name for the database + * @param $newOwner The new owner for the database + * @return 0 success + * @return -1 transaction error + * @return -2 owner error + * @return -3 rename error + * @return -4 comment error */ - function getTableParents($table) { - $this->clean($table); + function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') { + $this->clean($dbName); + $this->clean($newName); + $this->clean($newOwner); + $this->clean($comment); - $sql = " - SELECT - NULL AS nspname, relname - FROM - pg_class pc, pg_inherits pi - WHERE - pc.oid=pi.inhparent - AND pi.inhrelid = (SELECT oid from pg_class WHERE relname='{$table}') - ORDER BY - pi.inhseqno - "; + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } - return $this->selectSet($sql); + if ($dbName != $newName) { + $status = $this->alterDatabaseRename($dbName, $newName); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } + } + + $status = $this->alterDatabaseOwner($newName, $newOwner); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; } + if (trim($comment) != '' ) { + $status = $this->setComment('DATABASE', $dbName, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; + } + } + return $this->endTransaction(); + } /** - * Finds the names and schemas of child tables - * @param $table The table to find the children for + * Returns prepared transactions information + * @param $database (optional) Find only prepared transactions executed in a specific database * @return A recordset */ - function getTableChildren($table) { - $this->clean($table); - - $sql = " - SELECT - NULL AS nspname, relname - FROM - pg_class pc, pg_inherits pi - WHERE - pc.oid=pi.inhrelid - AND pi.inhparent = (SELECT oid from pg_class WHERE relname='{$table}') - "; + function getPreparedXacts($database = null) { + if ($database === null) + $sql = "SELECT * FROM pg_prepared_xacts"; + else { + $this->clean($database); + $sql = "SELECT transaction, gid, prepared, owner FROM pg_prepared_xacts + WHERE database='{$database}' ORDER BY owner"; + } return $this->selectSet($sql); } - // Table functions - /** - * Returns table information - * @param $table The name of the table + * Searches all system catalogs to find objects that match a certain name. + * @param $term The search term + * @param $filter The object type to restrict to ('' means no restriction) * @return A recordset */ - function getTable($table) { - $this->clean($table); + function findObject($term, $filter) { + global $conf; - $sql = "SELECT pc.relname, - pg_get_userbyid(pc.relowner) AS relowner, - (SELECT description FROM pg_description pd - WHERE pc.oid=pd.objoid) AS relcomment - FROM pg_class pc - WHERE pc.relname='{$table}'"; + // Escape search term for ILIKE match + $term = str_replace('_', '\\_', $term); + $term = str_replace('%', '\\%', $term); + $this->clean($term); + $this->clean($filter); - return $this->selectSet($sql); + // Exclude system relations if necessary + if (!$conf['show_system']) { + // XXX: The mention of information_schema here is in the wrong place, but + // it's the quickest fix to exclude the info schema from 7.4 + $where = " AND pn.nspname NOT LIKE 'pg\\\\_%' AND pn.nspname != 'information_schema'"; + $lan_where = "AND pl.lanispl"; + } + else { + $where = ''; + $lan_where = ''; } - /** - * Return all tables in current database - * @param $all True to fetch all tables, false for just in current schema - * @return All tables, sorted alphabetically - */ - function getTables($all = false) { - global $conf; - if (!$conf['show_system'] || $all) $where = "AND c.relname NOT LIKE 'pg@_%' ESCAPE '@' "; - else $where = ''; - - $sql = "SELECT NULL AS nspname, c.relname, - (SELECT usename FROM pg_user u WHERE u.usesysid=c.relowner) AS relowner, - (SELECT description FROM pg_description pd WHERE c.oid=pd.objoid) AS relcomment, - reltuples::bigint AS reltuples - FROM pg_class c - WHERE c.relkind='r' - AND NOT EXISTS (SELECT 1 FROM pg_rewrite r WHERE r.ev_class = c.oid AND r.ev_type = '1') + // Apply outer filter + $sql = ''; + if ($filter != '') { + $sql = "SELECT * FROM ("; + } + + $sql .= " + SELECT 'SCHEMA' AS type, oid, NULL AS schemaname, NULL AS relname, nspname AS name + FROM pg_catalog.pg_namespace pn WHERE nspname ILIKE '%{$term}%' {$where} + UNION ALL + SELECT CASE WHEN relkind='r' THEN 'TABLE' WHEN relkind='v' THEN 'VIEW' WHEN relkind='S' THEN 'SEQUENCE' END, pc.oid, + pn.nspname, NULL, pc.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn + WHERE pc.relnamespace=pn.oid AND relkind IN ('r', 'v', 'S') AND relname ILIKE '%{$term}%' {$where} + UNION ALL + SELECT CASE WHEN pc.relkind='r' THEN 'COLUMNTABLE' ELSE 'COLUMNVIEW' END, NULL, pn.nspname, pc.relname, pa.attname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, + pg_catalog.pg_attribute pa WHERE pc.relnamespace=pn.oid AND pc.oid=pa.attrelid + AND pa.attname ILIKE '%{$term}%' AND pa.attnum > 0 AND NOT pa.attisdropped AND pc.relkind IN ('r', 'v') {$where} + UNION ALL + SELECT 'FUNCTION', pp.oid, pn.nspname, NULL, pp.proname || '(' || pg_catalog.oidvectortypes(pp.proargtypes) || ')' FROM pg_catalog.pg_proc pp, pg_catalog.pg_namespace pn + WHERE pp.pronamespace=pn.oid AND NOT pp.proisagg AND pp.proname ILIKE '%{$term}%' {$where} + UNION ALL + SELECT 'INDEX', NULL, pn.nspname, pc.relname, pc2.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, + pg_catalog.pg_index pi, pg_catalog.pg_class pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pi.indrelid + AND pi.indexrelid=pc2.oid + AND NOT EXISTS ( + SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c + ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) + WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p') + ) + AND pc2.relname ILIKE '%{$term}%' {$where} + UNION ALL + SELECT 'CONSTRAINTTABLE', NULL, pn.nspname, pc.relname, pc2.conname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, + pg_catalog.pg_constraint pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pc2.conrelid AND pc2.conrelid != 0 + AND CASE WHEN pc2.contype IN ('f', 'c') THEN TRUE ELSE NOT EXISTS ( + SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c + ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) + WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p') + ) END + AND pc2.conname ILIKE '%{$term}%' {$where} + UNION ALL + SELECT 'CONSTRAINTDOMAIN', pt.oid, pn.nspname, pt.typname, pc.conname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn, + pg_catalog.pg_constraint pc WHERE pt.typnamespace=pn.oid AND pt.oid=pc.contypid AND pc.contypid != 0 + AND pc.conname ILIKE '%{$term}%' {$where} + UNION ALL + SELECT 'TRIGGER', NULL, pn.nspname, pc.relname, pt.tgname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, + pg_catalog.pg_trigger pt WHERE pc.relnamespace=pn.oid AND pc.oid=pt.tgrelid + AND (NOT pt.tgisconstraint OR NOT EXISTS + (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c + ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) + WHERE d.classid = pt.tableoid AND d.objid = pt.oid AND d.deptype = 'i' AND c.contype = 'f')) + AND pt.tgname ILIKE '%{$term}%' {$where} + UNION ALL + SELECT 'RULETABLE', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r + JOIN pg_catalog.pg_class c ON c.oid = r.ev_class + LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace + WHERE c.relkind='r' AND r.rulename != '_RETURN' AND r.rulename ILIKE '%{$term}%' {$where} + UNION ALL + SELECT 'RULEVIEW', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r + JOIN pg_catalog.pg_class c ON c.oid = r.ev_class + LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace + WHERE c.relkind='v' AND r.rulename != '_RETURN' AND r.rulename ILIKE '%{$term}%' {$where} + "; + + // Add advanced objects if show_advanced is set + if ($conf['show_advanced']) { + $sql .= " + UNION ALL + SELECT CASE WHEN pt.typtype='d' THEN 'DOMAIN' ELSE 'TYPE' END, pt.oid, pn.nspname, NULL, + pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn + WHERE pt.typnamespace=pn.oid AND typname ILIKE '%{$term}%' + AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid)) {$where} - ORDER BY relname"; + UNION ALL + SELECT 'OPERATOR', po.oid, pn.nspname, NULL, po.oprname FROM pg_catalog.pg_operator po, pg_catalog.pg_namespace pn + WHERE po.oprnamespace=pn.oid AND oprname ILIKE '%{$term}%' {$where} + UNION ALL + SELECT 'CONVERSION', pc.oid, pn.nspname, NULL, pc.conname FROM pg_catalog.pg_conversion pc, + pg_catalog.pg_namespace pn WHERE pc.connamespace=pn.oid AND conname ILIKE '%{$term}%' {$where} + UNION ALL + SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_catalog.pg_language pl + WHERE lanname ILIKE '%{$term}%' {$lan_where} + UNION ALL + SELECT DISTINCT ON (p.proname) 'AGGREGATE', p.oid, pn.nspname, NULL, p.proname FROM pg_catalog.pg_proc p + LEFT JOIN pg_catalog.pg_namespace pn ON p.pronamespace=pn.oid + WHERE p.proisagg AND p.proname ILIKE '%{$term}%' {$where} + UNION ALL + SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, pn.nspname, NULL, po.opcname FROM pg_catalog.pg_opclass po, + pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid + AND po.opcname ILIKE '%{$term}%' {$where} + "; + } + // Otherwise just add domains + else { + $sql .= " + UNION ALL + SELECT 'DOMAIN', pt.oid, pn.nspname, NULL, + pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn + WHERE pt.typnamespace=pn.oid AND pt.typtype='d' AND typname ILIKE '%{$term}%' + AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid)) + {$where} + "; + } + + if ($filter != '') { + // We use like to make RULE, CONSTRAINT and COLUMN searches work + $sql .= ") AS sub WHERE type LIKE '{$filter}%' "; + } + + $sql .= "ORDER BY type, schemaname, relname, name"; + return $this->selectSet($sql); } /** - * Retrieve the attribute definition of a table - * @param $table The name of the table - * @param $field (optional) The name of a field to return - * @return All attributes in order + * Returns the specified variable information. + * @return the field */ - function getTableAttributes($table, $field = '') { - $this->clean($table); - $this->clean($field); - - if ($field == '') { - $sql = "SELECT - a.attname, t.typname as type, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, -1 AS attstattarget, a.attstorage, - (SELECT adsrc FROM pg_attrdef adef WHERE a.attrelid=adef.adrelid AND a.attnum=adef.adnum) AS adsrc, - a.attstorage AS typstorage, false AS attisserial, - (SELECT description FROM pg_description d WHERE d.objoid = a.oid) as comment - FROM - pg_attribute a, - pg_class c, - pg_type t - WHERE - c.relname = '{$table}' AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid - ORDER BY a.attnum"; - } - else { - $sql = "SELECT - a.attname, t.typname as type, t.typname as base_type, - a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, -1 AS attstattarget, a.attstorage, - (SELECT adsrc FROM pg_attrdef adef WHERE a.attrelid=adef.adrelid AND a.attnum=adef.adnum) AS adsrc, - a.attstorage AS typstorage, - (SELECT description FROM pg_description d WHERE d.objoid = a.oid) as comment - FROM - pg_attribute a , - pg_class c, - pg_type t - WHERE - c.relname = '{$table}' AND a.attname='{$field}' AND a.attrelid = c.oid AND a.atttypid = t.oid"; - } + function getVariable($setting) { + $sql = "SHOW $setting"; return $this->selectSet($sql); } /** - * Checks to see whether or not a table has a unique id column - * @param $table The table name - * @return True if it has a unique id, false otherwise - * @return -99 error + * Returns all available variable information. + * @return A recordset */ - function hasObjectID($table) { - // 7.0 and 7.1 always had an oid column - return true; + function getVariables() { + $sql = "SHOW ALL"; + + return $this->selectSet($sql); } + // Schema functons + /** - * Returns the current default_with_oids setting - * @return default_with_oids setting + * Returns the current schema to prepend on object names */ - function getDefaultWithOid() { - // 8.0 is the first release to have this setting - // Prior releases don't have this setting... oids always activated - return 'on'; + function schema() { + return "\"{$this->_schema}\"."; } /** - * Creates a new table in the database - * @param $name The name of the table - * @param $fields The number of fields - * @param $field An array of field names - * @param $type An array of field types - * @param $array An array of '' or '[]' for each type if it's an array or not - * @param $length An array of field lengths - * @param $notnull An array of not null - * @param $default An array of default values - * @param $withoutoids True if WITHOUT OIDS, false otherwise - * @param $colcomment An array of comments - * @param $comment Table comment - * @param $tablespace The tablespace name ('' means none/default) - * @param $uniquekey An Array indicating the fields that are unique (those indexes that are set) - * @param $primarykey An Array indicating the field used for the primarykey (those indexes that are set) - * @return 0 success - * @return -1 no fields supplied + * Return all schemas in the current database. This differs from the version + * in 7.3 only in that it considers the information_schema to be a system schema. + * @return All schemas, sorted alphabetically */ - function createTable($name, $fields, $field, $type, $array, $length, $notnull, - $default, $withoutoids, $colcomment, $tblcomment, $tablespace, - $uniquekey, $primarykey) { - $this->fieldClean($name); - $this->clean($tblcomment); + function getSchemas() { + global $conf, $slony; - $status = $this->beginTransaction(); - if ($status != 0) return -1; + if (!$conf['show_system']) { + $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'"; + if (isset($slony) && $slony->isEnabled()) { + $temp = $slony->slony_schema; + $this->clean($temp); + $where .= " AND nspname != '{$temp}'"; + } - $schema = $this->schema(); + } + else $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; + $sql = " + SELECT pn.nspname, pu.usename AS nspowner, + pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment + FROM pg_catalog.pg_namespace pn + LEFT JOIN pg_catalog.pg_user pu ON (pn.nspowner = pu.usesysid) + {$where} + ORDER BY nspname"; - $found = false; - $first = true; - $comment_sql = ''; //Accumulate comments for the columns - $sql = "CREATE TABLE {$schema}\"{$name}\" ("; - for ($i = 0; $i < $fields; $i++) { - $this->fieldClean($field[$i]); - $this->clean($type[$i]); - $this->clean($length[$i]); - $this->clean($colcomment[$i]); + return $this->selectSet($sql); + } - // Skip blank columns - for user convenience - if ($field[$i] == '' || $type[$i] == '') continue; - // If not the first column, add a comma - if (!$first) $sql .= ", "; - else $first = false; + /** + * Return all information relating to a schema + * @param $schema The name of the schema + * @return Schema information + */ + function getSchemaByName($schema) { + $this->clean($schema); + $sql = " + SELECT nspname, nspowner, nspacl, + pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment + FROM pg_catalog.pg_namespace pn + WHERE nspname='{$schema}'"; + return $this->selectSet($sql); + } - switch ($type[$i]) { - // Have to account for weird placing of length for with/without - // time zone types - case 'timestamp with time zone': - case 'timestamp without time zone': - $qual = substr($type[$i], 9); - $sql .= "\"{$field[$i]}\" timestamp"; - if ($length[$i] != '') $sql .= "({$length[$i]})"; - $sql .= $qual; - break; - case 'time with time zone': - case 'time without time zone': - $qual = substr($type[$i], 4); - $sql .= "\"{$field[$i]}\" time"; - if ($length[$i] != '') $sql .= "({$length[$i]})"; - $sql .= $qual; - break; - default: - $sql .= "\"{$field[$i]}\" {$type[$i]}"; - if ($length[$i] != '') $sql .= "({$length[$i]})"; + /** + * Sets the current working schema. Will also set Class variable. + * @param $schema The the name of the schema to work in + * @return 0 success + */ + function setSchema($schema) { + // Get the current schema search path, including 'pg_catalog'. + $search_path = $this->getSearchPath(); + // Prepend $schema to search path + array_unshift($search_path, $schema); + $status = $this->setSearchPath($search_path); + if ($status == 0) { + $this->clean($schema); + $this->_schema = $schema; + return 0; } - // Add array qualifier if necessary - if ($array[$i] == '[]') $sql .= '[]'; - // Add other qualifiers - if (!isset($primarykey[$i])) { - if (isset($uniquekey[$i])) $sql .= " UNIQUE"; - if (isset($notnull[$i])) $sql .= " NOT NULL"; + else return $status; } - if ($default[$i] != '') $sql .= " DEFAULT {$default[$i]}"; - if ($colcomment[$i] != '') $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n"; + /** + * Sets the current schema search path + * @param $paths An array of schemas in required search order + * @return 0 success + * @return -1 Array not passed + * @return -2 Array must contain at least one item + */ + function setSearchPath($paths) { + if (!is_array($paths)) return -1; + elseif (sizeof($paths) == 0) return -2; + elseif (sizeof($paths) == 1 && $paths[0] == '') { + // Need to handle empty paths in some cases + $paths[0] = 'pg_catalog'; + } - $found = true; + // Loop over all the paths to check that none are empty + $temp = array(); + foreach ($paths as $schema) { + if ($schema != '') $temp[] = $schema; } + $this->fieldArrayClean($temp); - if (!$found) return -1; + $sql = 'SET SEARCH_PATH TO "' . implode('","', $temp) . '"'; - // PRIMARY KEY - $primarykeycolumns = array(); - for ($i = 0; $i < $fields; $i++) { - if (isset($primarykey[$i])) { - $primarykeycolumns[] = "\"{$field[$i]}\""; - } - } - if (count($primarykeycolumns) > 0) { - $sql .= ", PRIMARY KEY (" . implode(", ", $primarykeycolumns) . ")"; + return $this->execute($sql); } - $sql .= ")"; + /** + * Creates a new schema. + * @param $schemaname The name of the schema to create + * @param $authorization (optional) The username to create the schema for. + * @param $comment (optional) If omitted, defaults to nothing + * @return 0 success + */ + function createSchema($schemaname, $authorization = '', $comment = '') { + $this->fieldClean($schemaname); + $this->fieldClean($authorization); + $this->clean($comment); - // WITHOUT OIDS - if ($this->hasWithoutOIDs() && $withoutoids) - $sql .= ' WITHOUT OIDS'; + $sql = "CREATE SCHEMA \"{$schemaname}\""; + if ($authorization != '') $sql .= " AUTHORIZATION \"{$authorization}\""; - // Tablespace - if ($this->hasTablespaces() && $tablespace != '') { - $this->fieldClean($tablespace); - $sql .= " TABLESPACE \"{$tablespace}\""; + if ($comment != '') { + $status = $this->beginTransaction(); + if ($status != 0) return -1; } + // Create the new schema $status = $this->execute($sql); - if ($status) { + if ($status != 0) { $this->rollbackTransaction(); return -1; } - if ($tblcomment != '') { - $status = $this->setComment('TABLE', '', $name, $tblcomment, true); - if ($status) { + // Set the comment + if ($comment != '') { + $status = $this->setComment('SCHEMA', $schemaname, '', $comment); + if ($status != 0) { $this->rollbackTransaction(); return -1; } - } - if ($comment_sql != '') { - $status = $this->execute($comment_sql); - if ($status) { - $this->rollbackTransaction(); - return -1; - } - } return $this->endTransaction(); + } + return 0; } /** - * Protected method which alter a table - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $tblrs The table recordSet returned by getTable() - * @param $name The new name for the table - * @param $owner The new owner for the table - * @param $schema The new schema for the table - * @param $comment The comment on the table - * @param $tablespace The new tablespace for the table ('' means leave as is) + * Updates a schema. + * @param $schemaname The name of the schema to drop + * @param $comment The new comment for this schema * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 tablespace error - * @return -7 schema error */ - /* protected */ - function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) { - + function updateSchema($schemaname, $comment, $name) { + $this->fieldClean($schemaname); $this->fieldClean($name); $this->clean($comment); - /* $schema, $owner, $tablespace not supported in pg70 */ - $table = $tblrs->fields['relname']; + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } - // Comment - $status = $this->setComment('TABLE', '', $table, $comment); - if ($status != 0) return -4; + $status = $this->setComment('SCHEMA', $schemaname, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } - // Rename (only if name has changed) - if ($name != $table) { - $sql = "ALTER TABLE \"{$table}\" RENAME TO \"{$name}\""; + // Only if the name has changed + if ($name != $schemaname) { + $sql = "ALTER SCHEMA \"{$schemaname}\" RENAME TO \"{$name}\""; $status = $this->execute($sql); - if ($status != 0) return -3; + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } } - return 0; + return $this->endTransaction(); } /** - * Alter table properties - * @param $table The name of the table - * @param $name The new name for the table - * @param $owner The new owner for the table - * @param $schema The new schema for the table - * @param $comment The comment on the table - * @param $tablespace The new tablespace for the table ('' means leave as is) + * Drops a schema. + * @param $schemaname The name of the schema to drop + * @param $cascade True to cascade drop, false to restrict * @return 0 success - * @return -1 transaction error - * @return -2 get existing table error - * @return $this->_alterTable error code */ - function alterTable($table, $name, $owner, $schema, $comment, $tablespace) { + function dropSchema($schemaname, $cascade) { + $this->fieldClean($schemaname); - $this->fieldClean($table); - $data = $this->getTable($table); - if ($data->recordCount() != 1) - return -2; + $sql = "DROP SCHEMA \"{$schemaname}\""; + if ($cascade) $sql .= " CASCADE"; - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; + return $this->execute($sql); } - $status = $this->_alterTable($data, $name, $owner, $schema, $comment, $tablespace); + /** + * Return the current schema search path + * @return Array of schema names + */ + function getSearchPath() { + $sql = 'SELECT current_schemas(false) AS search_path'; - if ($status != 0) { - $this->rollbackTransaction(); - return $status; + return $this->phpArray($this->selectField($sql, 'search_path')); } - return $this->endTransaction(); + // Table functions + + /** + * Returns table information + * @param $table The name of the table + * @return A recordset + */ + function getTable($table) { + $this->clean($table); + + $sql = " + SELECT + c.relname, n.nspname, u.usename AS relowner, + pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment, + (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind = 'r' + AND n.nspname = '{$this->_schema}' + AND n.oid = c.relnamespace + AND c.relname = '{$table}'"; + + return $this->selectSet($sql); } /** - * Removes a table from the database - * @param $table The table to drop - * @param $cascade True to cascade drop, false to restrict - * @return 0 success + * Return all tables in current database (and schema) + * @param $all True to fetch all tables, false for just in current schema + * @return All tables, sorted alphabetically */ - function dropTable($table, $cascade) { - $this->fieldClean($table); + function getTables($all = false) { + if ($all) { + // Exclude pg_catalog and information_schema tables + $sql = "SELECT schemaname AS nspname, tablename AS relname, tableowner AS relowner + FROM pg_catalog.pg_tables + WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') + ORDER BY schemaname, tablename"; + } else { + $sql = "SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, + pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment, + reltuples::bigint, + (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind = 'r' + AND nspname='{$this->_schema}' + ORDER BY c.relname"; + } - $sql = "DROP TABLE \"{$table}\""; - if ($cascade) $sql .= " CASCADE"; + return $this->selectSet($sql); + } - return $this->execute($sql); + /** + * Retrieve the attribute definition of a table + * @param $table The name of the table + * @param $field (optional) The name of a field to return + * @return All attributes in order + */ + function getTableAttributes($table, $field = '') { + $this->clean($table); + $this->clean($field); + + if ($field == '') { + // This query is made much more complex by the addition of the 'attisserial' field. + // The subquery to get that field checks to see if there is an internally dependent + // sequence on the field. + $sql = " + SELECT + a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod) as type, + a.atttypmod, + a.attnotnull, a.atthasdef, adef.adsrc, + a.attstattarget, a.attstorage, t.typstorage, + ( + SELECT 1 FROM pg_catalog.pg_depend pd, pg_catalog.pg_class pc + WHERE pd.objid=pc.oid + AND pd.classid=pc.tableoid + AND pd.refclassid=pc.tableoid + AND pd.refobjid=a.attrelid + AND pd.refobjsubid=a.attnum + AND pd.deptype='i' + AND pc.relkind='S' + ) IS NOT NULL AS attisserial, + pg_catalog.col_description(a.attrelid, a.attnum) AS comment + + FROM + pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef + ON a.attrelid=adef.adrelid + AND a.attnum=adef.adnum + LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid + WHERE + a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' + AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE + nspname = '{$this->_schema}')) + AND a.attnum > 0 AND NOT a.attisdropped + ORDER BY a.attnum"; + } + else { + $sql = " + SELECT + a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod) as type, + pg_catalog.format_type(a.atttypid, NULL) as base_type, + a.atttypmod, + a.attnotnull, a.atthasdef, adef.adsrc, + a.attstattarget, a.attstorage, t.typstorage, + pg_catalog.col_description(a.attrelid, a.attnum) AS comment + FROM + pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef + ON a.attrelid=adef.adrelid + AND a.attnum=adef.adnum + LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid + WHERE + a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' + AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE + nspname = '{$this->_schema}')) + AND a.attname = '{$field}'"; + } + + return $this->selectSet($sql); } /** - * Empties a table in the database - * @param $table The table to be emptied - * @return 0 success + * Finds the names and schemas of parent tables (in order) + * @param $table The table to find the parents for + * @return A recordset */ - function emptyTable($table) { - $this->fieldClean($table); + function getTableParents($table) { + $this->clean($table); - $sql = "DELETE FROM \"{$table}\""; + $sql = " + SELECT + pn.nspname, relname + FROM + pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn + WHERE + pc.oid=pi.inhparent + AND pc.relnamespace=pn.oid + AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}' + AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$this->_schema}')) + ORDER BY + pi.inhseqno + "; - return $this->execute($sql); + return $this->selectSet($sql); } /** - * Renames a table - * @param $table The table to be renamed - * @param $newName The new name for the table - * @return 0 success + * Finds the names and schemas of child tables + * @param $table The table to find the children for + * @return A recordset */ - /*XXX FIXME !! NOT USED ANYMORE ?? - function renameTable($table, $newName) { - $this->fieldClean($table); - $this->fieldClean($newName); + function getTableChildren($table) { + $this->clean($table); - $sql = "ALTER TABLE \"{$table}\" RENAME TO \"{$newName}\""; + $sql = " + SELECT + pn.nspname, relname + FROM + pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn + WHERE + pc.oid=pi.inhrelid + AND pc.relnamespace=pn.oid + AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}' + AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$this->_schema}')) + "; - return $this->execute($sql); - }*/ + return $this->selectSet($sql); + } /** * Returns the SQL definition for the table. @@ -1351,9 +1586,349 @@ class Postgres extends ADODB_base { } /** + * Creates a new table in the database + * @param $name The name of the table + * @param $fields The number of fields + * @param $field An array of field names + * @param $type An array of field types + * @param $array An array of '' or '[]' for each type if it's an array or not + * @param $length An array of field lengths + * @param $notnull An array of not null + * @param $default An array of default values + * @param $withoutoids True if WITHOUT OIDS, false otherwise + * @param $colcomment An array of comments + * @param $comment Table comment + * @param $tablespace The tablespace name ('' means none/default) + * @param $uniquekey An Array indicating the fields that are unique (those indexes that are set) + * @param $primarykey An Array indicating the field used for the primarykey (those indexes that are set) + * @return 0 success + * @return -1 no fields supplied + */ + function createTable($name, $fields, $field, $type, $array, $length, $notnull, + $default, $withoutoids, $colcomment, $tblcomment, $tablespace, + $uniquekey, $primarykey) { + $this->fieldClean($name); + $this->clean($tblcomment); + + $status = $this->beginTransaction(); + if ($status != 0) return -1; + + $schema = $this->schema(); + + $found = false; + $first = true; + $comment_sql = ''; //Accumulate comments for the columns + $sql = "CREATE TABLE {$schema}\"{$name}\" ("; + for ($i = 0; $i < $fields; $i++) { + $this->fieldClean($field[$i]); + $this->clean($type[$i]); + $this->clean($length[$i]); + $this->clean($colcomment[$i]); + + // Skip blank columns - for user convenience + if ($field[$i] == '' || $type[$i] == '') continue; + // If not the first column, add a comma + if (!$first) $sql .= ", "; + else $first = false; + + switch ($type[$i]) { + // Have to account for weird placing of length for with/without + // time zone types + case 'timestamp with time zone': + case 'timestamp without time zone': + $qual = substr($type[$i], 9); + $sql .= "\"{$field[$i]}\" timestamp"; + if ($length[$i] != '') $sql .= "({$length[$i]})"; + $sql .= $qual; + break; + case 'time with time zone': + case 'time without time zone': + $qual = substr($type[$i], 4); + $sql .= "\"{$field[$i]}\" time"; + if ($length[$i] != '') $sql .= "({$length[$i]})"; + $sql .= $qual; + break; + default: + $sql .= "\"{$field[$i]}\" {$type[$i]}"; + if ($length[$i] != '') $sql .= "({$length[$i]})"; + } + // Add array qualifier if necessary + if ($array[$i] == '[]') $sql .= '[]'; + // Add other qualifiers + if (!isset($primarykey[$i])) { + if (isset($uniquekey[$i])) $sql .= " UNIQUE"; + if (isset($notnull[$i])) $sql .= " NOT NULL"; + } + if ($default[$i] != '') $sql .= " DEFAULT {$default[$i]}"; + + if ($colcomment[$i] != '') $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n"; + + $found = true; + } + + if (!$found) return -1; + + // PRIMARY KEY + $primarykeycolumns = array(); + for ($i = 0; $i < $fields; $i++) { + if (isset($primarykey[$i])) { + $primarykeycolumns[] = "\"{$field[$i]}\""; + } + } + if (count($primarykeycolumns) > 0) { + $sql .= ", PRIMARY KEY (" . implode(", ", $primarykeycolumns) . ")"; + } + + $sql .= ")"; + + // WITHOUT OIDS + if ($this->hasWithoutOIDs() && $withoutoids) + $sql .= ' WITHOUT OIDS'; + + // Tablespace + if ($this->hasTablespaces() && $tablespace != '') { + $this->fieldClean($tablespace); + $sql .= " TABLESPACE \"{$tablespace}\""; + } + + $status = $this->execute($sql); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + + if ($tblcomment != '') { + $status = $this->setComment('TABLE', '', $name, $tblcomment, true); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + } + + if ($comment_sql != '') { + $status = $this->execute($comment_sql); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + } + return $this->endTransaction(); + } + + /** + * Creates a new table in the database copying attribs and other properties from another table + * @param $name The name of the table + * @param $like an array giving the schema ans the name of the table from which attribs are copying from: + * array( + * 'table' => table name, + * 'schema' => the schema name, + * ) + * @param $defaults if true, copy the defaults values as well + * @param $constraints if true, copy the constraints as well (CHECK on table & attr) + * @param $tablespace The tablespace name ('' means none/default) + */ + function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '') { + $this->fieldClean($name); + + $this->fieldClean($like['schema']); + $this->fieldClean($like['table']); + $like = "\"{$like['schema']}\".\"{$like['table']}\""; + + $status = $this->beginTransaction(); + if ($status != 0) return -1; + + $sql = "CREATE TABLE \"{$this->_schema}\".\"{$name}\" (LIKE {$like}"; + + if ($defaults) $sql .= " INCLUDING DEFAULTS"; + if ($this->hasCreateTableLikeWithConstraints() && $constraints) $sql .= " INCLUDING CONSTRAINTS"; + if ($this->hasCreateTableLikeWithIndexes() && $idx) $sql .= " INCLUDING INDEXES"; + + $sql .= ")"; + + if ($this->hasTablespaces() && $tablespace != '') { + $this->fieldClean($tablespace); + $sql .= " TABLESPACE \"{$tablespace}\""; + } + + $status = $this->execute($sql); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + + return $this->endTransaction(); + } + + /** + * Alter a table's name + * @param $tblrs The table RecordSet returned by getTable() + * @param $name The new table's name + * @return 0 success + */ + function alterTableName($tblrs, $name = null) { + // Rename (only if name has changed) + if (!empty($name) && ($name != $tblrs->fields['relname'])) { + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$tblrs->fields['relname']}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); + if ($status == 0) + $tblrs->fields['relname'] = $name; + else + return $status; + } + return 0; + } + + /** + * Alter a table's owner + * @param $tblrs The table RecordSet returned by getTable() + * @param $name The new table's owner + * @return 0 success + */ + function alterTableOwner($tblrs, $owner = null) { + if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) { + // If owner has been changed, then do the alteration. We are + // careful to avoid this generally as changing owner is a + // superuser only function. + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$tblrs->fields['relname']}\" OWNER TO \"{$owner}\""; + + return $this->execute($sql); + } + return 0; + } + + /** + * Alter a table's tablespace + * @param $tblrs The table RecordSet returned by getTable() + * @param $name The new table's tablespace + * @return 0 success + */ + function alterTableTablespace($tblrs, $tablespace = null) { + if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) { + // If tablespace has been changed, then do the alteration. We + // don't want to do this unnecessarily. + $sql = "ALTER TABLE \"{$tblrs->fields['relname']}\" SET TABLESPACE \"{$tablespace}\""; + + return $this->execute($sql); + } + return 0; + } + + /** + * Alter a table's schema + * @param $tblrs The table RecordSet returned by getTable() + * @param $name The new table's schema + * @return 0 success + */ + function alterTableSchema($tblrs, $schema = null) { + if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) { + // If tablespace has been changed, then do the alteration. We + // don't want to do this unnecessarily. + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$tblrs->fields['relname']}\" SET SCHEMA \"{$schema}\""; + + return $this->execute($sql); + } + return 0; + } + + /** + * Protected method which alter a table + * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION + * @param $tblrs The table recordSet returned by getTable() + * @param $name The new name for the table + * @param $owner The new owner for the table + * @param $schema The new schema for the table + * @param $comment The comment on the table + * @param $tablespace The new tablespace for the table ('' means leave as is) + * @return 0 success + * @return -3 rename error + * @return -4 comment error + * @return -5 owner error + * @return -6 tablespace error + * @return -7 schema error + */ + protected + function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) { + + $this->fieldArrayClean($tblrs->fields); + + // Comment + $this->clean($comment); + $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment); + if ($status != 0) return -4; + + // Owner + $this->fieldClean($owner); + $status = $this->alterTableOwner($tblrs, $owner); + if ($status != 0) return -5; + + // Tablespace + $this->fieldClean($tablespace); + $status = $this->alterTableTablespace($tblrs, $tablespace); + if ($status != 0) return -6; + + // Rename + $this->fieldClean($name); + $status = $this->alterTableName($tblrs, $name); + if ($status != 0) return -3; + + // Schema + $this->fieldClean($schema); + $status = $this->alterTableSchema($tblrs, $schema); + if ($status != 0) return -7; + + return 0; + } + + /** + * Alter table properties + * @param $table The name of the table + * @param $name The new name for the table + * @param $owner The new owner for the table + * @param $schema The new schema for the table + * @param $comment The comment on the table + * @param $tablespace The new tablespace for the table ('' means leave as is) + * @return 0 success + * @return -1 transaction error + * @return -2 get existing table error + * @return $this->_alterTable error code + */ + function alterTable($table, $name, $owner, $schema, $comment, $tablespace) { + + $this->fieldClean($table); + $data = $this->getTable($table); + + if ($data->recordCount() != 1) + return -2; + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $status = $this->_alterTable($data, $name, $owner, $schema, $comment, $tablespace); + + if ($status != 0) { + $this->rollbackTransaction(); + return $status; + } + + return $this->endTransaction(); + } + + /** + * Returns the SQL for changing the current user + * @param $user The user to change to + * @return The SQL + */ + function getChangeUserSQL($user) { + $this->clean($user); + return "SET SESSION AUTHORIZATION '{$user}';"; + } + + /** * Given an array of attnums and a relation, returns an array mapping - * atttribute number to attribute name. Relation could be a table OR - * a view. + * attribute number to attribute name. * @param $table The table to get attributes for * @param $atts An array of attribute numbers * @return An array mapping attnum to attname @@ -1368,13 +1943,15 @@ class Postgres extends ADODB_base { if (sizeof($atts) == 0) return array(); - $sql = "SELECT attnum, attname FROM pg_attribute WHERE attrelid=(SELECT oid FROM pg_class WHERE relname='{$table}') AND attnum IN ('" . - join("','", $atts) . "')"; + $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE + attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND + relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}')) + AND attnum IN ('" . join("','", $atts) . "')"; $rs = $this->selectSet($sql); if ($rs->recordCount() != sizeof($atts)) { - return -2; - } + return -2; + } else { $temp = array(); while (!$rs->EOF) { @@ -1386,6 +1963,34 @@ class Postgres extends ADODB_base { } /** + * Empties a table in the database + * @param $table The table to be emptied + * @return 0 success + */ + function emptyTable($table) { + $this->fieldClean($table); + + $sql = "DELETE FROM \"{$this->_schema}\".\"{$table}\""; + + return $this->execute($sql); + } + + /** + * Removes a table from the database + * @param $table The table to drop + * @param $cascade True to cascade drop, false to restrict + * @return 0 success + */ + function dropTable($table, $cascade) { + $this->fieldClean($table); + + $sql = "DROP TABLE \"{$this->_schema}\".\"{$table}\""; + if ($cascade) $sql .= " CASCADE"; + + return $this->execute($sql); + } + + /** * Add a new column to a table * @param $table The table to add to * @param $column The name of the new column @@ -1404,7 +2009,7 @@ class Postgres extends ADODB_base { $this->clean($comment); $schema = $this->schema(); - + if ($length == '') $sql = "ALTER TABLE {$schema}\"{$table}\" ADD COLUMN \"{$column}\" {$type}"; else { @@ -1443,99 +2048,130 @@ class Postgres extends ADODB_base { $status = $this->execute($sql); if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } + $this->rollbackTransaction(); + return -1; + } $status = $this->setComment('COLUMN', $column, $table, $comment); if ($status != 0) { $this->rollbackTransaction(); return -1; - } + } return $this->endTransaction(); } /** - * Sets default value of a column - * @param $table The table from which to drop - * @param $column The column name to set - * @param $default The new default value + * Alters a column in a table + * @param $table The table in which the column resides + * @param $column The column to alter + * @param $name The new name for the column + * @param $notnull (boolean) True if not null, false otherwise + * @param $oldnotnull (boolean) True if column is already not null, false otherwise + * @param $default The new default for the column + * @param $olddefault The old default for the column + * @param $type The new type for the column + * @param $array True if array type, false otherwise + * @param $length The optional size of the column (ie. 30 for varchar(30)) + * @param $oldtype The old type for the column + * @param $comment Comment for the column * @return 0 success + * @return -1 batch alteration failed + * @return -3 rename column error + * @return -4 comment error + * @return -6 transaction error */ - function setColumnDefault($table, $column, $default) { + function alterColumn($table, $column, $name, $notnull, $oldnotnull, $default, $olddefault, + $type, $length, $array, $oldtype, $comment) { $this->fieldClean($table); $this->fieldClean($column); + $this->clean($comment); - $sql = "ALTER TABLE \"{$table}\" ALTER COLUMN \"{$column}\" SET DEFAULT {$default}"; + // Initialise an empty SQL string + $sql = ''; - return $this->execute($sql); + // Create the command for changing nullability + if ($notnull != $oldnotnull) { + $sql .= "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . (($notnull) ? 'SET' : 'DROP') . " NOT NULL"; } - /** - * Drops default value of a column - * @param $table The table from which to drop - * @param $column The column name to drop default - * @return 0 success - */ - function dropColumnDefault($table, $column) { - $this->fieldClean($table); - $this->fieldClean($column); + // Add default, if it has changed + if ($default != $olddefault) { + if ($default == '') { + if ($sql == '') $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" "; + else $sql .= ", "; + $sql .= "ALTER COLUMN \"{$column}\" DROP DEFAULT"; + } + else { + if ($sql == '') $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" "; + else $sql .= ", "; + $sql .= "ALTER COLUMN \"{$column}\" SET DEFAULT {$default}"; + } + } - $sql = "ALTER TABLE \"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT"; + // Add type, if it has changed + if ($length == '') + $ftype = $type; + else { + switch ($type) { + // Have to account for weird placing of length for with/without + // time zone types + case 'timestamp with time zone': + case 'timestamp without time zone': + $qual = substr($type, 9); + $ftype = "timestamp({$length}){$qual}"; + break; + case 'time with time zone': + case 'time without time zone': + $qual = substr($type, 4); + $ftype = "time({$length}){$qual}"; + break; + default: + $ftype = "{$type}({$length})"; + } + } - return $this->execute($sql); - } + // Add array qualifier, if requested + if ($array) $ftype .= '[]'; - /** - * Sets whether or not a column can contain NULLs - * @param $table The table that contains the column - * @param $column The column to alter - * @param $state True to set null, false to set not null - * @return 0 success - * @return -1 attempt to set not null, but column contains nulls - * @return -2 transaction error - * @return -3 lock error - * @return -4 update error - */ - function setColumnNull($table, $column, $state) { - $this->fieldClean($table); - $this->fieldClean($column); + if ($ftype != $oldtype) { + if ($sql == '') $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" "; + else $sql .= ", "; + $sql .= "ALTER COLUMN \"{$column}\" TYPE {$ftype}"; + } // Begin transaction $status = $this->beginTransaction(); - if ($status != 0) return -2; - - // Properly lock the table - $sql = "LOCK TABLE \"{$table}\" IN ACCESS EXCLUSIVE MODE"; - $status = $this->execute($sql); if ($status != 0) { $this->rollbackTransaction(); - return -3; + return -6; } - // Check for existing nulls - if (!$state) { - $sql = "SELECT COUNT(*) AS total FROM \"{$table}\" WHERE \"{$column}\" IS NULL"; - $result = $this->selectField($sql, 'total'); - if ($result > 0) { + // Attempt to process the batch alteration, if anything has been changed + if ($sql != '') { + $status = $this->execute($sql); + if ($status != 0) { $this->rollbackTransaction(); return -1; } } - // Otherwise update the table. Note the reverse-sensed $state variable - $sql = "UPDATE pg_attribute SET attnotnull = " . (($state) ? 'false' : 'true') . " - WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = '{$table}') - AND attname = '{$column}'"; - - $status = $this->execute($sql); + // Update the comment on the column + $status = $this->setComment('COLUMN', $column, $table, $comment); if ($status != 0) { - $this->rollbackTransaction(); - return -4; + $this->rollbackTransaction(); + return -4; + } + + // Rename the column, if it has been changed + if ($column != $name) { + $status = $this->renameColumn($table, $column, $name); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } } - // Otherwise, close the transaction return $this->endTransaction(); } @@ -1551,7 +2187,39 @@ class Postgres extends ADODB_base { $this->fieldClean($column); $this->fieldClean($newName); - $sql = "ALTER TABLE \"{$table}\" RENAME COLUMN \"{$column}\" TO \"{$newName}\""; + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" RENAME COLUMN \"{$column}\" TO \"{$newName}\""; + + return $this->execute($sql); + } + + /** + * Sets default value of a column + * @param $table The table from which to drop + * @param $column The column name to set + * @param $default The new default value + * @return 0 success + */ + function setColumnDefault($table, $column, $default) { + $this->fieldClean($table); + $this->fieldClean($column); + + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" SET DEFAULT {$default}"; + + return $this->execute($sql); + } + + /** + * Sets whether or not a column can contain NULLs + * @param $table The table that contains the column + * @param $column The column to alter + * @param $state True to set null, false to set not null + * @return 0 success + */ + function setColumnNull($table, $column, $state) { + $this->fieldClean($table); + $this->fieldClean($column); + + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . (($state) ? 'DROP' : 'SET') . " NOT NULL"; return $this->execute($sql); } @@ -1562,112 +2230,173 @@ class Postgres extends ADODB_base { * @param $column The column to be dropped * @param $cascade True to cascade drop, false to restrict * @return 0 success - * @return -99 not implemented */ function dropColumn($table, $column, $cascade) { - return -99; + $this->fieldClean($table); + $this->fieldClean($column); + + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" DROP COLUMN \"{$column}\""; + if ($cascade) $sql .= " CASCADE"; + + return $this->execute($sql); } /** - * Alters a column in a table OR view - * @param $table The table in which the column resides - * @param $column The column to alter - * @param $name The new name for the column - * @param $notnull (boolean) True if not null, false otherwise - * @param $oldnotnull (boolean) True if column is already not null, false otherwise - * @param $default The new default for the column - * @param $olddefault The old default for the column - * @param $type The new type for the column - * @param $array True if array type, false otherwise - * @param $length The optional size of the column (ie. 30 for varchar(30)) - * @param $oldtype The old type for the column - * @param $comment Comment for the column + * Drops default value of a column + * @param $table The table from which to drop + * @param $column The column name to drop default * @return 0 success - * @return -1 set not null error - * @return -2 set default error - * @return -3 rename column error - * @return -4 comment error */ - function alterColumn($table, $column, $name, $notnull, $oldnotnull, $default, $olddefault, - $type, $length, $array, $oldtype, $comment) { - $this->beginTransaction(); - - // @@ NEED TO HANDLE "NESTED" TRANSACTION HERE - if ($notnull != $oldnotnull) { - $status = $this->setColumnNull($table, $column, !$notnull); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - } + function dropColumnDefault($table, $column) { + $this->fieldClean($table); + $this->fieldClean($column); - // Set default, if it has changed - if ($default != $olddefault) { - if ($default == '') - $status = $this->dropColumnDefault($table, $column); - else - $status = $this->setColumnDefault($table, $column, $default); + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT"; - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } - } + return $this->execute($sql); + } - // Rename the column, if it has been changed - if ($column != $name) { - $status = $this->renameColumn($table, $column, $name); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - } + /** + * Sets up the data object for a dump. eg. Starts the appropriate + * transaction, sets variables, etc. + * @return 0 success + */ + function beginDump() { + $status = parent::beginDump(); + if ($status != 0) return $status; - // Parameters must be cleaned for the setComment function. It's ok to do - // that here since this is the last time these variables are used. - $this->fieldClean($name); - $this->fieldClean($table); - $this->clean($comment); - $status = $this->setComment('COLUMN', $name, $table, $comment); + // Set extra_float_digits to 2 + $sql = "SET extra_float_digits TO 2"; + $status = $this->execute($sql); if ($status != 0) { - $this->rollbackTransaction(); - return -4; + $this->rollbackTransaction(); + return -1; + } } + /** + * Ends the data object for a dump. + * @return 0 success + */ + function endDump() { return $this->endTransaction(); } + /** + * Returns a recordset of all columns in a relation. Used for data export. + * @@ Note: Really needs to use a cursor + * @param $relation The name of a relation + * @return A recordset on success + * @return -1 Failed to set datestyle + */ + function dumpRelation($relation, $oids) { + $this->fieldClean($relation); + + // Actually retrieve the rows + if ($oids) $oid_str = $this->id . ', '; + else $oid_str = ''; + + return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\""); + } + // Row functions /** - * Delete a row from a table - * @param $table The table from which to delete - * @param $key An array mapping column => value to delete - * @return 0 success + * Get the fields for uniquely identifying a row in a table + * @param $table The table for which to retrieve the identifier + * @return An array mapping attribute number to attribute name, empty for no identifiers + * @return -1 error */ - function deleteRow($table, $key) { - if (!is_array($key)) return -1; + function getRowIdentifier($table) { + $oldtable = $table; + $this->clean($table); + + $status = $this->beginTransaction(); + if ($status != 0) return -1; + + // Get the first primary or unique index (sorting primary keys first) that + // is NOT a partial index. + $sql = " + SELECT indrelid, indkey + FROM pg_catalog.pg_index + WHERE indisunique AND indrelid=( + SELECT oid FROM pg_catalog.pg_class + WHERE relname='{$table}' AND relnamespace=( + SELECT oid FROM pg_catalog.pg_namespace + WHERE nspname='{$this->_schema}' + ) + ) AND indpred IS NULL AND indexprs IS NULL + ORDER BY indisprimary DESC LIMIT 1"; + $rs = $this->selectSet($sql); + + // If none, check for an OID column. Even though OIDs can be duplicated, the edit and delete row + // functions check that they're only modiying a single row. Otherwise, return empty array. + if ($rs->recordCount() == 0) { + // Check for OID column + $temp = array(); + if ($this->hasObjectID($table)) { + $temp = array('oid'); + } + $this->endTransaction(); + return $temp; + } + // Otherwise find the names of the keys else { - // Begin transaction. We do this so that we can ensure only one row is - // deleted - $status = $this->beginTransaction(); - if ($status != 0) { + $attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey'])); + if (!is_array($attnames)) { $this->rollbackTransaction(); return -1; } - - $status = $this->delete($table, $key, $this->_schema); - if ($status != 0 || $this->conn->Affected_Rows() != 1) { - $this->rollbackTransaction(); - return -2; + else { + $this->endTransaction(); + return $attnames; } - - // End transaction - return $this->endTransaction(); } } /** + * Adds a new row to a table + * @param $table The table in which to insert + * @param $var An array mapping new values for the row + * @param $nulls An array mapping column => something if it is to be null + * @param $format An array of the data type (VALUE or EXPRESSION) + * @param $types An array of field types + * @return 0 success + * @return -1 invalid parameters + */ + function insertRow($table, $vars, $nulls, $format, $types) { + + if (!is_array($vars) || !is_array($nulls) || !is_array($format) + || !is_array($types)) return -1; + else { + $this->fieldClean($table); + + $schema = $this->schema(); + + // Build clause + if (sizeof($vars) > 0) { + $fields = ''; + $values = ''; + foreach($vars as $key => $value) { + $this->fieldClean($key); + + // Handle NULL values + if (isset($nulls[$key])) $tmp = 'NULL'; + else $tmp = $this->formatValue($types[$key], $format[$key], $value); + + if ($fields) $fields .= ", \"{$key}\""; + else $fields = "INSERT INTO {$schema}\"{$table}\" (\"{$key}\""; + + if ($values) $values .= ", {$tmp}"; + else $values = ") VALUES ({$tmp}"; + } + $sql = $fields . $values . ')'; + } + return $this->execute($sql); + } + } + + /** * Updates a row in a table * @param $table The table in which to update * @param $vars An array mapping new values for the row @@ -1687,7 +2416,7 @@ class Postgres extends ADODB_base { // Build clause if (sizeof($vars) > 0) { $schema = $this->schema(); - + foreach($vars as $key => $value) { $this->fieldClean($key); @@ -1708,247 +2437,155 @@ class Postgres extends ADODB_base { } else $sql .= " AND \"{$k}\"='{$v}'"; } - } + } // Begin transaction. We do this so that we can ensure only one row is // edited $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); return -1; - } + } - $status = $this->execute($sql); + $status = $this->execute($sql); if ($status != 0) { // update failed - $this->rollbackTransaction(); + $this->rollbackTransaction(); return -1; } elseif ($this->conn->Affected_Rows() != 1) { // more than one row could be updated $this->rollbackTransaction(); return -2; - } - - // End transaction - return $this->endTransaction(); } - } - - /** - * Adds a new row to a table - * @param $table The table in which to insert - * @param $var An array mapping new values for the row - * @param $nulls An array mapping column => something if it is to be null - * @param $format An array of the data type (VALUE or EXPRESSION) - * @param $types An array of field types - * @return 0 success - * @return -1 invalid parameters - */ - function insertRow($table, $vars, $nulls, $format, $types) { - - if (!is_array($vars) || !is_array($nulls) || !is_array($format) - || !is_array($types)) return -1; - else { - $this->fieldClean($table); - $schema = $this->schema(); - - // Build clause - if (sizeof($vars) > 0) { - $fields = ''; - $values = ''; - foreach($vars as $key => $value) { - $this->fieldClean($key); - - // Handle NULL values - if (isset($nulls[$key])) $tmp = 'NULL'; - else $tmp = $this->formatValue($types[$key], $format[$key], $value); - - if ($fields) $fields .= ", \"{$key}\""; - else $fields = "INSERT INTO {$schema}\"{$table}\" (\"{$key}\""; - - if ($values) $values .= ", {$tmp}"; - else $values = ") VALUES ({$tmp}"; - } - $sql = $fields . $values . ')'; - } - return $this->execute($sql); - } + // End transaction + return $this->endTransaction(); } - - /** - * Returns a recordset of all columns in a table - * @param $table The name of a table - * @param $key The associative array holding the key to retrieve - * @return A recordset - */ - function browseRow($table, $key) { - $this->fieldClean($table); - - $schema = $this->schema(); - - $sql = "SELECT * FROM {$schema}\"{$table}\""; - if (is_array($key) && sizeof($key) > 0) { - $sql .= " WHERE true"; - foreach ($key as $k => $v) { - $this->fieldClean($k); - $this->clean($v); - $sql .= " AND \"{$k}\"='{$v}'"; - } - } - - return $this->selectSet($sql); } /** - * Get the fields for uniquely identifying a row in a table - * @param $table The table for which to retrieve the identifier - * @return An array mapping attribute number to attribute name, empty for no identifiers - * @return -1 error + * Delete a row from a table + * @param $table The table from which to delete + * @param $key An array mapping column => value to delete + * @return 0 success */ - function getRowIdentifier($table) { - $oldtable = $table; - $this->clean($table); - - $status = $this->beginTransaction(); - if ($status != 0) return -1; - - // Get the first primary or unique index (sorting primary keys first) that - // is NOT a partial index. - $sql = "SELECT indrelid, indkey FROM pg_index WHERE indisunique AND indrelid=(SELECT oid FROM pg_class - WHERE relname='{$table}') AND indpred='' AND indproc=0 ORDER BY indisprimary DESC LIMIT 1"; - $rs = $this->selectSet($sql); - - // If none, check for an OID column. Even though OIDs can be duplicated, the edit and delete row - // functions check that they're only modiying a single row. Otherwise, return empty array. - if ($rs->recordCount() == 0) { - // Check for OID column - $temp = array(); - if ($this->hasObjectID($table)) { - $temp = array('oid'); - } - $this->endTransaction(); - return $temp; - } - // Otherwise find the names of the keys + function deleteRow($table, $key) { + if (!is_array($key)) return -1; else { - $attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey'])); - if (!is_array($attnames)) { + // Begin transaction. We do this so that we can ensure only one row is + // deleted + $status = $this->beginTransaction(); + if ($status != 0) { $this->rollbackTransaction(); return -1; } - else { - $this->endTransaction(); - return $attnames; + + $status = $this->delete($table, $key, $this->_schema); + if ($status != 0 || $this->conn->Affected_Rows() != 1) { + $this->rollbackTransaction(); + return -2; } + + // End transaction + return $this->endTransaction(); } } // Sequence functions /** - * Returns all sequences in the current database - * @return A recordset - */ - 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 ); - } - - /** * Returns properties of a single sequence * @param $sequence Sequence name * @return A recordset */ function getSequence($sequence) { - $temp = $sequence; - // Need both field cleaned and literal cleaned versions $this->fieldClean($sequence); - $this->clean($temp); - $sql = "SELECT '{$sequence}' AS seqname, s.*, - (SELECT description FROM pg_description pd WHERE pd.objoid=(SELECT oid FROM pg_class WHERE relname='{$temp}')) AS seqcomment, - u.usename AS seqowner - FROM \"{$sequence}\" AS s, pg_user u, pg_class c - WHERE - c.relowner = u.usesysid AND c.relkind = 'S' - AND c.relname = '{$sequence}'"; + $sql = " + SELECT c.relname AS seqname, s.*, + pg_catalog.obj_description(s.tableoid, 'pg_class') AS seqcomment, + u.usename AS seqowner, n.nspname + FROM \"{$sequence}\" AS s, 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.relname = '{$sequence}' AND c.relkind = 'S' AND n.nspname='{$this->_schema}' + AND n.oid = c.relnamespace"; return $this->selectSet( $sql ); } /** - * Drops a given sequence - * @param $sequence Sequence name - * @param $cascade True to cascade drop, false to restrict - * @return 0 success + * Returns all sequences in the current database + * @return A recordset */ - function dropSequence($sequence, $cascade) { - $this->fieldClean($sequence); - - $sql = "DROP SEQUENCE \"{$sequence}\""; - if ($cascade) $sql .= " CASCADE"; + 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->execute($sql); + return $this->selectSet( $sql ); } /** - * Resets a given sequence to min value of sequence + * Execute nextval on a given sequence * @param $sequence Sequence name * @return 0 success * @return -1 sequence not found */ - function resetSequence($sequence) { - // Get the minimum value of the sequence - $seq = $this->getSequence($sequence); - if ($seq->recordCount() != 1) return -1; - $minvalue = $seq->fields[$this->sqFields['minvalue']]; - + function nextvalSequence($sequence) { /* This double-cleaning is deliberate */ $this->fieldClean($sequence); $this->clean($sequence); - $sql = "SELECT SETVAL('\"{$sequence}\"', {$minvalue})"; + $sql = "SELECT pg_catalog.NEXTVAL('\"{$this->_schema}\".\"{$sequence}\"')"; return $this->execute($sql); } /** - * Execute nextval on a given sequence + * Execute setval on a given sequence * @param $sequence Sequence name + * @param $nextvalue The next value * @return 0 success * @return -1 sequence not found */ - function nextvalSequence($sequence) { + function setvalSequence($sequence, $nextvalue) { /* This double-cleaning is deliberate */ $this->fieldClean($sequence); $this->clean($sequence); + $this->clean($nextvalue); - $sql = "SELECT NEXTVAL('\"{$sequence}\"')"; + $sql = "SELECT pg_catalog.SETVAL('\"{$this->_schema}\".\"{$sequence}\"', '{$nextvalue}')"; return $this->execute($sql); } /** - * Execute setval on a given sequence + * Resets a given sequence to min value of sequence * @param $sequence Sequence name - * @param $nextvalue The next value * @return 0 success * @return -1 sequence not found */ - function setvalSequence($sequence, $nextvalue) { + function resetSequence($sequence) { + // Get the minimum value of the sequence + $seq = $this->getSequence($sequence); + if ($seq->recordCount() != 1) return -1; + $minvalue = $seq->fields['min_value']; + /* This double-cleaning is deliberate */ $this->fieldClean($sequence); $this->clean($sequence); - $this->clean($nextvalue); - $sql = "SELECT SETVAL('\"{$sequence}\"', '{$nextvalue}')"; + $sql = "SELECT pg_catalog.SETVAL('\"{$this->_schema}\".\"{$sequence}\"', {$minvalue})"; return $this->execute($sql); } @@ -1974,7 +2611,7 @@ class Postgres extends ADODB_base { $this->clean($cachevalue); $schema = $this->schema(); - + $sql = "CREATE SEQUENCE {$schema}\"{$sequence}\""; if ($increment != '') $sql .= " INCREMENT {$increment}"; if ($minvalue != '') $sql .= " MINVALUE {$minvalue}"; @@ -1988,17 +2625,81 @@ class Postgres extends ADODB_base { /** * Rename a sequence - * @param $sequence The sequence name + * @param $seqrs The sequence RecordSet returned by getSequence() * @param $name The new name for the sequence * @return 0 success */ - function renameSequence($sequence, $name) { - $this->fieldClean($name); - $this->fieldClean($sequence); + function alterSequenceName($seqrs, $name) { + if (!empty($name) && ($seqrs->fields['seqname'] != $name)) { + $sql = "ALTER SEQUENCE \"{$this->_schema}\".\"{$seqrs->fields['seqname']}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); + if ($status == 0) + $seqrs->fields['seqname'] = $name; + else + return $status; + } + return 0; + } + + /** + * Alter a sequence's owner + * @param $seqrs The sequence RecordSet returned by getSequence() + * @param $name The new owner for the sequence + * @return 0 success + */ + function alterSequenceOwner($seqrs, $owner) { + // If owner has been changed, then do the alteration. We are + // careful to avoid this generally as changing owner is a + // superuser only function. + if (!empty($owner) && ($seqrs->fields['seqowner'] != $owner)) { + $sql = "ALTER TABLE \"{$seqrs->fields['seqname']}\" OWNER TO \"{$owner}\""; + return $this->execute($sql); + } + return 0; + } + + /** + * Alter a sequence's schema + * @param $seqrs The sequence RecordSet returned by getSequence() + * @param $name The new schema for the sequence + * @return 0 success + */ + function alterSequenceSchema($seqrs, $schema) { + if (!empty($schema) && ($seqrs->fields['nspname'] != $schema)) { + $sql = "ALTER SEQUENCE \"{$this->_schema}\".\"{$seqrs->fields['seqname']}\" SET SCHEMA {$schema}"; + return $this->execute($sql); + } + return 0; + } - $sql = "ALTER TABLE \"{$sequence}\" RENAME TO \"{$name}\""; + /** + * Alter a sequence's properties + * @param $seqrs The sequence RecordSet returned by getSequence() + * @param $increment The sequence incremental value + * @param $minvalue The sequence minimum value + * @param $maxvalue The sequence maximum value + * @param $startvalue The sequence current value + * @param $cachevalue Thesequence cache value + * @param $cycledvalue Sequence can cycle ? + * @return 0 success + */ + function alterSequenceProps($seqrs, $increment, $minvalue, $maxvalue, + $startvalue, $cachevalue, $cycledvalue) { + + $sql = ''; + if (!empty($increment) && ($increment != $seqrs->fields['increment_by'])) $sql .= " INCREMENT {$increment}"; + if (!empty($minvalue) && ($minvalue != $seqrs->fields['min_value'])) $sql .= " MINVALUE {$minvalue}"; + if (!empty($maxvalue) && ($maxvalue != $seqrs->fields['max_value'])) $sql .= " MAXVALUE {$maxvalue}"; + if (!empty($startvalue) && ($startvalue != $seqrs->fields['last_value'])) $sql .= " RESTART {$startvalue}"; + if (!empty($cachevalue) && ($cachevalue != $seqrs->fields['cache_value'])) $sql .= " CACHE {$cachevalue}"; + // toggle cycle yes/no + if (!is_null($cycledvalue)) $sql .= (!$cycledvalue ? ' NO ' : '') . " CYCLE"; + if ($sql != '') { + $sql = "ALTER SEQUENCE \"{$this->_schema}\".\"{$seqrs->fields['seqname']}\" {$sql}"; return $this->execute($sql); } + return 0; + } /** * Protected method which alter a sequence @@ -2018,29 +2719,50 @@ class Postgres extends ADODB_base { * @return -3 rename error * @return -4 comment error * @return -5 owner error + * @return -6 get sequence props error * @return -7 schema error */ - /*protected*/ + protected function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue) { + $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue) { - $sequence = $seqrs->fields['seqname']; - $this->fieldClean($name); - $this->clean($comment); - /* $owner, $schema, $increment, $minvalue, $maxvalue, - * $startvalue, $cachevalue, $cycledvalue not supported in pg70 */ + $this->fieldArrayClean($seqrs->fields); // Comment - $status = $this->setComment('SEQUENCE', $sequence, '', $comment); + $this->clean($comment); + $status = $this->setComment('SEQUENCE', $seqrs->fields['seqname'], '', $comment); if ($status != 0) return -4; - // Rename (only if name has changed) - if ($name != $sequence) { - $status = $this->renameSequence($sequence, $name); - if ($status != 0) - return -3; - } + // Owner + $this->fieldClean($owner); + $status = $this->alterSequenceOwner($seqrs, $owner); + if ($status != 0) + return -5; + + // Props + $this->clean($increment); + $this->clean($minvalue); + $this->clean($maxvalue); + $this->clean($startvalue); + $this->clean($cachevalue); + $this->clean($cycledvalue); + $status = $this->alterSequenceProps($seqrs, $increment, $minvalue, + $maxvalue, $startvalue, $cachevalue, $cycledvalue); + if ($status != 0) + return -6; + + // Rename + $this->fieldClean($name); + $status = $this->alterSequenceName($seqrs, $name); + if ($status != 0) + return -3; + + // Schema + $this->clean($schema); + $status = $this->alterSequenceSchema($seqrs, $schema); + if ($status != 0) + return -7; return 0; } @@ -2064,10 +2786,12 @@ class Postgres extends ADODB_base { * @return $this->_alterSequence error code */ function alterSequence($sequence, $name, $comment, $owner=null, $schema=null, $increment=null, - $minvalue=null, $maxvalue=null, $startvalue=null, $cachevalue=null, $cycledvalue=null) { + $minvalue=null, $maxvalue=null, $startvalue=null, $cachevalue=null, $cycledvalue=null) { $this->fieldClean($sequence); + $data = $this->getSequence($sequence); + if ($data->recordCount() != 1) return -2; @@ -2088,239 +2812,249 @@ class Postgres extends ADODB_base { return $this->endTransaction(); } - // Constraint functions - /** - * Returns a list of all constraints on a table - * @param $table The table to find rules for - * @return A recordset + * Drops a given sequence + * @param $sequence Sequence name + * @param $cascade True to cascade drop, false to restrict + * @return 0 success */ - function getConstraints($table) { - $this->clean($table); + function dropSequence($sequence, $cascade) { + $this->fieldClean($sequence); - $status = $this->beginTransaction(); - if ($status != 0) return -1; + $sql = "DROP SEQUENCE \"{$this->_schema}\".\"{$sequence}\""; + if ($cascade) $sql .= " CASCADE"; + + return $this->execute($sql); + } + + // View functions + + /** + * Returns all details for a particular view + * @param $view The name of the view to retrieve + * @return View info + */ + function getView($view) { + $this->clean($view); $sql = " - SELECT - rcname AS conname, - 'CHECK (' || rcsrc || ')' AS consrc, - 'c' AS contype, - NULL::int2vector AS indkey - FROM - pg_relcheck - WHERE - rcrelid = (SELECT oid FROM pg_class WHERE relname='{$table}') - UNION ALL - SELECT - pc.relname, - NULL, - CASE WHEN indisprimary THEN - 'p' - ELSE - 'u' - END, - indkey - FROM - pg_class pc, - pg_index pi - WHERE - pc.oid=pi.indexrelid - AND (pi.indisunique OR pi.indisprimary) - AND pi.indrelid = (SELECT oid FROM pg_class WHERE relname='{$table}') - ORDER BY - 1 - "; + SELECT c.relname, n.nspname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, + pg_catalog.pg_get_viewdef(c.oid, true) AS vwdefinition, + pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) + WHERE (c.relname = '$view') AND n.nspname='{$this->_schema}'"; return $this->selectSet($sql); } /** - * Adds a check constraint to a table - * @param $table The table to which to add the check - * @param $definition The definition of the check - * @param $name (optional) The name to give the check, otherwise default name is assigned - * @return 0 success + * Returns a list of all views in the database + * @return All views */ - function addCheckConstraint($table, $definition, $name = '') { - $this->fieldClean($table); - $this->fieldClean($name); - // @@ How the heck do you clean a definition??? - - $sql = "ALTER TABLE \"{$table}\" ADD "; - if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; - $sql .= "CHECK ({$definition})"; + function getViews() { + $sql = " + SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, + pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) + WHERE (n.nspname='{$this->_schema}') AND (c.relkind = 'v'::\"char\") + ORDER BY relname"; - return $this->execute($sql); + return $this->selectSet($sql); } /** - * Drops a check constraint from a table - * @param $table The table from which to drop the check - * @param $name The name of the check to be dropped + * Updates a view. + * @param $viewname The name fo the view to update + * @param $definition The new definition for the view * @return 0 success - * @return -2 transaction error - * @return -3 lock error - * @return -4 check drop error + * @return -1 transaction error + * @return -2 drop view error + * @return -3 create view error */ - function dropCheckConstraint($table, $name) { - $this->clean($table); - $this->clean($name); + function setView($viewname, $definition,$comment) { + return $this->createView($viewname, $definition, true, $comment); + } - // Begin transaction + /** + * Creates a new view. + * @param $viewname The name of the view to create + * @param $definition The definition for the new view + * @param $replace True to replace the view, false otherwise + * @return 0 success + */ + function createView($viewname, $definition, $replace, $comment) { $status = $this->beginTransaction(); - if ($status != 0) return -2; + if ($status != 0) return -1; - // Properly lock the table - $sql = "LOCK TABLE \"{$table}\" IN ACCESS EXCLUSIVE MODE"; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } + $this->fieldClean($viewname); + $this->clean($comment); - // Delete the check constraint - $sql = "DELETE FROM pg_relcheck WHERE rcrelid=(SELECT oid FROM pg_class WHERE relname='{$table}') AND rcname='{$name}'"; - $status = $this->execute($sql); - if ($status != 0) { + // Note: $definition not cleaned + + $sql = "CREATE "; + if ($replace) $sql .= "OR REPLACE "; + $sql .= "VIEW ". $this->schema() ."\"{$viewname}\" AS {$definition}"; + + $status = $this->execute($sql); + if ($status) { $this->rollbackTransaction(); - return -4; + return -1; } - // Update the pg_class catalog to reflect the new number of checks - $sql = "UPDATE pg_class SET relchecks=(SELECT COUNT(*) FROM pg_relcheck WHERE - rcrelid=(SELECT oid FROM pg_class WHERE relname='{$table}')) - WHERE relname='{$table}'"; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; + if ($comment != '') { + $status = $this->setComment('VIEW', $viewname, '', $comment); + if ($status) { + $this->rollbackTransaction(); + return -1; + } } - // Otherwise, close the transaction return $this->endTransaction(); } - // Constraint functions - /** - * Removes a constraint from a relation - * @param $constraint The constraint to drop - * @param $relation The relation from which to drop - * @param $type The type of constraint (c, f, u or p) - * @param $cascade True to cascade drop, false to restrict + * Rename a view + * @param $vwrs The view recordSet returned by getView() + * @param $name The new view's name * @return 0 success - * @return -99 dropping foreign keys not supported */ - function dropConstraint($constraint, $relation, $type, $cascade) { - $this->fieldClean($constraint); - $this->fieldClean($relation); - - switch ($type) { - case 'c': - // CHECK constraint - return $this->dropCheckConstraint($relation, $constraint); - break; - case 'p': - case 'u': - // PRIMARY KEY or UNIQUE constraint - return $this->dropIndex($constraint, $cascade); - break; - case 'f': - // FOREIGN KEY constraint - return -99; + function alterViewName($vwrs, $name) { + // Rename (only if name has changed) + if (!empty($name) && ($name != $vwrs->fields['relname'])) { + $sql = "ALTER VIEW \"{$this->_schema}\".\"{$vwrs->fields['relname']}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); + if ($status == 0) + $vwrs->fields['relname'] = $name; + else + return $status; } + return 0; } /** - * Adds a unique constraint to a table - * @param $table The table to which to add the unique - * @param $fields (array) An array of fields over which to add the unique - * @param $name (optional) The name to give the unique, otherwise default name is assigned + * Alter a view's owner + * @param $vwrs The view recordSet returned by getView() + * @param $name The new view's owner * @return 0 success - * @return -1 invalid fields */ - function addUniqueKey($table, $fields, $name = '') { - if (!is_array($fields) || sizeof($fields) == 0) return -1; - $this->fieldClean($table); - $this->fieldArrayClean($fields); + function alterViewOwner($vwrs, $owner = null) { + if ((!empty($owner)) && ($vwrs->fields['relowner'] != $owner)) { + // If owner has been changed, then do the alteration. We are + // careful to avoid this generally as changing owner is a + // superuser only function. + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$vwrs->fields['relname']}\" OWNER TO \"{$owner}\""; + return $this->execute($sql); + } + return 0; + } + + /** + * Alter a view's schema + * @param $vwrs The view recordSet returned by getView() + * @param $name The new view's schema + * @return 0 success + */ + function alterViewSchema($vwrs, $schema) { + if (!empty($schema) && ($vwrs->fields['nspname'] != $schema)) { + // If tablespace has been changed, then do the alteration. We + // don't want to do this unnecessarily. + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$vwrs->fields['relname']}\" SET SCHEMA \"{$schema}\""; + return $this->execute($sql); + } + return 0; + } + + /** + * Protected method which alter a view + * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION + * @param $vwrs The view recordSet returned by getView() + * @param $name The new name for the view + * @param $owner The new owner for the view + * @param $comment The comment on the view + * @return 0 success + * @return -3 rename error + * @return -4 comment error + * @return -5 owner error + * @return -6 schema error + */ + protected + function _alterView($vwrs, $name, $owner, $schema, $comment) { + + $this->fieldArrayClean($vwrs->fields); + + // Comment + $this->clean($comment); + if ($this->setComment('VIEW', $vwrs->fields['relname'], '', $comment) != 0) + return -4; + + // Owner + $this->fieldClean($owner); + $status = $this->alterViewOwner($vwrs, $owner); + if ($status != 0) return -5; + + // Rename $this->fieldClean($name); + $status = $this->alterViewName($vwrs, $name); + if ($status != 0) return -3; - if ($name != '') - $sql = "CREATE UNIQUE INDEX \"{$name}\" ON \"{$table}\"(\"" . join('","', $fields) . "\")"; - else return -99; // Not supported + // Schema + $this->fieldClean($schema); + $status = $this->alterViewSchema($vwrs, $schema); + if ($status != 0) return -6; - return $this->execute($sql); + return 0; } /** - * Adds a foreign key constraint to a table - * @param $targschema The schema that houses the target table to which to add the foreign key - * @param $targtable The table to which to add the foreign key - * @param $target The table that contains the target columns - * @param $sfields (array) An array of source fields over which to add the foreign key - * @param $tfields (array) An array of target fields over which to add the foreign key - * @param $upd_action The action for updates (eg. RESTRICT) - * @param $del_action The action for deletes (eg. RESTRICT) - * @param $match The match type (eg. MATCH FULL) - * @param $deferrable The deferrability (eg. NOT DEFERRABLE) - * @param $intially The initial deferrability (eg. INITIALLY IMMEDIATE) - * @param $name (optional) The name to give the key, otherwise default name is assigned + * Alter view properties + * @param $view The name of the view + * @param $name The new name for the view + * @param $owner The new owner for the view + * @param $schema The new schema for the view + * @param $comment The comment on the view * @return 0 success - * @return -1 no fields given + * @return -1 transaction error + * @return -2 get existing view error + * @return $this->_alterView error code */ - function addForeignKey($table, $targschema, $targtable, $sfields, $tfields, $upd_action, $del_action, - $match, $deferrable, $initially, $name = '') { - if (!is_array($sfields) || sizeof($sfields) == 0 || - !is_array($tfields) || sizeof($tfields) == 0) return -1; - $this->fieldClean($table); - $this->fieldClean($targschema); - $this->fieldClean($targtable); - $this->fieldArrayClean($sfields); - $this->fieldArrayClean($tfields); - $this->fieldClean($name); + function alterView($view, $name, $owner, $schema, $comment) { - $schema = $this->schema(); - - $sql = "ALTER TABLE {$schema}\"{$table}\" ADD "; - if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; - $sql .= "FOREIGN KEY (\"" . join('","', $sfields) . "\") "; - $sql .= "REFERENCES "; - // Target table needs to be fully qualified - if ($this->hasSchemas()) { - $sql .= "\"{$targschema}\"."; + $this->fieldClean($view); + $data = $this->getView($view); + if ($data->recordCount() != 1) + return -2; + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; } - $sql .= "\"{$targtable}\"(\"" . join('","', $tfields) . "\") "; - if ($match != $this->fkmatches[0]) $sql .= " {$match}"; - if ($upd_action != $this->fkactions[0]) $sql .= " ON UPDATE {$upd_action}"; - if ($del_action != $this->fkactions[0]) $sql .= " ON DELETE {$del_action}"; - if ($deferrable != $this->fkdeferrable[0]) $sql .= " {$deferrable}"; - if ($initially != $this->fkinitial[0]) $sql .= " {$initially}"; - return $this->execute($sql); + $status = $this->_alterView($data, $name, $owner, $schema, $comment); + + if ($status != 0) { + $this->rollbackTransaction(); + return $status; + } + + return $this->endTransaction(); } /** - * Adds a primary key constraint to a table - * @param $table The table to which to add the primery key - * @param $fields (array) An array of fields over which to add the primary key - * @param $name (optional) The name to give the key, otherwise default name is assigned + * Drops a view. + * @param $viewname The name of the view to drop + * @param $cascade True to cascade drop, false to restrict * @return 0 success */ - function addPrimaryKey($table, $fields, $name = '') { - // This function can be faked with a unique index and a catalog twiddle, however - // how do we ensure that it's only used on NOT NULL fields? - return -99; // Not supported. - } + function dropView($viewname, $cascade) { + $this->fieldClean($viewname); - /** - * Finds the foreign keys that refer to the specified table - * @param $table The table to find referrers for - * @return A recordset - */ - function getReferrers($table) { - // In PostgreSQL < 7.3, there is no way to discover foreign keys - return -99; + $sql = "DROP VIEW ". $this->schema() ."\"{$viewname}\""; + if ($cascade) $sql .= " CASCADE"; + + return $this->execute($sql); } // Index functions @@ -2333,10 +3067,13 @@ class Postgres extends ADODB_base { */ 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, - obj_description(c.oid, 'pg_index') AS idxcomment - FROM pg_class c, pg_class c2, pg_index i - WHERE c.relname = '{$table}' AND c.oid = i.indrelid AND i.indexrelid = c2.oid + + $sql = " + SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered, + pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef + 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 "; if ($unique) $sql .= " AND i.indisunique "; $sql .= " ORDER BY c2.relname"; @@ -2382,7 +3119,7 @@ class Postgres extends ADODB_base { // Predicate if ($this->hasPartialIndexes() && trim($where) != '') { $sql .= " WHERE ({$where})"; - } + } return $this->execute($sql); } @@ -2396,654 +3133,974 @@ class Postgres extends ADODB_base { function dropIndex($index, $cascade) { $this->fieldClean($index); - $sql = "DROP INDEX \"{$index}\""; + $sql = "DROP INDEX \"{$this->_schema}\".\"{$index}\""; if ($cascade) $sql .= " CASCADE"; return $this->execute($sql); } /** + * Rebuild indexes + * @param $type 'DATABASE' or 'TABLE' or 'INDEX' + * @param $name The name of the specific database, table, or index to be reindexed + * @param $force If true, recreates indexes forcedly in PostgreSQL 7.0-7.1, forces rebuild of system indexes in 7.2-7.3, ignored in >=7.4 + */ + function reindex($type, $name, $force = false) { + $this->fieldClean($name); + switch($type) { + case 'DATABASE': + $sql = "REINDEX {$type} \"{$name}\""; + if ($force) $sql .= ' FORCE'; + break; + case 'TABLE': + case 'INDEX': + $sql = "REINDEX {$type} \"{$this->_schema}\".\"{$name}\""; + if ($force) $sql .= ' FORCE'; + break; + default: + return -1; + } + + return $this->execute($sql); + } + + /** * Clusters an index * @param $index The name of the index * @param $table The table the index is on * @return 0 success */ function clusterIndex($index, $table) { + $this->fieldClean($index); $this->fieldClean($table); // We don't bother with a transaction here, as there's no point rolling // back an expensive cluster if a cheap analyze fails for whatever reason - $sql = "CLUSTER \"{$index}\" ON \"{$table}\""; + $sql = "CLUSTER \"{$this->_schema}\".\"{$table}\" USING \"{$index}\""; return $this->execute($sql); } - // Rule functions + // Constraint functions /** - * Returns a list of all rules on a table + * Returns a list of all constraints on a table * @param $table The table to find rules for * @return A recordset */ - function getRules($table) { + function getConstraints($table) { $this->clean($table); + // This SQL is greatly complicated by the need to retrieve + // index clustering information for primary and unique constraints $sql = "SELECT - * + pc.conname, + pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc, + pc.contype, + CASE WHEN pc.contype='u' OR pc.contype='p' THEN ( + SELECT + indisclustered + FROM + pg_catalog.pg_depend pd, + pg_catalog.pg_class pl, + pg_catalog.pg_index pi + WHERE + pd.refclassid=pc.tableoid + AND pd.refobjid=pc.oid + AND pd.objid=pl.oid + AND pl.oid=pi.indexrelid + ) ELSE + NULL + END AS indisclustered FROM - pg_rules + pg_catalog.pg_constraint pc WHERE - tablename='{$table}' + pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' + AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace + WHERE nspname='{$this->_schema}')) ORDER BY - rulename + 1 "; return $this->selectSet($sql); } /** - * Removes a rule from a relation - * @param $rule The rule to drop - * @param $relation The relation from which to drop (unused) - * @param $cascade True to cascade drop, false to restrict - * @return 0 success + * Returns a list of all constraints on a table, + * including constraint name, definition, related col and referenced namespace, + * table and col if needed + * @param $table the table where we are looking for fk + * @return a recordset */ - function dropRule($rule, $relation, $cascade) { - $this->fieldClean($rule); + function getConstraintsWithFields($table) { + global $data; - $sql = "DROP RULE \"{$rule}\""; - if ($cascade) $sql .= " CASCADE"; + $data->clean($table); - return $this->execute($sql); + // get the max number of col used in a constraint for the table + $sql = "SELECT DISTINCT + max(SUBSTRING(array_dims(c.conkey) FROM E'^\\\[.*:(.*)\\\]$')) as nb + FROM + pg_catalog.pg_constraint AS c + JOIN pg_catalog.pg_class AS r ON (c.conrelid = r.oid) + JOIN pg_catalog.pg_namespace AS ns ON r.relnamespace=ns.oid + WHERE + r.relname = '$table' AND ns.nspname='". $this->_schema ."'"; + + $rs = $this->selectSet($sql); + + if ($rs->EOF) $max_col = 0; + else $max_col = $rs->fields['nb']; + + $sql = ' + SELECT + c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid, true) AS consrc, + ns1.nspname as p_schema, r1.relname as p_table, ns2.nspname as f_schema, + r2.relname as f_table, f1.attname as p_field, f2.attname as f_field, + pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment + FROM + pg_catalog.pg_constraint AS c + JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid) + JOIN pg_catalog.pg_attribute AS f1 ON (f1.attrelid=r1.oid AND (f1.attnum=c.conkey[1]'; + for ($i = 2; $i <= $rs->fields['nb']; $i++) { + $sql.= " OR f1.attnum=c.conkey[$i]"; + } + $sql.= ')) + JOIN pg_catalog.pg_namespace AS ns1 ON r1.relnamespace=ns1.oid + LEFT JOIN ( + pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid) + ) ON (c.confrelid=r2.oid) + LEFT JOIN pg_catalog.pg_attribute AS f2 ON + (f2.attrelid=r2.oid AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)'; + for ($i = 2; $i <= $rs->fields['nb']; $i++) + $sql.= "OR (c.confkey[$i]=f2.attnum AND c.conkey[$i]=f1.attnum)"; + + $sql .= sprintf(")) + WHERE + r1.relname = '%s' AND ns1.nspname='%s' + ORDER BY 1", $table, $this->_schema); + + return $this->selectSet($sql); } /** - * Creates a rule - * @param $name The name of the new rule - * @param $event SELECT, INSERT, UPDATE or DELETE - * @param $table Table on which to create the rule - * @param $where When to execute the rule, '' indicates always - * @param $instead True if an INSTEAD rule, false otherwise - * @param $type NOTHING for a do nothing rule, SOMETHING to use given action - * @param $action The action to take - * @param $replace (optional) True to replace existing rule, false otherwise + * Adds a primary key constraint to a table + * @param $table The table to which to add the primery key + * @param $fields (array) An array of fields over which to add the primary key + * @param $name (optional) The name to give the key, otherwise default name is assigned + * @param $tablespace (optional) The tablespace for the schema, '' indicates default. * @return 0 success - * @return -1 invalid event + * @return -1 no fields given */ - function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false) { - $this->fieldClean($name); + function addPrimaryKey($table, $fields, $name = '', $tablespace = '') { + if (!is_array($fields) || sizeof($fields) == 0) return -1; $this->fieldClean($table); - if (!in_array($event, $this->rule_events)) return -1; + $this->fieldArrayClean($fields); + $this->fieldClean($name); + $this->fieldClean($tablespace); $schema = $this->schema(); - $sql = "CREATE"; - if ($replace) $sql .= " OR REPLACE"; - $sql .= " RULE \"{$name}\" AS ON {$event} TO {$schema}\"{$table}\""; - // Can't escape WHERE clause - if ($where != '') $sql .= " WHERE {$where}"; - $sql .= " DO"; - if ($instead) $sql .= " INSTEAD"; - if ($type == 'NOTHING') - $sql .= " NOTHING"; - else $sql .= " ({$action})"; + $sql = "ALTER TABLE {$schema}\"{$table}\" ADD "; + if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; + $sql .= "PRIMARY KEY (\"" . join('","', $fields) . "\")"; + + if ($tablespace != '' && $this->hasTablespaces()) + $sql .= " USING INDEX TABLESPACE \"{$tablespace}\""; return $this->execute($sql); } /** - * Edits a rule - * @param $name The name of the new rule - * @param $event SELECT, INSERT, UPDATE or DELETE - * @param $table Table on which to create the rule - * @param $where When to execute the rule, '' indicates always - * @param $instead True if an INSTEAD rule, false otherwise - * @param $type NOTHING for a do nothing rule, SOMETHING to use given action - * @param $action The action to take + * Adds a unique constraint to a table + * @param $table The table to which to add the unique key + * @param $fields (array) An array of fields over which to add the unique key + * @param $name (optional) The name to give the key, otherwise default name is assigned + * @param $tablespace (optional) The tablespace for the schema, '' indicates default. * @return 0 success - * @return -1 invalid event - * @return -2 transaction error - * @return -3 drop existing rule error - * @return -4 create new rule error + * @return -1 no fields given */ - function setRule($name, $event, $table, $where, $instead, $type, $action) { - $status = $this->beginTransaction(); - if ($status != 0) return -2; - - $status = $this->dropRule($name, $table); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - - $status = $this->createRule($name, $event, $table, $where, $instead, $type, $action); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - - $status = $this->endTransaction(); - return ($status == 0) ? 0 : -2; - } - - // View functions + function addUniqueKey($table, $fields, $name = '', $tablespace = '') { + if (!is_array($fields) || sizeof($fields) == 0) return -1; + $this->fieldClean($table); + $this->fieldArrayClean($fields); + $this->fieldClean($name); + $this->fieldClean($tablespace); - /** - * Returns a list of all views in the database - * @return All views - */ - function getViews() { - global $conf; + $schema = $this->schema(); - if (!$conf['show_system']) - $where = " WHERE viewname NOT LIKE 'pg@_%' ESCAPE '@' "; - else - $where = ''; + $sql = "ALTER TABLE {$schema}\"{$table}\" ADD "; + if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; + $sql .= "UNIQUE (\"" . join('","', $fields) . "\")"; - $sql = "SELECT viewname AS relname, viewowner AS relowner, definition AS vwdefinition, - (SELECT description FROM pg_description pd, pg_class pc - WHERE pc.oid=pd.objoid AND pc.relname=v.viewname) AS relcomment - FROM pg_views v - {$where} - ORDER BY relname"; + if ($tablespace != '' && $this->hasTablespaces()) + $sql .= " USING INDEX TABLESPACE \"{$tablespace}\""; - return $this->selectSet($sql); + return $this->execute($sql); } /** - * Returns all details for a particular view - * @param $view The name of the view to retrieve - * @return View info + * Adds a check constraint to a table + * @param $table The table to which to add the check + * @param $definition The definition of the check + * @param $name (optional) The name to give the check, otherwise default name is assigned + * @return 0 success */ - function getView($view) { - $this->clean($view); + function addCheckConstraint($table, $definition, $name = '') { + $this->fieldClean($table); + $this->fieldClean($name); + // @@ How the heck do you clean a definition??? - $sql = "SELECT viewname AS relname, NULL AS nspname, viewowner AS relowner, definition AS vwdefinition, - (SELECT description FROM pg_description pd, pg_class pc - WHERE pc.oid=pd.objoid AND pc.relname=v.viewname) AS relcomment - FROM pg_views v - WHERE viewname='{$view}'"; + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ADD "; + if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; + $sql .= "CHECK ({$definition})"; - return $this->selectSet($sql); + return $this->execute($sql); } /** - * Creates a new view. - * @param $viewname The name of the view to create - * @param $definition The definition for the new view - * @param $replace True to replace the view, false otherwise + * Drops a check constraint from a table + * @param $table The table from which to drop the check + * @param $name The name of the check to be dropped * @return 0 success + * @return -2 transaction error + * @return -3 lock error + * @return -4 check drop error */ - function createView($viewname, $definition, $replace, $comment) { - $status = $this->beginTransaction(); - if ($status != 0) return -1; - - $this->fieldClean($viewname); - $this->clean($comment); - - // Note: $definition not cleaned + function dropCheckConstraint($table, $name) { + $this->clean($table); + $this->clean($name); - $sql = "CREATE "; - if ($replace) $sql .= "OR REPLACE "; - $sql .= "VIEW ". $this->schema() ."\"{$viewname}\" AS {$definition}"; + // Begin transaction + $status = $this->beginTransaction(); + if ($status != 0) return -2; + // Properly lock the table + $sql = "LOCK TABLE \"{$this->_schema}\".\"{$table}\" IN ACCESS EXCLUSIVE MODE"; $status = $this->execute($sql); - if ($status) { + if ($status != 0) { $this->rollbackTransaction(); - return -1; + return -3; + } + + // Delete the check constraint + $sql = "DELETE FROM pg_relcheck WHERE rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' + AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE + nspname = '{$this->_schema}')) AND rcname='{$name}'"; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; } - if ($comment != '') { - $status = $this->setComment('VIEW', $viewname, '', $comment); - if ($status) { - $this->rollbackTransaction(); - return -1; - } + // Update the pg_class catalog to reflect the new number of checks + $sql = "UPDATE pg_class SET relchecks=(SELECT COUNT(*) FROM pg_relcheck WHERE + rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' + AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE + nspname = '{$this->_schema}'))) + WHERE relname='{$table}'"; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; } + // Otherwise, close the transaction return $this->endTransaction(); } /** - * Drops a view. - * @param $viewname The name of the view to drop - * @param $cascade True to cascade drop, false to restrict + * Adds a foreign key constraint to a table + * @param $targschema The schema that houses the target table to which to add the foreign key + * @param $targtable The table to which to add the foreign key + * @param $target The table that contains the target columns + * @param $sfields (array) An array of source fields over which to add the foreign key + * @param $tfields (array) An array of target fields over which to add the foreign key + * @param $upd_action The action for updates (eg. RESTRICT) + * @param $del_action The action for deletes (eg. RESTRICT) + * @param $match The match type (eg. MATCH FULL) + * @param $deferrable The deferrability (eg. NOT DEFERRABLE) + * @param $intially The initial deferrability (eg. INITIALLY IMMEDIATE) + * @param $name (optional) The name to give the key, otherwise default name is assigned * @return 0 success + * @return -1 no fields given */ - function dropView($viewname, $cascade) { - $this->fieldClean($viewname); + function addForeignKey($table, $targschema, $targtable, $sfields, $tfields, $upd_action, $del_action, + $match, $deferrable, $initially, $name = '') { + if (!is_array($sfields) || sizeof($sfields) == 0 || + !is_array($tfields) || sizeof($tfields) == 0) return -1; + $this->fieldClean($table); + $this->fieldClean($targschema); + $this->fieldClean($targtable); + $this->fieldArrayClean($sfields); + $this->fieldArrayClean($tfields); + $this->fieldClean($name); - $sql = "DROP VIEW ". $this->schema() ."\"{$viewname}\""; - if ($cascade) $sql .= " CASCADE"; + $schema = $this->schema(); + + $sql = "ALTER TABLE {$schema}\"{$table}\" ADD "; + if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; + $sql .= "FOREIGN KEY (\"" . join('","', $sfields) . "\") "; + $sql .= "REFERENCES "; + // Target table needs to be fully qualified + if ($this->hasSchemas()) { + $sql .= "\"{$targschema}\"."; + } + $sql .= "\"{$targtable}\"(\"" . join('","', $tfields) . "\") "; + if ($match != $this->fkmatches[0]) $sql .= " {$match}"; + if ($upd_action != $this->fkactions[0]) $sql .= " ON UPDATE {$upd_action}"; + if ($del_action != $this->fkactions[0]) $sql .= " ON DELETE {$del_action}"; + if ($deferrable != $this->fkdeferrable[0]) $sql .= " {$deferrable}"; + if ($initially != $this->fkinitial[0]) $sql .= " {$initially}"; return $this->execute($sql); } /** - * Updates a view. Postgres 7.1 and below don't have CREATE OR REPLACE view, - * so we do it with a drop and a recreate. - * @param $viewname The name fo the view to update - * @param $definition The new definition for the view + * Removes a constraint from a relation + * @param $constraint The constraint to drop + * @param $relation The relation from which to drop + * @param $type The type of constraint (c, f, u or p) + * @param $cascade True to cascade drop, false to restrict * @return 0 success - * @return -1 transaction error - * @return -2 drop view error - * @return -3 create view error - * @return -4 comment error */ - function setView($viewname, $definition, $comment) { - $status = $this->beginTransaction(); - if ($status != 0) return -1; - - $status = $this->dropView($viewname, false); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } + function dropConstraint($constraint, $relation, $type, $cascade) { + $this->fieldClean($constraint); + $this->fieldClean($relation); - $status = $this->createView($viewname, $definition, false, $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$relation}\" DROP CONSTRAINT \"{$constraint}\""; + if ($cascade) $sql .= " CASCADE"; - $status = $this->endTransaction(); - return ($status == 0) ? 0 : -1; + return $this->execute($sql); } + /** - * Rename a view - * @param $view The current view's name - * @param $name The new view's name - * @return -1 Failed - * @return 0 success + * A function for getting all columns linked by foreign keys given a group of tables + * @param $tables multi dimensional assoc array that holds schema and table name + * @return A recordset of linked tables and columns + * @return -1 $tables isn't an array */ - function renameView($view, $name) { - $this->fieldClean($name); - $this->fieldClean($view); - $sql = "ALTER TABLE \"{$view}\" RENAME TO \"{$name}\""; - if ($this->execute($sql) != 0) - return -1; - return 0; - } - - /** - * Protected method which alter a view - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $vwrs The view recordSet returned by getView() - * @param $name The new name for the view - * @param $owner The new owner for the view - * @param $comment The comment on the view - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 schema error - */ - function _alterView($vwrs, $name, $owner, $schema, $comment) { + function getLinkingKeys($tables) { + if (!is_array($tables)) return -1; - $this->fieldClean($name); - $this->clean($comment); + $tables_list = "'{$tables[0]['tablename']}'"; + $schema_list = "'{$tables[0]['schemaname']}'"; + $schema_tables_list = "'{$tables[0]['schemaname']}.{$tables[0]['tablename']}'"; + for ($i = 1; $i < sizeof($tables); $i++) { + $tables_list .= ", '{$tables[$i]['tablename']}'"; + $schema_list .= ", '{$tables[$i]['schemaname']}'"; + $schema_tables_list .= ", '{$tables[$i]['schemaname']}.{$tables[$i]['tablename']}'"; + } + $maxDimension = 1; - $view = $vwrs->fields['relname']; + $sql = " + SELECT DISTINCT + array_dims(pc.conkey) AS arr_dim, + pgc1.relname AS p_table + FROM + pg_catalog.pg_constraint AS pc, + pg_catalog.pg_class AS pgc1 + WHERE + pc.contype = 'f' + AND (pc.conrelid = pgc1.relfilenode OR pc.confrelid = pgc1.relfilenode) + AND pgc1.relname IN ($tables_list) + "; - // Comment - if ($this->setComment('VIEW', $view, '', $comment) != 0) - return -4; + //parse our output to find the highest dimension of foreign keys since pc.conkey is stored in an array + $rs = $this->selectSet($sql); + while (!$rs->EOF) { + $arrData = explode(':', $rs->fields['arr_dim']); + $tmpDimension = intval(substr($arrData[1], 0, strlen($arrData[1] - 1))); + $maxDimension = $tmpDimension > $maxDimension ? $tmpDimension : $maxDimension; + $rs->MoveNext(); + } - // Rename (only if name has changed) - if ($name != $view) { - if ($this->renameView($view, $name) != 0) - return -3; + //we know the highest index for foreign keys that conkey goes up to, expand for us in an IN query + $cons_str = '( (pfield.attnum = conkey[1] AND cfield.attnum = confkey[1]) '; + for ($i = 2; $i <= $maxDimension; $i++) { + $cons_str .= "OR (pfield.attnum = conkey[{$i}] AND cfield.attnum = confkey[{$i}]) "; } + $cons_str .= ') '; - return 0; + $sql = " + SELECT + pgc1.relname AS p_table, + pgc2.relname AS f_table, + pfield.attname AS p_field, + cfield.attname AS f_field, + pgns1.nspname AS p_schema, + pgns2.nspname AS f_schema + FROM + pg_catalog.pg_constraint AS pc, + pg_catalog.pg_class AS pgc1, + pg_catalog.pg_class AS pgc2, + pg_catalog.pg_attribute AS pfield, + pg_catalog.pg_attribute AS cfield, + (SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN ($schema_list) ) AS pgns1, + (SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN ($schema_list) ) AS pgns2 + WHERE + pc.contype = 'f' + AND pgc1.relnamespace = pgns1.ns_id + AND pgc2.relnamespace = pgns2.ns_id + AND pc.conrelid = pgc1.relfilenode + AND pc.confrelid = pgc2.relfilenode + AND pfield.attrelid = pc.conrelid + AND cfield.attrelid = pc.confrelid + AND $cons_str + AND pgns1.nspname || '.' || pgc1.relname IN ($schema_tables_list) + AND pgns2.nspname || '.' || pgc2.relname IN ($schema_tables_list) + "; + return $this->selectSet($sql); } /** - * Alter table properties - * @param $table The name of the view - * @param $name The new name for the view - * @param $owner The new owner for the view - * @param $schema The new schema for the view - * @param $comment The comment on the view - * @return 0 success - * @return -1 transaction error - * @return -2 get existing view error - * @return $this->_alterView error code + * Finds the foreign keys that refer to the specified table + * @param $table The table to find referrers for + * @return A recordset */ - function alterView($view, $name, $owner, $schema, $comment) { - - $this->fieldClean($view); - $data = $this->getView($view); - if ($data->recordCount() != 1) - return -2; + function getReferrers($table) { + $this->clean($table); $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } + if ($status != 0) return -1; - $status = $this->_alterView($data, $name, $owner, $schema, $comment); + $sql = " + SELECT + pn.nspname, + pl.relname, + pc.conname, + pg_catalog.pg_get_constraintdef(pc.oid) AS consrc + FROM + pg_catalog.pg_constraint pc, + pg_catalog.pg_namespace pn, + pg_catalog.pg_class pl + WHERE + pc.connamespace = pn.oid + AND pc.conrelid = pl.oid + AND pc.contype = 'f' + AND confrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' + AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace + WHERE nspname='{$this->_schema}')) + ORDER BY 1,2,3 + "; - if ($status != 0) { - $this->rollbackTransaction(); - return $status; + return $this->selectSet($sql); } - return $this->endTransaction(); - } - - // Operator functions + // Domain functions /** - * Returns a list of all operators in the database - * @return All operators + * Gets all information for a single domain + * @param $domain The name of the domain to fetch + * @return A recordset */ - function getOperators() { - global $conf; - if (!$conf['show_system']) - $where = "WHERE po.oid > '{$this->_lastSystemOID}'::oid"; - else $where = ''; + function getDomain($domain) { + $this->clean($domain); $sql = " SELECT - po.oid, - po.oprname, - (SELECT typname FROM pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname, - (SELECT typname FROM pg_type pt WHERE pt.oid=po.oprright) AS oprrightname, - (SELECT typname FROM pg_type pt WHERE pt.oid=po.oprresult) AS resultname, - (SELECT description FROM pg_description pd WHERE po.oid=pd.objoid) AS oprcomment + t.typname AS domname, + pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype, + t.typnotnull AS domnotnull, + t.typdefault AS domdef, + pg_catalog.pg_get_userbyid(t.typowner) AS domowner, + pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment FROM - pg_operator po - {$where} - ORDER BY - po.oprname, oprleftname, oprrightname - "; + pg_catalog.pg_type t + WHERE + t.typtype = 'd' + AND t.typname = '{$domain}' + AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace + WHERE nspname = '{$this->_schema}')"; return $this->selectSet($sql); - } + } /** - * Returns all details for a particular operator - * @param $operator_oid The oid of the operator - * @return Function info + * Return all domains in current schema. Excludes domain constraints. + * @return All tables, sorted alphabetically */ - function getOperator($operator_oid) { - $this->clean($operator_oid); - + function getDomains() { $sql = " SELECT - po.oid, - po.oprname, - (SELECT typname FROM pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname, - (SELECT typname FROM pg_type pt WHERE pt.oid=po.oprright) AS oprrightname, - (SELECT typname FROM pg_type pt WHERE pt.oid=po.oprresult) AS resultname, - po.oprcanhash, - (SELECT oprname FROM pg_operator po2 WHERE po2.oid=po.oprcom) AS oprcom, - (SELECT oprname FROM pg_operator po2 WHERE po2.oid=po.oprnegate) AS oprnegate, - (SELECT oprname FROM pg_operator po2 WHERE po2.oid=po.oprlsortop) AS oprlsortop, - (SELECT oprname FROM pg_operator po2 WHERE po2.oid=po.oprltcmpop) AS oprltcmpop, - (SELECT oprname FROM pg_operator po2 WHERE po2.oid=po.oprgtcmpop) AS oprgtcmpop, - po.oprcode::regproc AS oprcode, - --(SELECT proname FROM pg_proc pp WHERE pp.oid=po.oprcode) AS oprcode, - (SELECT proname FROM pg_proc pp WHERE pp.oid=po.oprrest) AS oprrest, - (SELECT proname FROM pg_proc pp WHERE pp.oid=po.oprjoin) AS oprjoin + t.typname AS domname, + pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype, + t.typnotnull AS domnotnull, + t.typdefault AS domdef, + pg_catalog.pg_get_userbyid(t.typowner) AS domowner, + pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment FROM - pg_operator po + pg_catalog.pg_type t WHERE - po.oid='{$operator_oid}' - "; + t.typtype = 'd' + AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace + WHERE nspname='{$this->_schema}') + ORDER BY t.typname"; return $this->selectSet($sql); } /** - * Drops an operator - * @param $operator_oid The OID of the operator to drop - * @param $cascade True to cascade drop, false to restrict - * @return 0 success + * Get domain constraints + * @param $domain The name of the domain whose constraints to fetch + * @return A recordset */ - function dropOperator($operator_oid, $cascade) { - // Function comes in with $object as operator OID - $opr = $this->getOperator($operator_oid); - $this->fieldClean($opr->fields['oprname']); - - $schema = $this->schema(); + function getDomainConstraints($domain) { + $this->clean($domain); - $sql = "DROP OPERATOR {$schema}{$opr->fields['oprname']} ("; - // Quoting or formatting here??? - if ($opr->fields['oprleftname'] !== null) $sql .= $opr->fields['oprleftname'] . ', '; - else $sql .= "NONE, "; - if ($opr->fields['oprrightname'] !== null) $sql .= $opr->fields['oprrightname'] . ')'; - else $sql .= "NONE)"; - - if ($cascade) $sql .= " CASCADE"; + $sql = " + SELECT + conname, + contype, + pg_catalog.pg_get_constraintdef(oid, true) AS consrc + FROM + pg_catalog.pg_constraint + WHERE + contypid = ( + SELECT oid FROM pg_catalog.pg_type + WHERE typname='{$domain}' + AND typnamespace = ( + SELECT oid FROM pg_catalog.pg_namespace + WHERE nspname = '{$this->_schema}') + ) + ORDER BY conname"; - return $this->execute($sql); + return $this->selectSet($sql); } - // User functions - /** - * Changes a user's password - * @param $username The username - * @param $password The new password + * Creates a domain + * @param $domain The name of the domain to create + * @param $type The base type for the domain + * @param $length Optional type length + * @param $array True for array type, false otherwise + * @param $notnull True for NOT NULL, false otherwise + * @param $default Default value for domain + * @param $check A CHECK constraint if there is one * @return 0 success */ - function changePassword($username, $password) { - $this->fieldClean($username); - $this->clean($password); + function createDomain($domain, $type, $length, $array, $notnull, $default, $check) { + $this->fieldClean($domain); - $sql = "ALTER USER \"{$username}\" WITH PASSWORD '{$password}'"; + $sql = "CREATE DOMAIN \"{$this->_schema}\".\"{$domain}\" AS "; - return $this->execute($sql); - } + if ($length == '') + $sql .= $type; + else { + switch ($type) { + // Have to account for weird placing of length for with/without + // time zone types + case 'timestamp with time zone': + case 'timestamp without time zone': + $qual = substr($type, 9); + $sql .= "timestamp({$length}){$qual}"; + break; + case 'time with time zone': + case 'time without time zone': + $qual = substr($type, 4); + $sql .= "time({$length}){$qual}"; + break; + default: + $sql .= "{$type}({$length})"; + } + } - /** - * Returns all users in the database cluster - * @return All users - */ - function getUsers() { - $sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires"; - if ($this->hasUserSessionDefaults()) $sql .= ", useconfig"; - $sql .= " FROM pg_user ORDER BY usename"; + // Add array qualifier, if requested + if ($array) $sql .= '[]'; - return $this->selectSet($sql); + if ($notnull) $sql .= ' NOT NULL'; + if ($default != '') $sql .= " DEFAULT {$default}"; + if ($this->hasDomainConstraints() && $check != '') $sql .= " CHECK ({$check})"; + + return $this->execute($sql); } /** - * Returns information about a single user - * @param $username The username of the user to retrieve - * @return The user's data + * Alters a domain + * @param $domain The domain to alter + * @param $domdefault The domain default + * @param $domnotnull True for NOT NULL, false otherwise + * @param $domowner The domain owner + * @return 0 success + * @return -1 transaction error + * @return -2 default error + * @return -3 not null error + * @return -4 owner error */ - function getUser($username) { - $this->clean($username); + function alterDomain($domain, $domdefault, $domnotnull, $domowner) { + $this->fieldClean($domain); + $this->fieldClean($domowner); - $sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires"; - if ($this->hasUserSessionDefaults()) $sql .= ", useconfig"; - $sql .= " FROM pg_user WHERE usename='{$username}'"; + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } - return $this->selectSet($sql); - } + // Default + if ($domdefault == '') + $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" DROP DEFAULT"; + else + $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" SET DEFAULT {$domdefault}"; - /** - * Determines whether or not a user is a super user - * @param $username The username of the user - * @return True if is a super user, false otherwise - */ - function isSuperUser($username) { - $this->clean($username); + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; + } - if (function_exists('pg_parameter_status')) { - $val = pg_parameter_status($this->conn->_connectionID, 'is_superuser'); - if ($val !== false) return $val == 'on'; + // NOT NULL + if ($domnotnull) + $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" SET NOT NULL"; + else + $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" DROP NOT NULL"; + + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; } - $sql = "SELECT usesuper FROM pg_user WHERE usename='{$username}'"; + // Owner + $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" OWNER TO \"{$domowner}\""; - $usesuper = $this->selectField($sql, 'usesuper'); - if ($usesuper == -1) return false; - else return $usesuper == 't'; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; + } + + return $this->endTransaction(); } /** - * Creates a new user - * @param $username The username of the user to create - * @param $password A password for the user - * @param $createdb boolean Whether or not the user can create databases - * @param $createuser boolean Whether or not the user can create other users - * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire - * @param $group (array) The groups to create the user in + * Drops a domain. + * @param $domain The name of the domain to drop + * @param $cascade True to cascade drop, false to restrict * @return 0 success */ - function createUser($username, $password, $createdb, $createuser, $expiry, $groups) { - $this->fieldClean($username); - $this->clean($password); - $this->clean($expiry); - $this->fieldArrayClean($groups); + function dropDomain($domain, $cascade) { + $this->fieldClean($domain); - $sql = "CREATE USER \"{$username}\""; - if ($password != '') $sql .= " WITH PASSWORD '{$password}'"; - $sql .= ($createdb) ? ' CREATEDB' : ' NOCREATEDB'; - $sql .= ($createuser) ? ' CREATEUSER' : ' NOCREATEUSER'; - if (is_array($groups) && sizeof($groups) > 0) $sql .= " IN GROUP \"" . join('", "', $groups) . "\""; - if ($expiry != '') $sql .= " VALID UNTIL '{$expiry}'"; - else $sql .= " VALID UNTIL 'infinity'"; + $sql = "DROP DOMAIN \"{$this->_schema}\".\"{$domain}\""; + if ($cascade) $sql .= " CASCADE"; return $this->execute($sql); } /** - * Adjusts a user's info - * @param $username The username of the user to modify - * @param $password A new password for the user - * @param $createdb boolean Whether or not the user can create databases - * @param $createuser boolean Whether or not the user can create other users - * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire. + * Adds a check constraint to a domain + * @param $domain The domain to which to add the check + * @param $definition The definition of the check + * @param $name (optional) The name to give the check, otherwise default name is assigned * @return 0 success */ - function setUser($username, $password, $createdb, $createuser, $expiry) { - $this->fieldClean($username); - $this->clean($password); - $this->clean($expiry); + function addDomainCheckConstraint($domain, $definition, $name = '') { + $this->fieldClean($domain); + $this->fieldClean($name); - $sql = "ALTER USER \"{$username}\""; - if ($password != '') $sql .= " WITH PASSWORD '{$password}'"; - $sql .= ($createdb) ? ' CREATEDB' : ' NOCREATEDB'; - $sql .= ($createuser) ? ' CREATEUSER' : ' NOCREATEUSER'; - if ($expiry != '') $sql .= " VALID UNTIL '{$expiry}'"; - else $sql .= " VALID UNTIL 'infinity'"; + $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" ADD "; + if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; + $sql .= "CHECK ({$definition})"; return $this->execute($sql); } /** - * Removes a user - * @param $username The username of the user to drop + * Drops a domain constraint + * @param $domain The domain from which to remove the constraint + * @param $constraint The constraint to remove + * @param $cascade True to cascade, false otherwise * @return 0 success */ - function dropUser($username) { - $this->fieldClean($username); + function dropDomainConstraint($domain, $constraint, $cascade) { + $this->fieldClean($domain); + $this->fieldClean($constraint); - $sql = "DROP USER \"{$username}\""; + $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" DROP CONSTRAINT \"{$constraint}\""; + if ($cascade) $sql .= " CASCADE"; return $this->execute($sql); } - // Group functions + // Function functions /** - * Returns all groups in the database cluser - * @return All groups + * Returns all details for a particular function + * @param $func The name of the function to retrieve + * @return Function info */ - function getGroups() { - $sql = "SELECT groname FROM pg_group ORDER BY groname"; + function getFunction($function_oid) { + $this->clean($function_oid); + + $sql = " + SELECT + pc.oid AS prooid, proname, pg_catalog.pg_get_userbyid(proowner) AS proowner, + nspname as proschema, lanname as prolanguage, procost, prorows, + pg_catalog.format_type(prorettype, NULL) as proresult, prosrc, + probin, proretset, proisstrict, provolatile, prosecdef, + pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments, + proargnames AS proargnames, + pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment, + proconfig + FROM + pg_catalog.pg_proc pc, pg_catalog.pg_language pl, + pg_catalog.pg_namespace pn + WHERE + pc.oid = '{$function_oid}'::oid AND pc.prolang = pl.oid + AND pc.pronamespace = pn.oid + "; return $this->selectSet($sql); } /** - * Returns users in a specific group - * @param $groname The name of the group - * @return All users in the group + * Returns a list of all functions in the database + * @param $all If true, will find all available functions, if false just those in search path + * @param $type If not null, will find all functions with return value = type + * + * @return All functions */ - function getGroup($groname) { - $this->clean($groname); + function getFunctions($all = false, $type = null) { + if ($all) { + $where = 'pg_catalog.pg_function_is_visible(p.oid)'; + $distinct = 'DISTINCT ON (p.proname)'; - $sql = "SELECT grolist FROM pg_group WHERE groname = '{$groname}'"; - - $grodata = $this->selectSet($sql); - if ($grodata->fields['grolist'] !== null && $grodata->fields['grolist'] != '{}') { - $members = $grodata->fields['grolist']; - $members = ereg_replace("\{|\}","",$members); - $this->clean($members); - - $sql = "SELECT usename FROM pg_user WHERE usesysid IN ({$members}) ORDER BY usename"; + if ($type) { + $where .= " AND p.prorettype = (select oid from pg_catalog.pg_type p where p.typname = 'trigger') "; + } + } + else { + $where = "n.nspname = '{$this->_schema}'"; + $distinct = ''; } - else $sql = "SELECT usename FROM pg_user WHERE false"; + + $sql = " + SELECT + {$distinct} + p.oid AS prooid, + p.proname, + p.proretset, + pg_catalog.format_type(p.prorettype, NULL) AS proresult, + pg_catalog.oidvectortypes(p.proargtypes) AS proarguments, + pl.lanname AS prolanguage, + pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment, + p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, + CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns, + u.usename AS proowner + FROM pg_catalog.pg_proc p + INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace + INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang + LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner + WHERE NOT p.proisagg + AND {$where} + ORDER BY p.proname, proresult + "; return $this->selectSet($sql); } /** - * Creates a new group - * @param $groname The name of the group - * @param $users An array of users to add to the group - * @return 0 success + * Returns an array containing a function's properties + * @param $f The array of data for the function + * @return An array containing the properties */ - function createGroup($groname, $users) { - $this->fieldClean($groname); + function getFunctionProperties($f) { + $temp = array(); - $sql = "CREATE GROUP \"{$groname}\""; + // Volatility + if ($f['provolatile'] == 'v') + $temp[] = 'VOLATILE'; + elseif ($f['provolatile'] == 'i') + $temp[] = 'IMMUTABLE'; + elseif ($f['provolatile'] == 's') + $temp[] = 'STABLE'; + else + return -1; - if (is_array($users) && sizeof($users) > 0) { - $this->fieldArrayClean($users); - $sql .= ' WITH USER "' . join('", "', $users) . '"'; - } + // Null handling + $f['proisstrict'] = $this->phpBool($f['proisstrict']); + if ($f['proisstrict']) + $temp[] = 'RETURNS NULL ON NULL INPUT'; + else + $temp[] = 'CALLED ON NULL INPUT'; - return $this->execute($sql); + // Security + $f['prosecdef'] = $this->phpBool($f['prosecdef']); + if ($f['prosecdef']) + $temp[] = 'SECURITY DEFINER'; + else + $temp[] = 'SECURITY INVOKER'; + + return $temp; } /** - * Removes a group - * @param $groname The name of the group to drop + * Updates (replaces) a function. + * @param $function_oid The OID of the function + * @param $funcname The name of the function to create + * @param $newname The new name for the function + * @param $args The array of argument types + * @param $returns The return type + * @param $definition The definition for the new function + * @param $language The language the function is written for + * @param $flags An array of optional flags + * @param $setof True if returns a set, false otherwise + * @param $comment The comment on the function * @return 0 success + * @return -1 transaction error + * @return -3 create function error + * @return -4 comment error + * @return -5 rename function error + * @return -6 alter owner error + * @return -7 alter schema error */ - function dropGroup($groname) { - $this->fieldClean($groname); + function setFunction($function_oid, $funcname, $newname, $args, $returns, $definition, $language, $flags, $setof, $funcown, $newown, $funcschema, $newschema, $cost, $rows, $comment) { + // Begin a transaction + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } - $sql = "DROP GROUP \"{$groname}\""; + // Replace the existing function + $status = $this->createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, true); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } - return $this->execute($sql); + // Comment on the function + $this->fieldClean($funcname); + $this->clean($comment); + $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; + } + + // Rename the function, if necessary + $this->fieldClean($newname); + if ($funcname != $newname) { + $sql = "ALTER FUNCTION \"{$this->_schema}\".\"{$funcname}\"({$args}) RENAME TO \"{$newname}\""; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -5; + } + + $funcname = $newname; + } + + // Alter the owner, if necessary + if ($this->hasFunctionAlterOwner()) { + $this->fieldClean($newown); + if ($funcown != $newown) { + $sql = "ALTER FUNCTION \"{$this->_schema}\".\"{$funcname}\"({$args}) OWNER TO \"{$newown}\""; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -6; + } + } + + } + + // Alter the schema, if necessary + if ($this->hasFunctionAlterSchema()) { + $this->fieldClean($newschema); + if ($funcschema != $newschema) { + $sql = "ALTER FUNCTION \"{$this->_schema}\".\"{$funcname}\"({$args}) SET SCHEMA \"{$newschema}\""; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -7; + } + } + } + + return $this->endTransaction(); } /** - * Adds a group member - * @param $groname The name of the group - * @param $user The name of the user to add to the group + * Creates a new function. + * @param $funcname The name of the function to create + * @param $args A comma separated string of types + * @param $returns The return type + * @param $definition The definition for the new function + * @param $language The language the function is written for + * @param $flags An array of optional flags + * @param $setof True if it returns a set, false otherwise + * @param $rows number of rows planner should estimate will be returned + * @param $cost cost the planner should use in the function execution step + * @param $replace (optional) True if OR REPLACE, false for normal * @return 0 success */ - function addGroupMember($groname, $user) { - $this->fieldClean($groname); - $this->fieldClean($user); + function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $replace = false) { + $this->fieldClean($funcname); + $this->clean($args); + $this->clean($language); + $this->arrayClean($flags); + $this->clean($cost); + $this->clean($rows); - $sql = "ALTER GROUP \"{$groname}\" ADD USER \"{$user}\""; + $sql = "CREATE"; + if ($replace) $sql .= " OR REPLACE"; + $sql .= " FUNCTION \"{$this->_schema}\".\"{$funcname}\" ("; + + if ($args != '') + $sql .= $args; + + // For some reason, the returns field cannot have quotes... + $sql .= ") RETURNS "; + if ($setof) $sql .= "SETOF "; + $sql .= "{$returns} AS "; + + if (is_array($definition)) { + $this->arrayClean($definition); + $sql .= "'" . $definition[0] . "'"; + if ($definition[1]) { + $sql .= ",'" . $definition[1] . "'"; + } + } else { + $this->clean($definition); + $sql .= "'" . $definition . "'"; + } + + $sql .= " LANGUAGE \"{$language}\""; + + // Add costs + if (!empty($cost)) + $sql .= " COST {$cost}"; + + if ($rows <> 0 ){ + $sql .= " ROWS {$rows}"; + } + + // Add flags + foreach ($flags as $v) { + // Skip default flags + if ($v == '') continue; + else $sql .= "\n{$v}"; + } return $this->execute($sql); } /** - * Removes a group member - * @param $groname The name of the group - * @param $user The name of the user to remove from the group + * Drops a function. + * @param $function_oid The OID of the function to drop + * @param $cascade True to cascade drop, false to restrict * @return 0 success */ - function dropGroupMember($groname, $user) { - $this->fieldClean($groname); - $this->fieldClean($user); + function dropFunction($function_oid, $cascade) { + // Function comes in with $object as function OID + $fn = $this->getFunction($function_oid); + $this->fieldClean($fn->fields['proname']); - $sql = "ALTER GROUP \"{$groname}\" DROP USER \"{$user}\""; + $sql = "DROP FUNCTION \"{$this->_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})"; + if ($cascade) $sql .= " CASCADE"; return $this->execute($sql); } @@ -3051,41 +4108,55 @@ class Postgres extends ADODB_base { // Type functions /** + * Returns all details for a particular type + * @param $typname The name of the view to retrieve + * @return Type info + */ + function getType($typname) { + $this->clean($typname); + + $sql = "SELECT typtype, typbyval, typname, typinput AS typin, typoutput AS typout, typlen, typalign + FROM pg_type WHERE typname='{$typname}'"; + + return $this->selectSet($sql); + } + + /** * Returns a list of all types in the database * @param $all If true, will find all available functions, if false just those in search path * @param $tabletypes If true, will include table types - * @param $domains Ignored + * @param $domains If true, will include domains * @return A recordet */ function getTypes($all = false, $tabletypes = false, $domains = false) { - global $conf; - - if ($all || $conf['show_system']) { - $where = ''; - } else { - $where = "AND pt.oid > '{$this->_lastSystemOID}'::oid"; - } + if ($all) + $where = '1 = 1'; + else + $where = "n.nspname = '{$this->_schema}'"; // Never show system table types - $where2 = "AND c.oid > '{$this->_lastSystemOID}'::oid"; + $where2 = "AND c.relnamespace NOT IN (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname LIKE 'pg@_%' ESCAPE '@')"; // Create type filter $tqry = "'c'"; if ($tabletypes) $tqry .= ", 'r', 'v'"; + // Create domain filter + if (!$domains) + $where .= " AND t.typtype != 'd'"; + $sql = "SELECT - pt.typname AS basename, - pt.typname, + t.typname AS basename, + pg_catalog.format_type(t.oid, NULL) AS typname, pu.usename AS typowner, - (SELECT description FROM pg_description pd WHERE pt.oid=pd.objoid) AS typcomment - FROM - pg_type pt, - pg_user pu - WHERE - pt.typowner = pu.usesysid - AND (pt.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_class c WHERE c.oid = pt.typrelid {$where2})) - AND typname !~ '^_' - {$where} + t.typtype, + pg_catalog.obj_description(t.oid, 'pg_type') AS typcomment + FROM (pg_catalog.pg_type t + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace) + LEFT JOIN pg_catalog.pg_user pu ON t.typowner = pu.usesysid + WHERE (t.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid {$where2})) + AND t.typname !~ '^_' + AND {$where} ORDER BY typname "; @@ -3093,26 +4164,12 @@ class Postgres extends ADODB_base { } /** - * Returns all details for a particular type - * @param $typname The name of the view to retrieve - * @return Type info - */ - function getType($typname) { - $this->clean($typname); - - $sql = "SELECT typtype, typbyval, typname, typinput AS typin, typoutput AS typout, typlen, typalign - FROM pg_type WHERE typname='{$typname}'"; - - return $this->selectSet($sql); - } - - /** * Creates a new type * @param ... * @return 0 success */ function createType($typname, $typin, $typout, $typlen, $typdef, - $typelem, $typdelim, $typbyval, $typalign, $typstorage) { + $typelem, $typdelim, $typbyval, $typalign, $typstorage) { $this->fieldClean($typname); $this->fieldClean($typin); $this->fieldClean($typout); @@ -3145,7 +4202,311 @@ class Postgres extends ADODB_base { function dropType($typname, $cascade) { $this->fieldClean($typname); - $sql = "DROP TYPE \"{$typname}\""; + $sql = "DROP TYPE \"{$this->_schema}\".\"{$typname}\""; + if ($cascade) $sql .= " CASCADE"; + + return $this->execute($sql); + } + + /** + * Creates a new enum type in the database + * @param $name The name of the type + * @param $values An array of values + * @param $typcomment Type comment + * @return 0 success + * @return -1 transaction error + * @return -2 no values supplied + */ + function createEnumType($name, $values, $typcomment) { + $this->fieldClean($name); + $this->clean($typcomment); + + if (empty($values)) return -2; + + $status = $this->beginTransaction(); + if ($status != 0) return -1; + + $values = array_unique($values); + + $nbval = count($values); + + for ($i = 0; $i < $nbval; $i++) + $this->clean($values[$i]); + + $sql = "CREATE TYPE \"{$this->_schema}\".\"{$name}\" AS ENUM ('"; + $sql.= implode("','", $values); + $sql .= "')"; + + $status = $this->execute($sql); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + + if ($typcomment != '') { + $status = $this->setComment('TYPE', $name, '', $typcomment, true); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + } + + return $this->endTransaction(); + + } + + /** + * Get defined values for a given enum + * @return A recordset + */ + function getEnumValues($name) { + $this->fieldClean($name); + + $sql = "SELECT enumlabel AS enumval + FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON (t.oid=e.enumtypid) + WHERE t.typname = '{$name}' ORDER BY e.oid"; + return $this->selectSet($sql); + } + + /** + * Creates a new composite type in the database + * @param $name The name of the type + * @param $fields The number of fields + * @param $field An array of field names + * @param $type An array of field types + * @param $array An array of '' or '[]' for each type if it's an array or not + * @param $length An array of field lengths + * @param $colcomment An array of comments + * @param $typcomment Type comment + * @return 0 success + * @return -1 no fields supplied + */ + function createCompositeType($name, $fields, $field, $type, $array, $length, $colcomment, $typcomment) { + $this->fieldClean($name); + $this->clean($typcomment); + + $status = $this->beginTransaction(); + if ($status != 0) return -1; + + $found = false; + $first = true; + $comment_sql = ''; // Accumulate comments for the columns + $sql = "CREATE TYPE \"{$this->_schema}\".\"{$name}\" AS ("; + for ($i = 0; $i < $fields; $i++) { + $this->fieldClean($field[$i]); + $this->clean($type[$i]); + $this->clean($length[$i]); + $this->clean($colcomment[$i]); + + // Skip blank columns - for user convenience + if ($field[$i] == '' || $type[$i] == '') continue; + // If not the first column, add a comma + if (!$first) $sql .= ", "; + else $first = false; + + switch ($type[$i]) { + // Have to account for weird placing of length for with/without + // time zone types + case 'timestamp with time zone': + case 'timestamp without time zone': + $qual = substr($type[$i], 9); + $sql .= "\"{$field[$i]}\" timestamp"; + if ($length[$i] != '') $sql .= "({$length[$i]})"; + $sql .= $qual; + break; + case 'time with time zone': + case 'time without time zone': + $qual = substr($type[$i], 4); + $sql .= "\"{$field[$i]}\" time"; + if ($length[$i] != '') $sql .= "({$length[$i]})"; + $sql .= $qual; + break; + default: + $sql .= "\"{$field[$i]}\" {$type[$i]}"; + if ($length[$i] != '') $sql .= "({$length[$i]})"; + } + // Add array qualifier if necessary + if ($array[$i] == '[]') $sql .= '[]'; + + if ($colcomment[$i] != '') $comment_sql .= "COMMENT ON COLUMN \"{$this->_schema}\".\"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n"; + + $found = true; + } + + if (!$found) return -1; + + $sql .= ")"; + + $status = $this->execute($sql); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + + if ($typcomment != '') { + $status = $this->setComment('TYPE', $name, '', $typcomment, true); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + } + + if ($comment_sql != '') { + $status = $this->execute($comment_sql); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + } + return $this->endTransaction(); + } + + /** + * Returns a list of all casts in the database + * @return All casts + */ + function getCasts() { + global $conf; + + if ($conf['show_system']) + $where = ''; + else + $where = " + AND n1.nspname NOT LIKE 'pg\\\\_%' + AND n2.nspname NOT LIKE 'pg\\\\_%' + AND n3.nspname NOT LIKE 'pg\\\\_%' + "; + + $sql = " + SELECT + c.castsource::pg_catalog.regtype AS castsource, + c.casttarget::pg_catalog.regtype AS casttarget, + CASE WHEN c.castfunc=0 THEN NULL + ELSE c.castfunc::pg_catalog.regprocedure END AS castfunc, + c.castcontext, + obj_description(c.oid, 'pg_cast') as castcomment + FROM + (pg_catalog.pg_cast c LEFT JOIN pg_catalog.pg_proc p ON c.castfunc=p.oid JOIN pg_catalog.pg_namespace n3 ON p.pronamespace=n3.oid), + pg_catalog.pg_type t1, + pg_catalog.pg_type t2, + pg_catalog.pg_namespace n1, + pg_catalog.pg_namespace n2 + WHERE + c.castsource=t1.oid + AND c.casttarget=t2.oid + AND t1.typnamespace=n1.oid + AND t2.typnamespace=n2.oid + {$where} + ORDER BY 1, 2 + "; + + return $this->selectSet($sql); + } + + /** + * Returns a list of all conversions in the database + * @return All conversions + */ + function getConversions() { + $sql = " + SELECT + c.conname, + pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding, + pg_catalog.pg_encoding_to_char(c.contoencoding) AS contoencoding, + c.condefault, + pg_catalog.obj_description(c.oid, 'pg_conversion') AS concomment + FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n + WHERE n.oid = c.connamespace + AND n.nspname='{$this->_schema}' + ORDER BY 1; + "; + + return $this->selectSet($sql); + } + + // Rule functions + + /** + * Returns a list of all rules on a table OR view + * @param $table The table to find rules for + * @return A recordset + */ + function getRules($table) { + $this->clean($table); + + $sql = " + SELECT * + FROM pg_catalog.pg_rules + WHERE + schemaname='{$this->_schema}' AND tablename='{$table}' + ORDER BY rulename + "; + + return $this->selectSet($sql); + } + + /** + * Edits a rule on a table OR view + * @param $name The name of the new rule + * @param $event SELECT, INSERT, UPDATE or DELETE + * @param $table Table on which to create the rule + * @param $where When to execute the rule, '' indicates always + * @param $instead True if an INSTEAD rule, false otherwise + * @param $type NOTHING for a do nothing rule, SOMETHING to use given action + * @param $action The action to take + * @return 0 success + * @return -1 invalid event + */ + function setRule($name, $event, $table, $where, $instead, $type, $action) { + return $this->createRule($name, $event, $table, $where, $instead, $type, $action, true); + } + + /** + * Creates a rule + * @param $name The name of the new rule + * @param $event SELECT, INSERT, UPDATE or DELETE + * @param $table Table on which to create the rule + * @param $where When to execute the rule, '' indicates always + * @param $instead True if an INSTEAD rule, false otherwise + * @param $type NOTHING for a do nothing rule, SOMETHING to use given action + * @param $action The action to take + * @param $replace (optional) True to replace existing rule, false otherwise + * @return 0 success + * @return -1 invalid event + */ + function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false) { + $this->fieldClean($name); + $this->fieldClean($table); + if (!in_array($event, $this->rule_events)) return -1; + + $schema = $this->schema(); + + $sql = "CREATE"; + if ($replace) $sql .= " OR REPLACE"; + $sql .= " RULE \"{$name}\" AS ON {$event} TO {$schema}\"{$table}\""; + // Can't escape WHERE clause + if ($where != '') $sql .= " WHERE {$where}"; + $sql .= " DO"; + if ($instead) $sql .= " INSTEAD"; + if ($type == 'NOTHING') + $sql .= " NOTHING"; + else $sql .= " ({$action})"; + + return $this->execute($sql); + } + + /** + * Removes a rule from a table OR view + * @param $rule The rule to drop + * @param $relation The relation from which to drop + * @param $cascade True to cascade drop, false to restrict + * @return 0 success + */ + function dropRule($rule, $relation, $cascade) { + $this->fieldClean($rule); + $this->fieldClean($relation); + + $sql = "DROP RULE \"{$rule}\" ON \"{$this->_schema}\".\"{$relation}\""; if ($cascade) $sql .= " CASCADE"; return $this->execute($sql); @@ -3154,6 +4515,52 @@ class Postgres extends ADODB_base { // Trigger functions /** + * Grabs a single trigger + * @param $table The name of a table whose triggers to retrieve + * @param $trigger The name of the trigger to retrieve + * @return A recordset + */ + function getTrigger($table, $trigger) { + $this->clean($table); + $this->clean($trigger); + + $sql = " + SELECT * FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c + WHERE t.tgrelid=c.oid AND c.relname='{$table}' AND t.tgname='{$trigger}' + AND c.relnamespace=( + SELECT oid FROM pg_catalog.pg_namespace + WHERE nspname='{$this->_schema}')"; + + return $this->selectSet($sql); + } + + /** + * Grabs a list of triggers on a table + * @param $table The name of a table whose triggers to retrieve + * @return A recordset + */ + function getTriggers($table = '') { + $this->clean($table); + + $sql = "SELECT + t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef, + CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid, + p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, + ns.nspname AS pronamespace + FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns + WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' + AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}')) + AND (NOT tgisconstraint OR NOT EXISTS + (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c + ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) + WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f')) + AND p.oid=t.tgfoid + AND p.pronamespace = ns.oid"; + + return $this->selectSet($sql); + } + + /** * A helper function for getTriggers that translates * an array of attribute numbers to an array of field names. * @param $trigger An array containing fields from the trigger table @@ -3255,24 +4662,10 @@ class Postgres extends ADODB_base { } /** - * Grabs a list of triggers on a table - * @param $table The name of a table whose triggers to retrieve - * @return A recordset + * Returns a list of all functions that can be used in triggers */ - function getTriggers($table = '') { - $this->clean($table); - - // We include constraint triggers - $sql = "SELECT t.tgname, t.tgisconstraint, t.tgdeferrable, t.tginitdeferred, t.tgtype, - t.tgargs, t.tgnargs, t.tgconstrrelid, - (SELECT relname FROM pg_class c2 WHERE c2.oid=t.tgconstrrelid) AS tgconstrrelname, - (SELECT proname FROM pg_proc p WHERE t.tgfoid=p.oid) AS tgfname, - c.relname, NULL AS tgdef - FROM pg_trigger t, pg_class c - WHERE t.tgrelid=c.oid - AND c.relname='{$table}'"; - - return $this->selectSet($sql); + function getTriggerFunctions() { + return $this->getFunctions(true, 'trigger'); } /** @@ -3292,13 +4685,30 @@ class Postgres extends ADODB_base { /* No Statement Level Triggers in PostgreSQL (by now) */ $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime} - {$tgevent} ON \"{$table}\" + {$tgevent} ON \"{$this->_schema}\".\"{$table}\" FOR EACH {$tgfrequency} EXECUTE PROCEDURE \"{$tgproc}\"({$tgargs})"; return $this->execute($sql); } /** + * Alters a trigger + * @param $table The name of the table containing the trigger + * @param $trigger The name of the trigger to alter + * @param $name The new name for the trigger + * @return 0 success + */ + function alterTrigger($table, $trigger, $name) { + $this->fieldClean($table); + $this->fieldClean($trigger); + $this->fieldClean($name); + + $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$this->_schema}\".\"{$table}\" RENAME TO \"{$name}\""; + + return $this->execute($sql); + } + + /** * Drops a trigger * @param $tgname The name of the trigger to drop * @param $table The table from which to drop the trigger @@ -3309,13 +4719,1429 @@ class Postgres extends ADODB_base { $this->fieldClean($tgname); $this->fieldClean($table); - $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$table}\""; + $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$this->_schema}\".\"{$table}\""; if ($cascade) $sql .= " CASCADE"; return $this->execute($sql); } - // Privilege functions + /** + * Enables a trigger + * @param $tgname The name of the trigger to enable + * @param $table The table in which to enable the trigger + * @return 0 success + */ + function enableTrigger($tgname, $table) { + $this->fieldClean($tgname); + $this->fieldClean($table); + + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ENABLE TRIGGER \"{$tgname}\""; + + return $this->execute($sql); + } + + /** + * Disables a trigger + * @param $tgname The name of the trigger to disable + * @param $table The table in which to disable the trigger + * @return 0 success + */ + function disableTrigger($tgname, $table) { + $this->fieldClean($tgname); + $this->fieldClean($table); + + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\""; + + return $this->execute($sql); + } + + // Operator functions + + /** + * Returns a list of all operators in the database + * @return All operators + */ + function getOperators() { + // We stick with the subselects here, as you cannot ORDER BY a regtype + $sql = " + SELECT + po.oid, po.oprname, + (SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname, + (SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname, + po.oprresult::pg_catalog.regtype AS resultname, + pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment + FROM + pg_catalog.pg_operator po + WHERE + po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}') + ORDER BY + po.oprname, oprleftname, oprrightname + "; + + return $this->selectSet($sql); + } + + /** + * Returns all details for a particular operator + * @param $operator_oid The oid of the operator + * @return Function info + */ + function getOperator($operator_oid) { + $this->clean($operator_oid); + + $sql = " + SELECT + po.oid, po.oprname, + oprleft::pg_catalog.regtype AS oprleftname, + oprright::pg_catalog.regtype AS oprrightname, + oprresult::pg_catalog.regtype AS resultname, + po.oprcanhash, + oprcom::pg_catalog.regoperator AS oprcom, + oprnegate::pg_catalog.regoperator AS oprnegate, + oprlsortop::pg_catalog.regoperator AS oprlsortop, + oprrsortop::pg_catalog.regoperator AS oprrsortop, + oprltcmpop::pg_catalog.regoperator AS oprltcmpop, + oprgtcmpop::pg_catalog.regoperator AS oprgtcmpop, + po.oprcode::pg_catalog.regproc AS oprcode, + po.oprrest::pg_catalog.regproc AS oprrest, + po.oprjoin::pg_catalog.regproc AS oprjoin + FROM + pg_catalog.pg_operator po + WHERE + po.oid='{$operator_oid}' + "; + + return $this->selectSet($sql); + } + + /** + * Drops an operator + * @param $operator_oid The OID of the operator to drop + * @param $cascade True to cascade drop, false to restrict + * @return 0 success + */ + function dropOperator($operator_oid, $cascade) { + // Function comes in with $object as operator OID + $opr = $this->getOperator($operator_oid); + $this->fieldClean($opr->fields['oprname']); + + $schema = $this->schema(); + + $sql = "DROP OPERATOR {$schema}{$opr->fields['oprname']} ("; + // Quoting or formatting here??? + if ($opr->fields['oprleftname'] !== null) $sql .= $opr->fields['oprleftname'] . ', '; + else $sql .= "NONE, "; + if ($opr->fields['oprrightname'] !== null) $sql .= $opr->fields['oprrightname'] . ')'; + else $sql .= "NONE)"; + + if ($cascade) $sql .= " CASCADE"; + + return $this->execute($sql); + } + + // Operator Class functions + + /** + * Gets all opclasses + * + * @return A recordset + */ + + function getOpClasses() { + + $sql = " + SELECT + pa.amname, po.opcname, + po.opcintype::pg_catalog.regtype AS opcintype, + po.opcdefault, + pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment + FROM + pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn + WHERE + po.opcmethod=pa.oid + AND po.opcnamespace=pn.oid + AND pn.nspname='{$this->_schema}' + ORDER BY 1,2 + "; + + return $this->selectSet($sql); + } + + // FTS functions + + /** + * Creates a new FTS configuration. + * @param string $cfgname The name of the FTS configuration to create + * @param string $parser The parser to be used in new FTS configuration + * @param string $locale Locale of the FTS configuration + * @param string $template The existing FTS configuration to be used as template for the new one + * @param string $withmap Should we copy whole map of existing FTS configuration to the new one + * @param string $makeDefault Should this configuration be the default for locale given + * @param string $comment If omitted, defaults to nothing + * @return 0 success + */ + function createFtsConfiguration($cfgname, $parser = '', $template = '', $comment = '') { + $this->fieldClean($cfgname); + + $sql = "CREATE TEXT SEARCH CONFIGURATION \"{$cfgname}\" ("; + if ($parser != '') { + $this->fieldClean($parser['schema']); + $this->fieldClean($parser['parser']); + $parser = "\"{$parser['schema']}\".\"{$parser['parser']}\""; + $sql .= " PARSER = {$parser}"; + } + if ($template != '') { + $this->fieldClean($template); + $sql .= " COPY = \"{$template}\""; + } + $sql .= ")"; + + if ($comment != '') { + $status = $this->beginTransaction(); + if ($status != 0) return -1; + } + + // Create the FTS configuration + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + // Set the comment + if ($comment != '') { + $this->clean($comment); + $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + return $this->endTransaction(); + } + + return 0; + } + + /** + * Returns all FTS configurations available + */ + function getFtsConfigurations() { + $sql = " + SELECT + n.nspname as schema, + c.cfgname as name, + pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment + FROM + pg_catalog.pg_ts_config c + JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace + WHERE + pg_catalog.pg_ts_config_is_visible(c.oid) + ORDER BY + schema, name"; + return $this->selectSet($sql); + } + + /** + * Return all information related to a FTS configuration + * @param $ftscfg The name of the FTS configuration + * @return FTS configuration information + */ + function getFtsConfigurationByName($ftscfg) { + $this->clean($ftscfg); + $sql = " + SELECT + n.nspname as schema, + c.cfgname as name, + p.prsname as parser, + c.cfgparser as parser_id, + pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment + FROM pg_catalog.pg_ts_config c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace + LEFT JOIN pg_catalog.pg_ts_parser p ON p.oid = c.cfgparser + WHERE pg_catalog.pg_ts_config_is_visible(c.oid) + AND c.cfgname = '{$ftscfg}'"; + + return $this->selectSet($sql); + } + + /** + * Returns the map of FTS configuration given (list of mappings (tokens) and their processing dictionaries) + * + * @param string $ftscfg Name of the FTS configuration + */ + function getFtsConfigurationMap($ftscfg) { + $this->fieldClean($ftscfg); + $getOidSql = "SELECT oid FROM pg_catalog.pg_ts_config WHERE cfgname = '{$ftscfg}'"; + $oidSet = $this->selectSet($getOidSql); + $oid = $oidSet->fields['oid']; + + $sql = " + SELECT + (SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name, + (SELECT t.description FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS description, + c.cfgname AS cfgname, n.nspname ||'.'|| d.dictname as dictionaries + FROM + pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d, + pg_catalog.pg_namespace n + WHERE + c.oid = {$oid} AND m.mapcfg = c.oid and m.mapdict = d.oid and d.dictnamespace = n.oid + ORDER BY name + "; + return $this->selectSet($sql); + } + + /** + * Returns all FTS parsers available + */ + function getFtsParsers() { + $sql = " + SELECT + n.nspname as schema, + p.prsname as name, + pg_catalog.obj_description(p.oid, 'pg_ts_parser') as comment + FROM pg_catalog.pg_ts_parser p + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace + WHERE pg_catalog.pg_ts_parser_is_visible(p.oid) + ORDER BY schema, name"; + return $this->selectSet($sql); + } + + /** + * Returns all FTS dictionaries available + */ + function getFtsDictionaries() { + $sql = " + SELECT + n.nspname as schema, d.dictname as name, + pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment + FROM pg_catalog.pg_ts_dict d + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace + WHERE pg_catalog.pg_ts_dict_is_visible(d.oid) + ORDER BY schema, name;"; + return $this->selectSet($sql); + } + + /** + * Returns all FTS dictionary templates available + */ + function getFtsDictionaryTemplates() { + $sql = " + SELECT + n.nspname as schema, + t.tmplname as name, + ( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname + FROM pg_catalog.pg_proc p + LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace + WHERE t.tmplinit = p.oid ) AS init, + ( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname + FROM pg_catalog.pg_proc p + LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace + WHERE t.tmpllexize = p.oid ) AS lexize, + pg_catalog.obj_description(t.oid, 'pg_ts_template') as comment + FROM pg_catalog.pg_ts_template t + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace + WHERE pg_catalog.pg_ts_template_is_visible(t.oid) + ORDER BY schema, name;"; + return $this->selectSet($sql); + } + + /** + * Drops FTS coniguration + */ + function dropFtsConfiguration($ftscfg, $cascade) { + $this->fieldClean($ftscfg); + + $sql = "DROP TEXT SEARCH CONFIGURATION \"{$ftscfg}\""; + if ($cascade) $sql .= " CASCADE"; + + return $this->execute($sql); + } + + /** + * Drops FTS dictionary + * + * @todo Support of dictionary templates dropping + */ + function dropFtsDictionary($ftsdict, $cascade = true) { + $this->fieldClean($ftsdict); + + $sql = "DROP TEXT SEARCH DICTIONARY"; + $sql .= " \"{$ftsdict}\""; + if ($cascade) $sql .= " CASCADE"; + + return $this->execute($sql); + } + + /** + * Alters FTS configuration + */ + function updateFtsConfiguration($cfgname, $comment, $name) { + $this->fieldClean($cfgname); + $this->fieldClean($name); + $this->clean($comment); + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + // Only if the name has changed + if ($name != $cfgname) { + $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$cfgname}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + } + + return $this->endTransaction(); + } + + /** + * Creates a new FTS dictionary or FTS dictionary template. + * @param string $dictname The name of the FTS dictionary to create + * @param boolean $isTemplate Flag whether we create usual dictionary or dictionary template + * @param string $template The existing FTS dictionary to be used as template for the new one + * @param string $lexize The name of the function, which does transformation of input word + * @param string $init The name of the function, which initializes dictionary + * @param string $option Usually, it stores various options required for the dictionary + * @param string $comment If omitted, defaults to nothing + * @return 0 success + */ + function createFtsDictionary($dictname, $isTemplate = false, $template = '', $lexize = '', $init = '', $option = '', $comment = '') { + $this->fieldClean($dictname); + $this->fieldClean($template); + $this->fieldClean($lexize); + $this->fieldClean($init); + $this->fieldClean($option); + $this->clean($comment); + + $sql = "CREATE TEXT SEARCH"; + if ($isTemplate) { + $sql .= " TEMPLATE {$dictname} ("; + if ($lexize != '') $sql .= " LEXIZE = {$lexize}"; + if ($init != '') $sql .= ", INIT = {$init}"; + $sql .= ")"; + $whatToComment = 'TEXT SEARCH TEMPLATE'; + } else { + $sql .= " DICTIONARY {$dictname} ("; + if ($template != '') $sql .= " TEMPLATE = {$template}"; + if ($option != '') $sql .= ", {$option}"; + $sql .= ")"; + $whatToComment = 'TEXT SEARCH DICTIONARY'; + } + + if ($comment != '') { + $status = $this->beginTransaction(); + if ($status != 0) return -1; + } + + // Create the FTS dictionary + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + // Set the comment + if ($comment != '') { + $status = $this->setComment($whatToComment, $dictname, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + } + + return $this->endTransaction(); + } + + /** + * Alters FTS dictionary or dictionary template + */ + function updateFtsDictionary($dictname, $comment, $name) { + $this->fieldClean($dictname); + $this->fieldClean($name); + $this->clean($comment); + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $status = $this->setComment('TEXT SEARCH DICTIONARY', $dictname, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + // Only if the name has changed + if ($name != $dictname) { + $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$dictname}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + } + + return $this->endTransaction(); + } + + /** + * Return all information relating to a FTS dictionary + * @param $ftsdict The name of the FTS dictionary + * @return FTS dictionary information + */ + function getFtsDictionaryByName($ftsdict) { + $this->clean($ftsdict); + $sql = "SELECT + n.nspname as schema, + d.dictname as name, + ( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM + pg_catalog.pg_ts_template t + LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = t.tmplnamespace + WHERE d.dicttemplate = t.oid ) AS template, + d.dictinitoption as init, + pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment + FROM pg_catalog.pg_ts_dict d + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace + WHERE d.dictname = '{$ftsdict}' + AND pg_catalog.pg_ts_dict_is_visible(d.oid) + ORDER BY schema, name"; + + return $this->selectSet($sql); + } + + /** + * Creates/updates/deletes FTS mapping. + * @param string $cfgname The name of the FTS configuration to alter + * @param array $mapping Array of tokens' names + * @param string $action What to do with the mapping: add, alter or drop + * @param string $dictname Dictionary that will process tokens given or null in case of drop action + * @return 0 success + */ + function changeFtsMapping($ftscfg, $mapping, $action, $dictname = null) { + $this->fieldClean($ftscfg); + $this->fieldClean($dictname); + $this->arrayClean($mapping); + + if (count($mapping) > 0) { + switch ($action) { + case 'alter': + $whatToDo = "ALTER"; + break; + case 'drop': + $whatToDo = "DROP"; + break; + default: + $whatToDo = "ADD"; + break; + } + $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$ftscfg}\" {$whatToDo} MAPPING FOR "; + $sql .= implode(",", $mapping); + if ($action != 'drop' && !empty($dictname)) { + $sql .= " WITH {$dictname}"; + } + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + return $this->endTransaction(); + } else { + return -1; + } + } + + /** + * Return all information related to a given FTS configuration's mapping + * @param $ftscfg The name of the FTS configuration + * @param $mapping The name of the mapping + * @return FTS configuration information + */ + function getFtsMappingByName($ftscfg, $mapping) { + $this->fieldClean($ftscfg); + $this->fieldClean($mapping); + + $getOidSql = "SELECT oid, cfgparser FROM pg_catalog.pg_ts_config WHERE cfgname = '{$ftscfg}'"; + $oidSet = $this->selectSet($getOidSql); + $oid = $oidSet->fields['oid']; + $cfgparser = $oidSet->fields['cfgparser']; + + $getTokenIdSql = "SELECT tokid FROM pg_catalog.ts_token_type({$cfgparser}) WHERE alias = '{$mapping}'"; + $tokenIdSet = $this->selectSet($getTokenIdSql); + $tokid = $tokenIdSet->fields['tokid']; + + $sql = "SELECT + (SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name, + d.dictname as dictionaries + FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d + WHERE c.oid = {$oid} AND m.mapcfg = c.oid AND m.maptokentype = {$tokid} AND m.mapdict = d.oid + LIMIT 1;"; + return $this->selectSet($sql); + } + + /** + * Return list of FTS mappings possible for given parser (specified by given configuration since configuration + * can only have 1 parser) + */ + function getFtsMappings($ftscfg) { + $cfg = $this->getFtsConfigurationByName($ftscfg); + $sql = "SELECT alias AS name, description FROM pg_catalog.ts_token_type({$cfg->fields['parser_id']}) ORDER BY name"; + return $this->selectSet($sql); + } + + // Language functions + + /** + * Gets all languages + * @param $all True to get all languages, regardless of show_system + * @return A recordset + */ + function getLanguages($all = false) { + global $conf; + + if ($conf['show_system'] || $all) + $where = ''; + else + $where = 'WHERE lanispl'; + + $sql = " + SELECT + lanname, lanpltrusted, + lanplcallfoid::pg_catalog.regproc AS lanplcallf + FROM + pg_catalog.pg_language + {$where} + ORDER BY lanname + "; + + return $this->selectSet($sql); + } + + // Aggregate functions + + /** + * Creates a new aggregate in the database + * @param $name The name of the aggregate + * @param $basetype The input data type of the aggregate + * @param $sfunc The name of the state transition function for the aggregate + * @param $stype The data type for the aggregate's state value + * @param $ffunc The name of the final function for the aggregate + * @param $initcond The initial setting for the state value + * @param $sortop The sort operator for the aggregate + * @param $comment Aggregate comment + * @return 0 success + * @return -1 error + */ + function createAggregate($name, $basetype, $sfunc, $stype, $ffunc, $initcond, $sortop, $comment) { + $this->fieldClean($name); + $this->fieldClean($basetype); + $this->fieldClean($sfunc); + $this->fieldClean($stype); + $this->fieldClean($ffunc); + $this->fieldClean($initcond); + $this->fieldClean($sortop); + $this->clean($comment); + + $this->beginTransaction(); + + $schema = $this->schema(); + $sql = "CREATE AGGREGATE {$schema}\"{$name}\" (BASETYPE = \"{$basetype}\", SFUNC = \"{$sfunc}\", STYPE = \"{$stype}\""; + if(trim($ffunc) != '') $sql .= ", FINALFUNC = \"{$ffunc}\""; + if(trim($initcond) != '') $sql .= ", INITCOND = \"{$initcond}\""; + if(trim($sortop) != '') $sql .= ", SORTOP = \"{$sortop}\""; + $sql .= ")"; + + $status = $this->execute($sql); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + + if (trim($comment) != '') { + $status = $this->setComment('AGGREGATE', $name, '', $comment, $basetype); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + } + + return $this->endTransaction(); + } + + /** + * Renames an aggregate function + * @param $aggrname The actual name of the aggregate + * @param $aggrtype The actual input data type of the aggregate + * @param $newaggrname The new name of the aggregate + * @return 0 success + */ + function renameAggregate($aggrschema, $aggrname, $aggrtype, $newaggrname) { + $sql = "ALTER AGGREGATE \"{$aggrschema}\"" . '.' . "\"{$aggrname}\" (\"{$aggrtype}\") RENAME TO \"{$newaggrname}\""; + return $this->execute($sql); + } + + /** + * Removes an aggregate function from the database + * @param $aggrname The name of the aggregate + * @param $aggrtype The input data type of the aggregate + * @param $cascade True to cascade drop, false to restrict + * @return 0 success + */ + function dropAggregate($aggrname, $aggrtype, $cascade) { + $this->fieldClean($aggrname); + $this->fieldClean($aggrtype); + + $sql = "DROP AGGREGATE \"{$this->_schema}\".\"{$aggrname}\" (\"{$aggrtype}\")"; + if ($cascade) $sql .= " CASCADE"; + + return $this->execute($sql); + } + + /** + * Gets all information for an aggregate + * @param $name The name of the aggregate + * @param $basetype The input data type of the aggregate + * @return A recordset + */ + function getAggregate($name, $basetype) { + $this->fieldclean($name); + $this->fieldclean($basetype); + + $sql = " + SELECT p.proname, CASE p.proargtypes[0] + WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL + ELSE pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes, + a.aggtransfn, format_type(a.aggtranstype, NULL) AS aggstype, a.aggfinalfn, + a.agginitval, a.aggsortop, u.usename, pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment + FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a + WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid + AND p.proisagg AND n.nspname='{$this->_schema}' + AND p.proname='" . $name . "' + AND CASE p.proargtypes[0] + WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN '' + ELSE pg_catalog.format_type(p.proargtypes[0], NULL) + END ='" . $basetype . "'"; + + return $this->selectSet($sql); + } + + /** + * Gets all aggregates + * @return A recordset + */ + function getAggregates() { + $sql = "SELECT p.proname, CASE p.proargtypes[0] WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL ELSE + pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes, a.aggtransfn, u.usename, + pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment + FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a + WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid + AND p.proisagg AND n.nspname='{$this->_schema}' ORDER BY 1, 2"; + + return $this->selectSet($sql); + } + + /** + * Changes the owner of an aggregate function + * @param $aggrname The name of the aggregate + * @param $aggrtype The input data type of the aggregate + * @param $newaggrowner The new owner of the aggregate + * @return 0 success + */ + function changeAggregateOwner($aggrname, $aggrtype, $newaggrowner) { + $sql = "ALTER AGGREGATE \"{$this->_schema}\".\"{$aggrname}\" (\"{$aggrtype}\") OWNER TO \"{$newaggrowner}\""; + return $this->execute($sql); + } + + /** + * Changes the schema of an aggregate function + * @param $aggrname The name of the aggregate + * @param $aggrtype The input data type of the aggregate + * @param $newaggrschema The new schema for the aggregate + * @return 0 success + */ + function changeAggregateSchema($aggrname, $aggrtype, $newaggrschema) { + $sql = "ALTER AGGREGATE \"{$this->_schema}\".\"{$aggrname}\" (\"{$aggrtype}\") SET SCHEMA \"{$newaggrschema}\""; + return $this->execute($sql); + } + + /** + * Alters an aggregate + * @param $aggrname The actual name of the aggregate + * @param $aggrtype The actual input data type of the aggregate + * @param $aggrowner The actual owner of the aggregate + * @param $aggrschema The actual schema the aggregate belongs to + * @param $aggrcomment The actual comment for the aggregate + * @param $newaggrname The new name of the aggregate + * @param $newaggrowner The new owner of the aggregate + * @param $newaggrschema The new schema where the aggregate will belong to + * @param $newaggrcomment The new comment for the aggregate + * @return 0 success + * @return -1 change owner error + * @return -2 change comment error + * @return -3 change schema error + * @return -4 change name error + */ + function alterAggregate($aggrname, $aggrtype, $aggrowner, $aggrschema, $aggrcomment, $newaggrname, $newaggrowner, $newaggrschema, $newaggrcomment) { + // Clean fields + $this->fieldClean($aggrname); + $this->fieldClean($aggrtype); + $this->fieldClean($aggrowner); + $this->fieldClean($aggrschema); + $this->clean($aggrcomment); + $this->fieldClean($newaggrname); + $this->fieldClean($newaggrowner); + $this->fieldClean($newaggrschema); + $this->clean($newaggrcomment); + + $this->beginTransaction(); + + // Change the owner, if it has changed + if($aggrowner != $newaggrowner) { + $status = $this->changeAggregateOwner($aggrname, $aggrtype, $newaggrowner); + if($status != 0) { + $this->rollbackTransaction(); + return -1; + } + } + + // Set the comment, if it has changed + if($aggrcomment != $newaggrcomment) { + $status = $this->setComment('AGGREGATE', $aggrname, '', $newaggrcomment, $aggrtype); + if ($status) { + $this->rollbackTransaction(); + return -2; + } + } + + // Change the schema, if it has changed + if($aggrschema != $newaggrschema) { + $status = $this->changeAggregateSchema($aggrname, $aggrtype, $newaggrschema); + if($status != 0) { + $this->rollbackTransaction(); + return -3; + } + } + + // Rename the aggregate, if it has changed + if($aggrname != $newaggrname) { + $status = $this->renameAggregate($newaggrschema, $aggrname, $aggrtype, $newaggrname); + if($status != 0) { + $this->rollbackTransaction(); + return -4; + } + } + + return $this->endTransaction(); + } + + // Role, User/Group functions + + /** + * Returns all roles in the database cluster + * @param $rolename (optional) The role name to exclude from the select + * @return All roles + */ + function getRoles($rolename = '') { + $sql = ' + SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit, + rolcanlogin, rolconnlimit, rolvaliduntil, rolconfig + FROM pg_catalog.pg_roles'; + if($rolename) $sql .= " WHERE rolname!='{$rolename}'"; + $sql .= ' ORDER BY rolname'; + + return $this->selectSet($sql); + } + + /** + * Returns information about a single role + * @param $rolename The name of the role to retrieve + * @return The role's data + */ + function getRole($rolename) { + $this->clean($rolename); + + $sql = " + SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit, + rolcanlogin, rolconnlimit, rolvaliduntil, rolconfig + FROM pg_catalog.pg_roles WHERE rolname='{$rolename}'"; + + return $this->selectSet($sql); + } + + /** + * Grants membership in a role + * @param $role The name of the target role + * @param $rolename The name of the role that will belong to the target role + * @param $admin (optional) Flag to grant the admin option + * @return 0 success + */ + function grantRole($role, $rolename, $admin=0) { + $this->fieldClean($role); + $this->fieldClean($rolename); + + $sql = "GRANT \"{$role}\" TO \"{$rolename}\""; + if($admin == 1) $sql .= ' WITH ADMIN OPTION'; + + return $this->execute($sql); + } + + /** + * Revokes membership in a role + * @param $role The name of the target role + * @param $rolename The name of the role that will not belong to the target role + * @param $admin (optional) Flag to revoke only the admin option + * @param $type (optional) Type of revoke: RESTRICT | CASCADE + * @return 0 success + */ + function revokeRole($role, $rolename, $admin = 0, $type = 'RESTRICT') { + $this->fieldClean($role); + $this->fieldClean($rolename); + + $sql = "REVOKE "; + if($admin == 1) $sql .= 'ADMIN OPTION FOR '; + $sql .= "\"{$role}\" FROM \"{$rolename}\" {$type}"; + + return $this->execute($sql); + } + + /** + * Returns all users in the database cluster + * @return All users + */ + function getUsers() { + $sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires"; + if ($this->hasUserSessionDefaults()) $sql .= ", useconfig"; + $sql .= " FROM pg_user ORDER BY usename"; + + return $this->selectSet($sql); + } + + /** + * Returns information about a single user + * @param $username The username of the user to retrieve + * @return The user's data + */ + function getUser($username) { + $this->clean($username); + + $sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires"; + if ($this->hasUserSessionDefaults()) $sql .= ", useconfig"; + $sql .= " FROM pg_user WHERE usename='{$username}'"; + + return $this->selectSet($sql); + } + + /** + * Creates a new role + * @param $rolename The name of the role to create + * @param $password A password for the role + * @param $superuser Boolean whether or not the role is a superuser + * @param $createdb Boolean whether or not the role can create databases + * @param $createrole Boolean whether or not the role can create other roles + * @param $inherits Boolean whether or not the role inherits the privileges from parent roles + * @param $login Boolean whether or not the role will be allowed to login + * @param $connlimit Number of concurrent connections the role can make + * @param $expiry String Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire + * @param $memberof (array) Roles to which the new role will be immediately added as a new member + * @param $members (array) Roles which are automatically added as members of the new role + * @param $adminmembers (array) Roles which are automatically added as admin members of the new role + * @return 0 success + */ + function createRole($rolename, $password, $superuser, $createdb, $createrole, $inherits, $login, $connlimit, $expiry, $memberof, $members, $adminmembers) { + $enc = $this->_encryptPassword($rolename, $password); + $this->fieldClean($rolename); + $this->clean($enc); + $this->clean($connlimit); + $this->clean($expiry); + $this->fieldArrayClean($memberof); + $this->fieldArrayClean($members); + $this->fieldArrayClean($adminmembers); + + $sql = "CREATE ROLE \"{$rolename}\""; + if ($password != '') $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; + $sql .= ($superuser) ? ' SUPERUSER' : ' NOSUPERUSER'; + $sql .= ($createdb) ? ' CREATEDB' : ' NOCREATEDB'; + $sql .= ($createrole) ? ' CREATEROLE' : ' NOCREATEROLE'; + $sql .= ($inherits) ? ' INHERIT' : ' NOINHERIT'; + $sql .= ($login) ? ' LOGIN' : ' NOLOGIN'; + if ($connlimit != '') $sql .= " CONNECTION LIMIT {$connlimit}"; else $sql .= ' CONNECTION LIMIT -1'; + if ($expiry != '') $sql .= " VALID UNTIL '{$expiry}'"; else $sql .= " VALID UNTIL 'infinity'"; + if (is_array($memberof) && sizeof($memberof) > 0) $sql .= ' IN ROLE "' . join('", "', $memberof) . '"'; + if (is_array($members) && sizeof($members) > 0) $sql .= ' ROLE "' . join('", "', $members) . '"'; + if (is_array($adminmembers) && sizeof($adminmembers) > 0) $sql .= ' ADMIN "' . join('", "', $adminmembers) . '"'; + + return $this->execute($sql); + } + + /** + * Adjusts a role's info + * @param $rolename The name of the role to adjust + * @param $password A password for the role + * @param $superuser Boolean whether or not the role is a superuser + * @param $createdb Boolean whether or not the role can create databases + * @param $createrole Boolean whether or not the role can create other roles + * @param $inherits Boolean whether or not the role inherits the privileges from parent roles + * @param $login Boolean whether or not the role will be allowed to login + * @param $connlimit Number of concurrent connections the role can make + * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire + * @param $memberof (array) Roles to which the role will be immediately added as a new member + * @param $members (array) Roles which are automatically added as members of the role + * @param $adminmembers (array) Roles which are automatically added as admin members of the role + * @param $memberofold (array) Original roles whose the role belongs to + * @param $membersold (array) Original roles that are members of the role + * @param $adminmembersold (array) Original roles that are admin members of the role + * @return 0 success + */ + function setRole($rolename, $password, $superuser, $createdb, $createrole, $inherits, $login, $connlimit, $expiry, $memberof, $members, $adminmembers, $memberofold, $membersold, $adminmembersold) { + $enc = $this->_encryptPassword($rolename, $password); + $this->fieldClean($rolename); + $this->clean($enc); + $this->clean($connlimit); + $this->clean($expiry); + $this->fieldArrayClean($memberof); + $this->fieldArrayClean($members); + $this->fieldArrayClean($adminmembers); + + $sql = "ALTER ROLE \"{$rolename}\""; + if ($password != '') $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; + $sql .= ($superuser) ? ' SUPERUSER' : ' NOSUPERUSER'; + $sql .= ($createdb) ? ' CREATEDB' : ' NOCREATEDB'; + $sql .= ($createrole) ? ' CREATEROLE' : ' NOCREATEROLE'; + $sql .= ($inherits) ? ' INHERIT' : ' NOINHERIT'; + $sql .= ($login) ? ' LOGIN' : ' NOLOGIN'; + if ($connlimit != '') $sql .= " CONNECTION LIMIT {$connlimit}"; else $sql .= ' CONNECTION LIMIT -1'; + if ($expiry != '') $sql .= " VALID UNTIL '{$expiry}'"; else $sql .= " VALID UNTIL 'infinity'"; + + $status = $this->execute($sql); + + if ($status != 0) return -1; + + //memberof + $old = explode(',', $memberofold); + foreach ($memberof as $m) { + if (!in_array($m, $old)) { + $status = $this->grantRole($m, $rolename); + if ($status != 0) return -1; + } + } + if($memberofold) + { + foreach ($old as $o) { + if (!in_array($o, $memberof)) { + $status = $this->revokeRole($o, $rolename, 0, 'CASCADE'); + if ($status != 0) return -1; + } + } + } + + //members + $old = explode(',', $membersold); + foreach ($members as $m) { + if (!in_array($m, $old)) { + $status = $this->grantRole($rolename, $m); + if ($status != 0) return -1; + } + } + if($membersold) + { + foreach ($old as $o) { + if (!in_array($o, $members)) { + $status = $this->revokeRole($rolename, $o, 0, 'CASCADE'); + if ($status != 0) return -1; + } + } + } + + //adminmembers + $old = explode(',', $adminmembersold); + foreach ($adminmembers as $m) { + if (!in_array($m, $old)) { + $status = $this->grantRole($rolename, $m, 1); + if ($status != 0) return -1; + } + } + if($adminmembersold) + { + foreach ($old as $o) { + if (!in_array($o, $adminmembers)) { + $status = $this->revokeRole($rolename, $o, 1, 'CASCADE'); + if ($status != 0) return -1; + } + } + } + + return $status; + } + + /** + * Renames a role + * @param $rolename The name of the role to rename + * @param $newrolename The new name of the role + * @return 0 success + */ + function renameRole($rolename, $newrolename){ + $this->fieldClean($rolename); + $this->fieldClean($newrolename); + + $sql = "ALTER ROLE \"{$rolename}\" RENAME TO \"{$newrolename}\""; + + return $this->execute($sql); + } + + /** + * Adjusts a role's info and renames it + * @param $rolename The name of the role to adjust + * @param $password A password for the role + * @param $superuser Boolean whether or not the role is a superuser + * @param $createdb Boolean whether or not the role can create databases + * @param $createrole Boolean whether or not the role can create other roles + * @param $inherits Boolean whether or not the role inherits the privileges from parent roles + * @param $login Boolean whether or not the role will be allowed to login + * @param $connlimit Number of concurrent connections the role can make + * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire + * @param $memberof (array) Roles to which the role will be immediately added as a new member + * @param $members (array) Roles which are automatically added as members of the role + * @param $adminmembers (array) Roles which are automatically added as admin members of the role + * @param $memberofold (array) Original roles whose the role belongs to + * @param $membersold (array) Original roles that are members of the role + * @param $adminmembersold (array) Original roles that are admin members of the role + * @param $newrolename The new name of the role + * @return 0 success + * @return -1 transaction error + * @return -2 set role attributes error + * @return -3 rename error + */ + function setRenameRole($rolename, $password, $superuser, $createdb, $createrole, + $inherits, $login, $connlimit, $expiry, $memberof, $members, $adminmembers, + $memberofold, $membersold, $adminmembersold, $newrolename) { + + $status = $this->beginTransaction(); + if ($status != 0) return -1; + + $status = $this->setRole($rolename, $password, $superuser, $createdb, $createrole, $inherits, $login, $connlimit, $expiry, $memberof, $members, $adminmembers, $memberofold, $membersold, $adminmembersold); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; + } + + if ($rolename != $newrolename){ + $status = $this->renameRole($rolename, $newrolename); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } + } + + return $this->endTransaction(); + } + + /** + * Removes a role + * @param $rolename The name of the role to drop + * @return 0 success + */ + function dropRole($rolename) { + $this->fieldClean($rolename); + + $sql = "DROP ROLE \"{$rolename}\""; + + return $this->execute($sql); + } + + /** + * Creates a new user + * @param $username The username of the user to create + * @param $password A password for the user + * @param $createdb boolean Whether or not the user can create databases + * @param $createuser boolean Whether or not the user can create other users + * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire + * @param $group (array) The groups to create the user in + * @return 0 success + */ + function createUser($username, $password, $createdb, $createuser, $expiry, $groups) { + $enc = $this->_encryptPassword($username, $password); + $this->fieldClean($username); + $this->clean($enc); + $this->clean($expiry); + $this->fieldArrayClean($groups); + + $sql = "CREATE USER \"{$username}\""; + if ($password != '') $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; + $sql .= ($createdb) ? ' CREATEDB' : ' NOCREATEDB'; + $sql .= ($createuser) ? ' CREATEUSER' : ' NOCREATEUSER'; + if (is_array($groups) && sizeof($groups) > 0) $sql .= " IN GROUP \"" . join('", "', $groups) . "\""; + if ($expiry != '') $sql .= " VALID UNTIL '{$expiry}'"; + else $sql .= " VALID UNTIL 'infinity'"; + + return $this->execute($sql); + } + + /** + * Renames a user + * @param $username The username of the user to rename + * @param $newname The new name of the user + * @return 0 success + */ + function renameUser($username, $newname){ + $this->fieldClean($username); + $this->fieldClean($newname); + + $sql = "ALTER USER \"{$username}\" RENAME TO \"{$newname}\""; + + return $this->execute($sql); + } + + /** + * Adjusts a user's info + * @param $username The username of the user to modify + * @param $password A new password for the user + * @param $createdb boolean Whether or not the user can create databases + * @param $createuser boolean Whether or not the user can create other users + * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire. + * @return 0 success + */ + function setUser($username, $password, $createdb, $createuser, $expiry) { + $enc = $this->_encryptPassword($username, $password); + $this->fieldClean($username); + $this->clean($enc); + $this->clean($expiry); + + $sql = "ALTER USER \"{$username}\""; + if ($password != '') $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; + $sql .= ($createdb) ? ' CREATEDB' : ' NOCREATEDB'; + $sql .= ($createuser) ? ' CREATEUSER' : ' NOCREATEUSER'; + if ($expiry != '') $sql .= " VALID UNTIL '{$expiry}'"; + else $sql .= " VALID UNTIL 'infinity'"; + + return $this->execute($sql); + } + + /** + * Adjusts a user's info and renames the user + * @param $username The username of the user to modify + * @param $password A new password for the user + * @param $createdb boolean Whether or not the user can create databases + * @param $createuser boolean Whether or not the user can create other users + * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire. + * @param $newname The new name of the user + * @return 0 success + * @return -1 transaction error + * @return -2 set user attributes error + * @return -3 rename error + */ + function setRenameUser($username, $password, $createdb, $createuser, $expiry, $newname) { + $status = $this->beginTransaction(); + if ($status != 0) return -1; + + $status = $this->setUser($username, $password, $createdb, $createuser, $expiry); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; + } + + if ($username != $newname){ + $status = $this->renameUser($username, $newname); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } + } + + return $this->endTransaction(); + } + + /** + * Removes a user + * @param $username The username of the user to drop + * @return 0 success + */ + function dropUser($username) { + $this->fieldClean($username); + + $sql = "DROP USER \"{$username}\""; + + return $this->execute($sql); + } + + /** + * Determines whether or not a user is a super user + * @param $username The username of the user + * @return True if is a super user, false otherwise + */ + function isSuperUser($username) { + $this->clean($username); + + if (function_exists('pg_parameter_status')) { + $val = pg_parameter_status($this->conn->_connectionID, 'is_superuser'); + if ($val !== false) return $val == 'on'; + } + + $sql = "SELECT usesuper FROM pg_user WHERE usename='{$username}'"; + + $usesuper = $this->selectField($sql, 'usesuper'); + if ($usesuper == -1) return false; + else return $usesuper == 't'; + } + + /** + * Changes a role's password + * @param $rolename The role name + * @param $password The new password + * @return 0 success + */ + function changePassword($rolename, $password) { + $enc = $this->_encryptPassword($rolename, $password); + $this->fieldClean($rolename); + $this->clean($enc); + + $sql = "ALTER ROLE \"{$rolename}\" WITH ENCRYPTED PASSWORD '{$enc}'"; + + return $this->execute($sql); + } + + /** + * Adds a group member + * @param $groname The name of the group + * @param $user The name of the user to add to the group + * @return 0 success + */ + function addGroupMember($groname, $user) { + $this->fieldClean($groname); + $this->fieldClean($user); + + $sql = "ALTER GROUP \"{$groname}\" ADD USER \"{$user}\""; + + return $this->execute($sql); + } + + /** + * Returns all role names which the role belongs to + * @param $rolename The role name + * @return All role names + */ + function getMemberOf($rolename) { + $this->clean($rolename); + + $sql = " + SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M + WHERE R.oid=M.roleid + AND member IN ( + SELECT oid FROM pg_catalog.pg_roles + WHERE rolname='{$rolename}') + ORDER BY rolname"; + + return $this->selectSet($sql); + } + + /** + * Returns all role names that are members of a role + * @param $rolename The role name + * @param $admin (optional) Find only admin members + * @return All role names + */ + function getMembers($rolename, $admin = 'f') { + $this->clean($rolename); + + $sql = " + SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M + WHERE R.oid=M.member AND admin_option='{$admin}' + AND roleid IN (SELECT oid FROM pg_catalog.pg_roles + WHERE rolname='{$rolename}') + ORDER BY rolname"; + + return $this->selectSet($sql); + } + + /** + * Removes a group member + * @param $groname The name of the group + * @param $user The name of the user to remove from the group + * @return 0 success + */ + function dropGroupMember($groname, $user) { + $this->fieldClean($groname); + $this->fieldClean($user); + + $sql = "ALTER GROUP \"{$groname}\" DROP USER \"{$user}\""; + + return $this->execute($sql); + } + + /** + * Return users in a specific group + * @param $groname The name of the group + * @return All users in the group + */ + function getGroup($groname) { + $this->clean($groname); + + $sql = " + SELECT s.usename FROM pg_catalog.pg_user s, pg_catalog.pg_group g + WHERE g.groname='{$groname}' AND s.usesysid = ANY (g.grolist) + ORDER BY s.usename"; + + return $this->selectSet($sql); + } + + /** + * Returns all groups in the database cluser + * @return All groups + */ + function getGroups() { + $sql = "SELECT groname FROM pg_group ORDER BY groname"; + + return $this->selectSet($sql); + } + + /** + * Creates a new group + * @param $groname The name of the group + * @param $users An array of users to add to the group + * @return 0 success + */ + function createGroup($groname, $users) { + $this->fieldClean($groname); + + $sql = "CREATE GROUP \"{$groname}\""; + + if (is_array($users) && sizeof($users) > 0) { + $this->fieldArrayClean($users); + $sql .= ' WITH USER "' . join('", "', $users) . '"'; + } + + return $this->execute($sql); + } + + /** + * Removes a group + * @param $groname The name of the group to drop + * @return 0 success + */ + function dropGroup($groname) { + $this->fieldClean($groname); + + $sql = "DROP GROUP \"{$groname}\""; + + return $this->execute($sql); + } /** * Internal function used for parsing ACLs @@ -3445,7 +6271,7 @@ class Postgres extends ADODB_base { * Grabs an array of users and their privileges for an object, * given its type. * @param $object The name of the object whose privileges are to be retrieved - * @param $type The type of the object (eg. relation, view or sequence) + * @param $type The type of the object (eg. database, schema, relation, function or language) * @return Privileges array * @return -1 invalid type * @return -2 object not found @@ -3458,7 +6284,28 @@ class Postgres extends ADODB_base { case 'table': case 'view': case 'sequence': - $sql = "SELECT relacl AS acl FROM pg_class WHERE relname='{$object}'"; + $sql = " + SELECT relacl AS acl FROM pg_catalog.pg_class + WHERE relname='{$object}' + AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace + WHERE nspname='{$this->_schema}')"; + break; + case 'database': + $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'"; + break; + case 'function': + // Since we fetch functions by oid, they are already constrained to + // the current schema. + $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'"; + break; + case 'language': + $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'"; + break; + case 'schema': + $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'"; + break; + case 'tablespace': + $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'"; break; default: return -1; @@ -3585,519 +6432,270 @@ class Postgres extends ADODB_base { return $this->execute($sql); } - // Administration functions - /** - * Vacuums a database - * @param $table The table to vacuum - * @param $analyze If true, also does analyze - * @param $full If true, selects "full" vacuum (PostgreSQL >= 7.2) - * @param $freeze If true, selects aggressive "freezing" of tuples (PostgreSQL >= 7.2) + * Helper function that computes encypted PostgreSQL passwords + * @param $username The username + * @param $password The password */ - function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false) { - $sql = "VACUUM"; - if ($analyze) $sql .= " ANALYZE"; - if ($table != '') { - $this->fieldClean($table); - $sql .= " \"{$table}\""; + function _encryptPassword($username, $password) { + return 'md5' . md5($password . $username); } - return $this->execute($sql); - } + // Tablespace functions /** - * Analyze a database - * @param $table (optional) The table to analyze + * Retrieves information for all tablespaces + * @param $all Include all tablespaces (necessary when moving objects back to the default space) + * @return A recordset */ - function analyzeDB($table = '') { - if ($table != '') { - $this->fieldClean($table); - $sql = "VACUUM ANALYZE \"{$table}\""; - } - else - $sql = "VACUUM ANALYZE"; + function getTablespaces($all = false) { + global $conf; - return $this->execute($sql); - } + $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation, + (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid) AS spccomment + FROM pg_catalog.pg_tablespace"; - /** - * Rebuild indexes - * @param $type 'DATABASE' or 'TABLE' or 'INDEX' - * @param $name The name of the specific database, table, or index to be reindexed - * @param $force If true, recreates indexes forcedly in PostgreSQL 7.0-7.1, forces rebuild of system indexes in 7.2-7.3, ignored in >=7.4 - */ - function reindex($type, $name, $force = false) { - $this->fieldClean($name); - switch($type) { - case 'DATABASE': - case 'TABLE': - case 'INDEX': - $sql = "REINDEX {$type} \"{$name}\""; - if ($force) $sql .= ' FORCE'; - break; - default: - return -1; + if (!$conf['show_system'] && !$all) { + $sql .= " WHERE spcname NOT LIKE 'pg\\\\_%'"; } - return $this->execute($sql); - } + $sql .= " ORDER BY spcname"; - // Function functions + return $this->selectSet($sql); + } /** - * Returns a list of all functions in the database - * @param $all If true, will find all available functions, if false just userland ones - * @return All functions + * Retrieves a tablespace's information + * @return A recordset */ - function getFunctions($all = false) { - global $conf; + function getTablespace($spcname) { + $this->clean($spcname); - if ($all || $conf['show_system']) - $where = ''; - else - $where = "AND pc.oid > '{$this->_lastSystemOID}'::oid"; - - $sql = "SELECT - pc.oid AS prooid, - proname, - proretset, - pt.typname AS proresult, - pl.lanname AS prolanguage, - oidvectortypes(pc.proargtypes) AS proarguments, - (SELECT description FROM pg_description pd WHERE pc.oid=pd.objoid) AS procomment, - proname || ' (' || oidvectortypes(pc.proargtypes) || ')' AS proproto, - CASE WHEN proretset THEN 'setof '::text ELSE '' END || pt.typname AS proreturns, - usename as proowner - FROM - pg_proc pc, pg_user pu, pg_type pt, pg_language pl - WHERE - pc.proowner = pu.usesysid - AND pc.prorettype = pt.oid - AND pc.prolang = pl.oid - {$where} - UNION - SELECT - pc.oid AS prooid, - proname, - proretset, - 'opaque' AS proresult, - pl.lanname AS prolanguage, - oidvectortypes(pc.proargtypes) AS proarguments, - (SELECT description FROM pg_description pd WHERE pc.oid=pd.objoid) AS procomment, - proname || ' (' || oidvectortypes(pc.proargtypes) || ')' AS proproto, - CASE WHEN proretset THEN 'setof '::text ELSE '' END || 'opaque' AS proreturns, - usename as proowner - FROM - pg_proc pc, pg_user pu, pg_type pt, pg_language pl - WHERE - pc.proowner = pu.usesysid - AND pc.prorettype = 0 - AND pc.prolang = pl.oid - {$where} - ORDER BY - proname, proresult - "; + $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation, + (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid) AS spccomment + FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'"; return $this->selectSet($sql); } /** - * Returns a list of all functions that can be used in triggers - */ - function getTriggerFunctions() { - return $this->getFunctions(true); - } - - /** - * Returns all details for a particular function - * @param $function_oid The OID of the function to retrieve - * @return Function info + * Creates a tablespace + * @param $spcname The name of the tablespace to create + * @param $spcowner The owner of the tablespace. '' for current + * @param $spcloc The directory in which to create the tablespace + * @return 0 success */ - function getFunction($function_oid) { - $this->clean($function_oid); + function createTablespace($spcname, $spcowner, $spcloc, $comment='') { + $this->fieldClean($spcname); + $this->clean($spcloc); + $this->clean($comment); - $sql = "SELECT - pc.oid AS prooid, - proname, - lanname AS prolanguage, - pt.typname AS proresult, - prosrc, - probin, - proretset, - proiscachable, - oidvectortypes(pc.proargtypes) AS proarguments, - (SELECT description FROM pg_description pd WHERE pc.oid=pd.objoid) AS procomment - FROM - pg_proc pc, pg_language pl, pg_type pt - WHERE - pc.oid = '$function_oid'::oid - AND pc.prolang = pl.oid - AND pc.prorettype = pt.oid - "; + $sql = "CREATE TABLESPACE \"{$spcname}\""; - return $this->selectSet($sql); + if ($spcowner != '') { + $this->fieldClean($spcowner); + $sql .= " OWNER \"{$spcowner}\""; } - /** - * Returns an array containing a function's properties - * @param $f The array of data for the function - * @return An array containing the properties - */ - function getFunctionProperties($f) { - $temp = array(); + $sql .= " LOCATION '{$spcloc}'"; - // Cachable - $f['proiscachable'] = $this->phpBool($f['proiscachable']); - if ($f['proiscachable']) - $temp[] = 'ISCACHABLE'; - else - $temp[] = ''; + $status = $this->execute($sql); + if ($status != 0) return -1; - return $temp; + if ($comment != '' && $this->hasSharedComments()) { + $status = $this->setComment('TABLESPACE',$spcname,'',$comment); + if ($status != 0) return -2; + } + + return 0; } /** - * Updates a function. Postgres 7.1 doesn't have CREATE OR REPLACE function, - * so we do it with a drop and a recreate. - * @param $function_oid The OID of the function - * @param $funcname The name of the function to create - * @param $newname The new name for the function - * @param $args The array of argument types - * @param $returns The return type - * @param $definition The definition for the new function - * @param $language The language the function is written for - * @param $flags An array of optional flags - * @param $setof True if returns a set, false otherwise - * @param $comment The comment on the function + * Alters a tablespace + * @param $spcname The name of the tablespace + * @param $name The new name for the tablespace + * @param $owner The new owner for the tablespace * @return 0 success * @return -1 transaction error - * @return -2 drop function error - * @return -3 create function error + * @return -2 owner error + * @return -3 rename error * @return -4 comment error */ - function setFunction($function_oid, $funcname, $newname, $args, $returns, $definition, $language, $flags, $setof, $comment) { + function alterTablespace($spcname, $name, $owner, $comment='') { + $this->fieldClean($spcname); + $this->fieldClean($name); + $this->fieldClean($owner); + + // Begin transaction $status = $this->beginTransaction(); if ($status != 0) return -1; - // Drop existing function - $status = $this->dropFunction($function_oid, false); + // Owner + $sql = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\""; + $status = $this->execute($sql); if ($status != 0) { $this->rollbackTransaction(); return -2; } - // Create function with new name - $status = $this->createFunction($newname, $args, $returns, $definition, $language, $flags, $setof, false); + // Rename (only if name has changed) + if ($name != $spcname) { + $sql = "ALTER TABLESPACE \"{$spcname}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); if ($status != 0) { $this->rollbackTransaction(); return -3; } + } - // Comment on the function - $this->fieldClean($newname); - $this->clean($comment); - - $status = $this->setComment('FUNCTION', "\"{$newname}\"({$args})", null, $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; + // Set comment if it has changed + if (trim($comment) != '' && $this->hasSharedComments()) { + $status = $this->setComment('TABLESPACE',$spcname,'',$comment); + if ($status != 0) return -4; } - $status = $this->endTransaction(); - return ($status == 0) ? 0 : -1; + return $this->endTransaction(); } /** - * Creates a new function. - * @param $funcname The name of the function to create - * @param $args A comma separated string of types - * @param $returns The return type - * @param $definition The definition for the new function - * @param $language The language the function is written for - * @param $flags An array of optional flags - * @param $setof True if it returns a set, false otherwise - * @param $rows number of rows planner should estimate will be returned - * @param $cost cost the planner should use in the function execution step - * @param $replace (optional) True if OR REPLACE, false for normal + * Drops a tablespace + * @param $spcname The name of the domain to drop * @return 0 success */ - function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $replace = false) { - $this->fieldClean($funcname); - $this->clean($args); - $this->clean($language); - $this->arrayClean($flags); - - $sql = "CREATE"; - if ($replace) $sql .= " OR REPLACE"; - $sql .= " FUNCTION \"{$funcname}\" ("; - - if ($args != '') - $sql .= $args; - - // For some reason, the returns field cannot have quotes... - $sql .= ") RETURNS "; - if ($setof) $sql .= "SETOF "; - $sql .= "{$returns} AS "; - - if (is_array($definition)) { - $this->arrayClean($definition); - $sql .= "'" . $definition[0] . "'"; - if ($definition[1]) { - $sql .= ",'" . $definition[1] . "'"; - } - } else { - $this->clean($definition); - $sql .= "'" . $definition . "'"; - } + function dropTablespace($spcname) { + $this->fieldClean($spcname); - $sql .= " LANGUAGE '{$language}'"; + $sql = "DROP TABLESPACE \"{$spcname}\""; - // Add flags - $first = true; - foreach ($flags as $v) { - // Skip default flags - if ($v == '') continue; - elseif ($first) { - $sql .= " WITH ({$v}"; - $first = false; - } - else { - $sql .= ", {$v}"; - } + return $this->execute($sql); } - // Close off WITH clause if necessary - if (!$first) $sql .= ")"; - return $this->execute($sql); - } + // Administration functions /** - * Drops a function. - * @param $function_oid The OID of the function to drop - * @param $cascade True to cascade drop, false to restrict - * @return 0 success + * Analyze a database + * @note PostgreSQL 7.2 finally had an independent ANALYZE command + * @param $table (optional) The table to analyze */ - function dropFunction($function_oid, $cascade) { - // Function comes in with $object as function OID - $fn = $this->getFunction($function_oid); - $this->fieldClean($fn->fields['proname']); + function analyzeDB($table = '') { + if ($table != '') { + $this->fieldClean($table); - $sql = "DROP FUNCTION \"{$fn->fields['proname']}\"({$fn->fields['proarguments']})"; - if ($cascade) $sql .= " CASCADE"; + $sql = "ANALYZE \"{$this->_schema}\".\"{$table}\""; + } + else + $sql = "ANALYZE"; return $this->execute($sql); } - // Language functions - /** - * Gets all languages - * @param $all True to get all languages, regardless of show_system - * @return A recordset + * Vacuums a database + * @param $table The table to vacuum + * @param $analyze If true, also does analyze + * @param $full If true, selects "full" vacuum (PostgreSQL >= 7.2) + * @param $freeze If true, selects aggressive "freezing" of tuples (PostgreSQL >= 7.2) */ - function getLanguages($all = false) { - global $conf; - - if ($conf['show_system'] || $all) - $where = ''; - else - $where = 'WHERE lanispl'; + function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false) { - $sql = " - SELECT - lanname, - lanpltrusted, - lanplcallfoid::regproc AS lanplcallf - FROM - pg_language - {$where} - ORDER BY - lanname - "; + $sql = "VACUUM"; + if ($full) $sql .= " FULL"; + if ($freeze) $sql .= " FREEZE"; + if ($analyze) $sql .= " ANALYZE"; + if ($table != '') { + $this->fieldClean($table); + $sql .= " \"{$this->_schema}\".\"{$table}\""; + } - return $this->selectSet($sql); + return $this->execute($sql); } - // Aggregate functions - /** - * Gets all aggregates + * Returns all available process information. * @return A recordset */ - function getAggregates() { - global $conf; - - if ($conf['show_system']) - $where = ''; - else - $where = "WHERE a.oid > '{$this->_lastSystemOID}'::oid"; - + function getAutovacuum() { $sql = " - SELECT - a.aggname AS proname, - CASE a.aggbasetype - WHEN 0 THEN NULL - ELSE (SELECT typname FROM pg_type t WHERE t.oid=a.aggbasetype) - END AS proargtypes, - (SELECT description FROM pg_description pd WHERE a.oid=pd.objoid) AS aggcomment - FROM - pg_aggregate a - {$where} - ORDER BY - 1, 2; - "; + SELECT vacrelid, nspname, relname, enabled, vac_base_thresh, + vac_scale_factor, anl_base_thresh, anl_scale_factor, vac_cost_delay, vac_cost_limit + FROM pg_autovacuum + join pg_class on (oid=vacrelid) + join pg_namespace on (oid=relnamespace) + ORDER BY nspname, relname"; return $this->selectSet($sql); } /** - * Gets all information for an aggregate - * @param $name The name of the aggregate - * @param $basetype The input data type of the aggregate + * Returns all available process information. + * @param $database (optional) Find only connections to specified database * @return A recordset */ - function getAggregate($name, $basetype) { - $this->fieldclean($name); - $this->fieldclean($basetype); - + function getProcesses($database = null) { + if ($database === null) + $sql = "SELECT * FROM pg_catalog.pg_stat_activity ORDER BY datname, usename, procpid"; + else { + $this->clean($database); $sql = " - SELECT a.aggname AS proname, - CASE a.aggbasetype - WHEN 0 THEN NULL - ELSE format_type(a.aggbasetype, NULL) - END AS proargtypes, - a.aggtransfn, format_type(a.aggtranstype, NULL) AS aggstype, a.aggfinalfn, a.agginitval, u.usename, - obj_description(a.oid, 'pg_aggregate') AS aggrcomment - FROM pg_user u, pg_aggregate a - WHERE a.aggowner=u.usesysid - AND a.aggname='" . $name . "' - AND CASE a.aggbasetype - WHEN 0 THEN '' - ELSE format_type(a.aggbasetype, NULL) - END ='" . $basetype . "'"; - - return $this->selectSet($sql); - } - - /** - * Creates a new aggregate in the database - * @param $name The name of the aggregate - * @param $basetype The input data type of the aggregate - * @param $sfunc The name of the state transition function for the aggregate - * @param $stype The data type for the aggregate's state value - * @param $ffunc The name of the final function for the aggregate - * @param $initcond The initial setting for the state value - * @param $sortop The sort operator for the aggregate - * @param $comment Aggregate comment - * @return 0 success - * @return -1 error - */ - function createAggregate($name, $basetype, $sfunc, $stype, $ffunc, $initcond, $sortop, $comment) { - $this->fieldClean($name); - $this->fieldClean($basetype); - $this->fieldClean($sfunc); - $this->fieldClean($stype); - $this->fieldClean($ffunc); - $this->fieldClean($initcond); - $this->fieldClean($sortop); - $this->clean($comment); - - $this->beginTransaction(); - - $schema = $this->schema(); - $sql = "CREATE AGGREGATE {$schema}\"{$name}\" (BASETYPE = \"{$basetype}\", SFUNC = \"{$sfunc}\", STYPE = \"{$stype}\""; - if(trim($ffunc) != '') $sql .= ", FINALFUNC = \"{$ffunc}\""; - if(trim($initcond) != '') $sql .= ", INITCOND = \"{$initcond}\""; - if(trim($sortop) != '') $sql .= ", SORTOP = \"{$sortop}\""; - $sql .= ")"; - - $status = $this->execute($sql); - if ($status) { - $this->rollbackTransaction(); - return -1; - } - - if (trim($comment) != '') { - $status = $this->setComment('AGGREGATE', $name, '', $comment, $basetype); - if ($status) { - $this->rollbackTransaction(); - return -1; - } + SELECT * FROM pg_catalog.pg_stat_activity + WHERE datname='{$database}' ORDER BY usename, procpid"; } - return $this->endTransaction(); - } - - /** - * Removes an aggregate function from the database - * @param $aggrname The name of the aggregate - * @param $aggrtype The input data type of the aggregate - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropAggregate($aggrname, $aggrtype, $cascade) { - $this->fieldClean($aggrname); - $this->fieldClean($aggrtype); - - $sql = "DROP AGGREGATE \"{$aggrname}\" (\"{$aggrtype}\")"; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); + return $this->selectSet($sql); } - // Operator Class functions - /** - * Gets all opclasses + * Returns table locks information in the current database * @return A recordset */ - function getOpClasses() { + + function getLocks() { global $conf; - if ($conf['show_system']) - $where = ''; + if (!$conf['show_system']) + $where = "AND pn.nspname NOT LIKE 'pg\\\\_%'"; else - $where = "AND po.oid > '{$this->_lastSystemOID}'::oid"; + $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; $sql = " - SELECT DISTINCT - pa.amname, - po.opcname, - (SELECT typname FROM pg_type t WHERE t.oid=opcdeftype) AS opcintype, - TRUE AS opcdefault, - NULL::text AS opccomment + SELECT + pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction, + (select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid' + and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction FROM - pg_opclass po, pg_am pa, pg_amop pam + pg_catalog.pg_locks pl, + pg_catalog.pg_class pc, + pg_catalog.pg_namespace pn WHERE - pam.amopid=pa.oid - AND pam.amopclaid=po.oid - {$where} - ORDER BY 1,2 - "; + pl.relation = pc.oid AND pc.relnamespace=pn.oid + {$where} + ORDER BY pid,nspname,tablename"; return $this->selectSet($sql); } - // Type conversion routines - /** - * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false - * @param $parameter the parameter + * Sends a cancel or kill command to a process + * @param $pid The ID of the backend process + * @param $signal 'CANCEL' + * @return 0 success + * @return -1 invalid signal type */ - function dbBool(&$parameter) { - if ($parameter) $parameter = 't'; - else $parameter = 'f'; + function sendSignal($pid, $signal) { + // Clean + $pid = (int)$pid; - return $parameter; - } + if ($signal == 'CANCEL') + $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val"; + else + return -1; - /** - * Change a parameter from 't' or 'f' to a boolean, (others evaluate to false) - * @param $parameter the parameter - */ - function phpBool($parameter) { - $parameter = ($parameter == 't'); - return $parameter; + // Execute the query + $val = $this->selectField($sql, 'val'); + + if ($val === -1) return -1; + elseif ($val == '1') return 0; + else return -1; } // Misc functions @@ -4145,7 +6743,7 @@ class Postgres extends ADODB_base { break; default: // Unknown object type - return -1; + return -1; } if ($comment != '') @@ -4158,446 +6756,16 @@ class Postgres extends ADODB_base { } /** - * Returns the SQL for changing the current user - * @param $user The user to change to - * @return The SQL - */ - function getChangeUserSQL($user) { - $this->fieldClean($user); - return "\\connect - \"{$user}\""; - } - - /** - * Sets up the data object for a dump. eg. Starts the appropriate - * transaction, sets variables, etc. - * @return 0 success - */ - function beginDump() { - // Begin serializable transaction (to dump consistent data) - $status = $this->beginTransaction(); - if ($status != 0) return -1; - - // Set serializable - $sql = "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Set datestyle to ISO - $sql = "SET DATESTYLE = ISO"; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - } - - /** - * Ends the data object for a dump. + * Sets the client encoding + * @param $encoding The encoding to for the client * @return 0 success */ - function endDump() { - return $this->endTransaction(); - } - - /** - * Generates the SQL for the 'select' function - * @param $table The table from which to select - * @param $show An array of columns to show. Empty array means all columns. - * @param $values An array mapping columns to values - * @param $ops An array of the operators to use - * @param $orderby (optional) An array of column numbers or names (one based) - * mapped to sort direction (asc or desc or '' or null) to order by - * @return The SQL query - */ - function getSelectSQL($table, $show, $values, $ops, $orderby = array()) { - $this->fieldClean($table); - $this->fieldArrayClean($show); - - // If an empty array is passed in, then show all columns - if (sizeof($show) == 0) { - if ($this->hasObjectID($table)) - $sql = "SELECT \"{$this->id}\", * FROM "; - else - $sql = "SELECT * FROM "; - } - else { - // Add oid column automatically to results for editing purposes - if (!in_array($this->id, $show) && $this->hasObjectID($table)) - $sql = "SELECT \"{$this->id}\", \""; - else - $sql = "SELECT \""; - - $sql .= join('","', $show) . "\" FROM "; - } - - if ($this->hasSchemas() && isset($_REQUEST['schema'])) { - $this->fieldClean($_REQUEST['schema']); - $sql .= "\"{$_REQUEST['schema']}\"."; - } - $sql .= "\"{$table}\""; - - // If we have values specified, add them to the WHERE clause - $first = true; - if (is_array($values) && sizeof($values) > 0) { - foreach ($values as $k => $v) { - if ($v != '' || $this->selectOps[$ops[$k]] == 'p') { - $this->fieldClean($k); - if ($first) { - $sql .= " WHERE "; - $first = false; - } else { - $sql .= " AND "; - } - // Different query format depending on operator type - switch ($this->selectOps[$ops[$k]]) { - case 'i': - // Only clean the field for the inline case - // this is because (x), subqueries need to - // to allow 'a','b' as input. - $this->clean($v); - $sql .= "\"{$k}\" {$ops[$k]} '{$v}'"; - break; - case 'p': - $sql .= "\"{$k}\" {$ops[$k]}"; - break; - case 'x': - $sql .= "\"{$k}\" {$ops[$k]} ({$v})"; - break; - case 't': - $sql .= "\"{$k}\" {$ops[$k]}('{$v}')"; - break; - default: - // Shouldn't happen - } - } - } - } - - // ORDER BY - if (is_array($orderby) && sizeof($orderby) > 0) { - $sql .= " ORDER BY "; - $first = true; - foreach ($orderby as $k => $v) { - if ($first) $first = false; - else $sql .= ', '; - if (ereg('^[0-9]+$', $k)) { - $sql .= $k; - } - else { - $this->fieldClean($k); - $sql .= '"' . $k . '"'; - } - if (strtoupper($v) == 'DESC') $sql .= " DESC"; - } - } - - return $sql; - } - - /** - * Finds the number of rows that would be returned by a - * query. - * @param $query The SQL query - * @param $count The count query - * @return The count of rows - * @return -1 error - */ - function browseQueryCount($query, $count) { - // Count the number of rows - $rs = $this->selectSet($query); - if (!is_object($rs)) { - return -1; - } - - return $rs->recordCount(); - } - - /** - * Returns a recordset of all columns in a query. Supports paging. - * @param $type Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier, - * or 'SELECT" if it's a select query - * @param $table The base table of the query. NULL for no table. - * @param $query The query that is being executed. NULL for no query. - * @param $sortkey The column number to sort by, or '' or null for no sorting - * @param $sortdir The direction in which to sort the specified column ('asc' or 'desc') - * @param $page The page of the relation to retrieve - * @param $page_size The number of rows per page - * @param &$max_pages (return-by-ref) The max number of pages in the relation - * @return A recordset on success - * @return -1 transaction error - * @return -2 counting error - * @return -3 page or page_size invalid - * @return -4 unknown type - * @return -5 failed setting transaction read only - */ - function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages) { - // Check that we're not going to divide by zero - if (!is_numeric($page_size) || $page_size != (int)$page_size || $page_size <= 0) return -3; - - // If $type is TABLE, then generate the query - switch ($type) { - case 'TABLE': - if (ereg('^[0-9]+$', $sortkey) && $sortkey > 0) $orderby = array($sortkey => $sortdir); - else $orderby = array(); - $query = $this->getSelectSQL($table, array(), array(), array(), $orderby); - break; - case 'QUERY': - case 'SELECT': - // Trim query - $query = trim($query); - // Trim off trailing semi-colon if there is one - if (substr($query, strlen($query) - 1, 1) == ';') - $query = substr($query, 0, strlen($query) - 1); - break; - default: - return -4; - } - - // Generate count query - $count = "SELECT COUNT(*) AS total FROM ($query) AS sub"; - - // Open a transaction - $status = $this->beginTransaction(); - if ($status != 0) return -1; - - // If backend supports read only queries, then specify read only mode - // to avoid side effects from repeating queries that do writes. - if ($this->hasReadOnlyQueries()) { - $status = $this->execute("SET TRANSACTION READ ONLY"); - if ($status != 0) { - $this->rollbackTransaction(); - return -5; - } - } - - - // Count the number of rows - $total = $this->browseQueryCount($query, $count); - if ($total < 0) { - $this->rollbackTransaction(); - return -2; - } - - // Calculate max pages - $max_pages = ceil($total / $page_size); - - // Check that page is less than or equal to max pages - if (!is_numeric($page) || $page != (int)$page || $page > $max_pages || $page < 1) { - $this->rollbackTransaction(); - return -3; - } - - // Set fetch mode to NUM so that duplicate field names are properly returned - // for non-table queries. Since the SELECT feature only allows selecting one - // table, duplicate fields shouldn't appear. - if ($type == 'QUERY') $this->conn->setFetchMode(ADODB_FETCH_NUM); - - // Figure out ORDER BY. Sort key is always the column number (based from one) - // of the column to order by. Only need to do this for non-TABLE queries - if ($type != 'TABLE' && ereg('^[0-9]+$', $sortkey) && $sortkey > 0) { - $orderby = " ORDER BY {$sortkey}"; - // Add sort order - if ($sortdir == 'desc') - $orderby .= ' DESC'; - else - $orderby .= ' ASC'; - } - else $orderby = ''; - - // Actually retrieve the rows, with offset and limit - if ($this->hasFullSubqueries()) - $rs = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size); - else - $rs = $this->selectSet("{$query} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size); - $status = $this->endTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - return $rs; - } - - /** - * Returns a recordset of all columns in a relation. Used for data export. - * @@ Note: Really needs to use a cursor - * @param $relation The name of a relation - * @return A recordset on success - * @return -1 Failed to set datestyle - */ - function dumpRelation($relation, $oids) { - $this->fieldClean($relation); - - // Actually retrieve the rows - if ($oids) $oid_str = $this->id . ', '; - else $oid_str = ''; - - return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\""); - } - - /** - * Searches all system catalogs to find objects that match a certain name. - * @param $term The search term - * @param $filter The object type to restrict to ('' means no restriction) - * @return A recordset - */ - function findObject($term, $filter) { - global $conf; - - // Escape search term for ~* match - $special = array('.', '*', '^', '$', ':', '?', '+', ',', '=', '!', '[', ']', '(', ')', '{', '}', '<', '>', '-', '\\'); - foreach ($special as $v) { - $term = str_replace($v, "\\{$v}", $term); - } - $this->clean($term); - $this->clean($filter); - - // Build SQL, excluding system relations as necessary - // Relations - $case_clause = "CASE WHEN relkind='r' THEN (CASE WHEN EXISTS (SELECT 1 FROM pg_rewrite r WHERE r.ev_class = pc.oid AND r.ev_type = '1') THEN 'VIEW'::VARCHAR ELSE 'TABLE'::VARCHAR END) WHEN relkind='v' THEN 'VIEW'::VARCHAR WHEN relkind='S' THEN 'SEQUENCE'::VARCHAR END"; - $sql = " - SELECT {$case_clause} AS type, - pc.oid, NULL::VARCHAR AS schemaname, NULL::VARCHAR AS relname, pc.relname AS name FROM pg_class pc - WHERE relkind IN ('r', 'v', 'S') AND relname ~* '.*{$term}.*'"; - if (!$conf['show_system']) $sql .= " AND pc.relname NOT LIKE 'pg@_%' ESCAPE '@'"; - if ($filter == 'TABLE' || $filter == 'VIEW' || $filter == 'SEQUENCE') $sql .= " AND {$case_clause} = '{$filter}'"; - elseif ($filter != '') $sql .= " AND FALSE"; - - // Columns - $sql .= " - UNION ALL - SELECT CASE WHEN relkind='r' THEN (CASE WHEN EXISTS (SELECT 1 FROM pg_rewrite r WHERE r.ev_class = pc.oid AND r.ev_type = '1') THEN 'COLUMNVIEW'::VARCHAR ELSE 'COLUMNTABLE'::VARCHAR END) WHEN relkind='v' THEN 'COLUMNVIEW'::VARCHAR END, - NULL, NULL, pc.relname, pa.attname FROM pg_class pc, - pg_attribute pa WHERE pc.oid=pa.attrelid - AND pa.attname ~* '.*{$term}.*' AND pa.attnum > 0 AND pc.relkind IN ('r', 'v')"; - if (!$conf['show_system']) $sql .= " AND pc.relname NOT LIKE 'pg@_%' ESCAPE '@'"; - if ($filter != '' && $filter != 'COLUMNTABLE' || $filter != 'COLUMNVIEW') $sql .= " AND FALSE"; - - // Functions - $sql .= " - UNION ALL - SELECT 'FUNCTION', pp.oid, NULL, NULL, pp.proname || '(' || oidvectortypes(pp.proargtypes) || ')' FROM pg_proc pp - WHERE proname ~* '.*{$term}.*'"; - if (!$conf['show_system']) $sql .= " AND pp.oid > '{$this->_lastSystemOID}'::oid"; - if ($filter != '' && $filter != 'FUNCTION') $sql .= " AND FALSE"; - - // Indexes - $sql .= " - UNION ALL - SELECT 'INDEX', NULL, NULL, pc.relname, pc2.relname FROM pg_class pc, - pg_index pi, pg_class pc2 WHERE pc.oid=pi.indrelid - AND pi.indexrelid=pc2.oid - AND pc2.relname ~* '.*{$term}.*' AND NOT pi.indisprimary AND NOT pi.indisunique"; - if (!$conf['show_system']) $sql .= " AND pc2.relname NOT LIKE 'pg@_%' ESCAPE '@'"; - if ($filter != '' && $filter != 'INDEX') $sql .= " AND FALSE"; - - // Check Constraints - $sql .= " - UNION ALL - SELECT 'CONSTRAINTTABLE', NULL, NULL, pc.relname, pr.rcname FROM pg_class pc, - pg_relcheck pr WHERE pc.oid=pr.rcrelid - AND pr.rcname ~* '.*{$term}.*'"; - if (!$conf['show_system']) $sql .= " AND pc.relname NOT LIKE 'pg@_%' ESCAPE '@'"; - if ($filter != '' && $filter != 'CONSTRAINT') $sql .= " AND FALSE"; - - // Unique and Primary Key Constraints - $sql .= " - UNION ALL - SELECT 'CONSTRAINTTABLE', NULL, NULL, pc.relname, pc2.relname FROM pg_class pc, - pg_index pi, pg_class pc2 WHERE pc.oid=pi.indrelid - AND pi.indexrelid=pc2.oid - AND pc2.relname ~* '.*{$term}.*' AND (pi.indisprimary OR pi.indisunique)"; - if (!$conf['show_system']) $sql .= " AND pc2.relname NOT LIKE 'pg@_%' ESCAPE '@'"; - if ($filter != '' && $filter != 'CONSTRAINT') $sql .= " AND FALSE"; - - // Triggers - $sql .= " - UNION ALL - SELECT 'TRIGGER', NULL, NULL, pc.relname, pt.tgname FROM pg_class pc, - pg_trigger pt WHERE pc.oid=pt.tgrelid - AND pt.tgname ~* '.*{$term}.*'"; - if (!$conf['show_system']) $sql .= " AND pc.relname NOT LIKE 'pg@_%' ESCAPE '@'"; - if ($filter != '' && $filter != 'TRIGGER') $sql .= " AND FALSE"; - - // Table Rules - $sql .= " - UNION ALL - SELECT 'RULETABLE', NULL, NULL, c.relname AS tablename, r.rulename - FROM pg_rewrite r, pg_class c - WHERE c.relkind='r' AND NOT EXISTS (SELECT 1 FROM pg_rewrite r WHERE r.ev_class = c.oid AND r.ev_type = '1') - AND r.rulename !~ '^_RET' AND c.oid = r.ev_class AND r.rulename ~* '.*{$term}.*'"; - if (!$conf['show_system']) $sql .= " AND c.relname NOT LIKE 'pg@_%' ESCAPE '@'"; - if ($filter != '' && $filter != 'RULE') $sql .= " AND FALSE"; - - // View Rules - $sql .= " - UNION ALL - SELECT 'RULEVIEW', NULL, NULL, c.relname AS tablename, r.rulename - FROM pg_rewrite r, pg_class c - WHERE c.relkind='r' AND EXISTS (SELECT 1 FROM pg_rewrite r WHERE r.ev_class = c.oid AND r.ev_type = '1') - AND r.rulename !~ '^_RET' AND c.oid = r.ev_class AND r.rulename ~* '.*{$term}.*'"; - if (!$conf['show_system']) $sql .= " AND c.relname NOT LIKE 'pg@_%' ESCAPE '@'"; - if ($filter != '' && $filter != 'RULE') $sql .= " AND FALSE"; - - // Advanced Objects - if ($conf['show_advanced']) { - // Types - $sql .= " - UNION ALL - SELECT 'TYPE', pt.oid, NULL, NULL, pt.typname FROM pg_type pt - WHERE typname ~* '.*{$term}.*' AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_class c WHERE c.oid = pt.typrelid))"; - if (!$conf['show_system']) $sql .= " AND pt.oid > '{$this->_lastSystemOID}'::oid"; - if ($filter != '' && $filter != 'TYPE') $sql .= " AND FALSE"; - - // Operators - $sql .= " - UNION ALL - SELECT 'OPERATOR', po.oid, NULL, NULL, po.oprname FROM pg_operator po - WHERE oprname ~* '.*{$term}.*'"; - if (!$conf['show_system']) $sql .= " AND po.oid > '{$this->_lastSystemOID}'::oid"; - if ($filter != '' && $filter != 'OPERATOR') $sql .= " AND FALSE"; - - // Languages - $sql .= " - UNION ALL - SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_language pl - WHERE lanname ~* '.*{$term}.*'"; - if (!$conf['show_system']) $sql .= " AND pl.lanispl"; - if ($filter != '' && $filter != 'LANGUAGE') $sql .= " AND FALSE"; - - // Aggregates - $sql .= " - UNION ALL - SELECT DISTINCT ON (a.aggname) 'AGGREGATE', a.oid, NULL, NULL, a.aggname FROM pg_aggregate a - WHERE aggname ~* '.*{$term}.*'"; - if (!$conf['show_system']) $sql .= " AND a.oid > '{$this->_lastSystemOID}'::oid"; - if ($filter != '' && $filter != 'AGGREGATE') $sql .= " AND FALSE"; - - // Op Classes - $sql .= " - UNION ALL - SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, NULL, NULL, po.opcname FROM pg_opclass po - WHERE po.opcname ~* '.*{$term}.*'"; - if (!$conf['show_system']) $sql .= " AND po.oid > '{$this->_lastSystemOID}'::oid"; - if ($filter != '' && $filter != 'OPCLASS') $sql .= " AND FALSE"; - } - - $sql .= " ORDER BY type, schemaname, relname, name"; + function setClientEncoding($encoding) { + $this->clean($encoding); - return $this->selectSet($sql); - } + $sql = "SET CLIENT_ENCODING TO '{$encoding}'"; - /** - * Private helper method to detect a valid $foo$ quote delimiter at - * the start of the parameter dquote - * @return True if valid, false otherwise - */ - function valid_dolquote($dquote) { - // XXX: support multibyte - return (ereg('^[$][$]', $dquote) || ereg('^[$][_[:alpha:]][_[:alnum:]]*[$]', $dquote)); + return $this->execute($sql); } /** @@ -4609,6 +6777,7 @@ class Postgres extends ADODB_base { * @param &$prevlen Length of previous character (ie. 1) * @param &$thislen Length of current character (ie. 1) */ + private function advance_1(&$i, &$prevlen, &$thislen) { $prevlen = $thislen; $i += $thislen; @@ -4616,6 +6785,17 @@ class Postgres extends ADODB_base { } /** + * Private helper method to detect a valid $foo$ quote delimiter at + * the start of the parameter dquote + * @return True if valid, false otherwise + */ + private + function valid_dolquote($dquote) { + // XXX: support multibyte + return (ereg('^[$][$]', $dquote) || ereg('^[$][_[:alpha:]][_[:alnum:]]*[$]', $dquote)); + } + + /** * Executes an SQL script as a series of SQL statements. Returns * the result of the final step. This is a very complicated lexer * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in @@ -4728,7 +6908,7 @@ class Postgres extends ADODB_base { /* * start of $foo$ type quote? - */ + */ else if (!$dol_quote && $this->valid_dolquote(substr($line, $i))) { $dol_end = strpos(substr($line, $i + 1), '$'); $dol_quote = substr($line, $i, $dol_end + 1); @@ -4749,7 +6929,7 @@ class Postgres extends ADODB_base { /* count nested parentheses */ else if (substr($line, $i, 1) == '(') { $paren_level++; - } + } else if (substr($line, $i, 1) == ')' && $paren_level > 0) { $paren_level--; @@ -4765,7 +6945,7 @@ class Postgres extends ADODB_base { /* * insert a cosmetic newline, if this is not the first * line in the buffer - */ + */ if (strlen($query_buf) > 0) $query_buf .= "\n"; /* append the line to the query buffer */ @@ -4791,8 +6971,8 @@ class Postgres extends ADODB_base { break; } } - } - } + } + } $query_buf = null; $query_start = $i + $thislen; @@ -4803,7 +6983,7 @@ class Postgres extends ADODB_base { * We grab the whole string so that we don't * mistakenly see $foo$ inside an identifier as the start * of a dollar quote. - */ + */ // XXX: multibyte here else if (ereg('^[_[:alpha:]]$', substr($line, $i, 1))) { $sub = substr($line, $i, $thislen); @@ -4815,7 +6995,7 @@ class Postgres extends ADODB_base { // Since we're now over the next character to be examined, it is necessary // to move back one space. $i-=$prevlen; - } + } } // end for /* Put the rest of the line in the query buffer. */ @@ -4834,7 +7014,7 @@ class Postgres extends ADODB_base { /* * Process query at the end of file without a semicolon, so long as * it's non-empty. - */ + */ if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) { // Execute the query (supporting 4.1.x PHP...) @@ -4853,77 +7033,412 @@ class Postgres extends ADODB_base { if ($copy == "\\.\n" || $copy == "\\.\r\n") { pg_end_copy($conn); break; + } + } + } + } + + fclose($fd); + + return true; + } + + /** + * Generates the SQL for the 'select' function + * @param $table The table from which to select + * @param $show An array of columns to show. Empty array means all columns. + * @param $values An array mapping columns to values + * @param $ops An array of the operators to use + * @param $orderby (optional) An array of column numbers or names (one based) + * mapped to sort direction (asc or desc or '' or null) to order by + * @return The SQL query + */ + function getSelectSQL($table, $show, $values, $ops, $orderby = array()) { + $this->fieldClean($table); + $this->fieldArrayClean($show); + + // If an empty array is passed in, then show all columns + if (sizeof($show) == 0) { + if ($this->hasObjectID($table)) + $sql = "SELECT \"{$this->id}\", * FROM "; + else + $sql = "SELECT * FROM "; + } + else { + // Add oid column automatically to results for editing purposes + if (!in_array($this->id, $show) && $this->hasObjectID($table)) + $sql = "SELECT \"{$this->id}\", \""; + else + $sql = "SELECT \""; + + $sql .= join('","', $show) . "\" FROM "; + } + + if ($this->hasSchemas() && isset($_REQUEST['schema'])) { + $this->fieldClean($_REQUEST['schema']); + $sql .= "\"{$_REQUEST['schema']}\"."; + } + $sql .= "\"{$table}\""; + + // If we have values specified, add them to the WHERE clause + $first = true; + if (is_array($values) && sizeof($values) > 0) { + foreach ($values as $k => $v) { + if ($v != '' || $this->selectOps[$ops[$k]] == 'p') { + $this->fieldClean($k); + if ($first) { + $sql .= " WHERE "; + $first = false; + } else { + $sql .= " AND "; + } + // Different query format depending on operator type + switch ($this->selectOps[$ops[$k]]) { + case 'i': + // Only clean the field for the inline case + // this is because (x), subqueries need to + // to allow 'a','b' as input. + $this->clean($v); + $sql .= "\"{$k}\" {$ops[$k]} '{$v}'"; + break; + case 'p': + $sql .= "\"{$k}\" {$ops[$k]}"; + break; + case 'x': + $sql .= "\"{$k}\" {$ops[$k]} ({$v})"; + break; + case 't': + $sql .= "\"{$k}\" {$ops[$k]}('{$v}')"; + break; + default: + // Shouldn't happen } } } + } + + // ORDER BY + if (is_array($orderby) && sizeof($orderby) > 0) { + $sql .= " ORDER BY "; + $first = true; + foreach ($orderby as $k => $v) { + if ($first) $first = false; + else $sql .= ', '; + if (ereg('^[0-9]+$', $k)) { + $sql .= $k; + } + else { + $this->fieldClean($k); + $sql .= '"' . $k . '"'; + } + if (strtoupper($v) == 'DESC') $sql .= " DESC"; + } + } + + return $sql; + } + + /** + * Returns a recordset of all columns in a query. Supports paging. + * @param $type Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier, + * or 'SELECT" if it's a select query + * @param $table The base table of the query. NULL for no table. + * @param $query The query that is being executed. NULL for no query. + * @param $sortkey The column number to sort by, or '' or null for no sorting + * @param $sortdir The direction in which to sort the specified column ('asc' or 'desc') + * @param $page The page of the relation to retrieve + * @param $page_size The number of rows per page + * @param &$max_pages (return-by-ref) The max number of pages in the relation + * @return A recordset on success + * @return -1 transaction error + * @return -2 counting error + * @return -3 page or page_size invalid + * @return -4 unknown type + * @return -5 failed setting transaction read only + */ + function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages) { + // Check that we're not going to divide by zero + if (!is_numeric($page_size) || $page_size != (int)$page_size || $page_size <= 0) return -3; + + // If $type is TABLE, then generate the query + switch ($type) { + case 'TABLE': + if (ereg('^[0-9]+$', $sortkey) && $sortkey > 0) $orderby = array($sortkey => $sortdir); + else $orderby = array(); + $query = $this->getSelectSQL($table, array(), array(), array(), $orderby); + break; + case 'QUERY': + case 'SELECT': + // Trim query + $query = trim($query); + // Trim off trailing semi-colon if there is one + if (substr($query, strlen($query) - 1, 1) == ';') + $query = substr($query, 0, strlen($query) - 1); + break; + default: + return -4; + } + + // Generate count query + $count = "SELECT COUNT(*) AS total FROM ($query) AS sub"; + + // Open a transaction + $status = $this->beginTransaction(); + if ($status != 0) return -1; + + // If backend supports read only queries, then specify read only mode + // to avoid side effects from repeating queries that do writes. + if ($this->hasReadOnlyQueries()) { + $status = $this->execute("SET TRANSACTION READ ONLY"); + if ($status != 0) { + $this->rollbackTransaction(); + return -5; + } + } + + + // Count the number of rows + $total = $this->browseQueryCount($query, $count); + if ($total < 0) { + $this->rollbackTransaction(); + return -2; + } + + // Calculate max pages + $max_pages = ceil($total / $page_size); + + // Check that page is less than or equal to max pages + if (!is_numeric($page) || $page != (int)$page || $page > $max_pages || $page < 1) { + $this->rollbackTransaction(); + return -3; + } + + // Set fetch mode to NUM so that duplicate field names are properly returned + // for non-table queries. Since the SELECT feature only allows selecting one + // table, duplicate fields shouldn't appear. + if ($type == 'QUERY') $this->conn->setFetchMode(ADODB_FETCH_NUM); + + // Figure out ORDER BY. Sort key is always the column number (based from one) + // of the column to order by. Only need to do this for non-TABLE queries + if ($type != 'TABLE' && ereg('^[0-9]+$', $sortkey) && $sortkey > 0) { + $orderby = " ORDER BY {$sortkey}"; + // Add sort order + if ($sortdir == 'desc') + $orderby .= ' DESC'; + else + $orderby .= ' ASC'; + } + else $orderby = ''; + + // Actually retrieve the rows, with offset and limit + if ($this->hasFullSubqueries()) + $rs = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size); + else + $rs = $this->selectSet("{$query} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size); + $status = $this->endTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + return $rs; + } + + /** + * Finds the number of rows that would be returned by a + * query. + * @param $query The SQL query + * @param $count The count query + * @return The count of rows + * @return -1 error + */ + function browseQueryCount($query, $count) { + return $this->selectField($count, 'total'); + } + + /** + * Returns a recordset of all columns in a table + * @param $table The name of a table + * @param $key The associative array holding the key to retrieve + * @return A recordset + */ + function browseRow($table, $key) { + $this->fieldClean($table); + + $schema = $this->schema(); + + $sql = "SELECT * FROM {$schema}\"{$table}\""; + if (is_array($key) && sizeof($key) > 0) { + $sql .= " WHERE true"; + foreach ($key as $k => $v) { + $this->fieldClean($k); + $this->clean($v); + $sql .= " AND \"{$k}\"='{$v}'"; + } + } + + return $this->selectSet($sql); + } + + // Type conversion routines + + /** + * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false + * @param $parameter the parameter + */ + function dbBool(&$parameter) { + if ($parameter) $parameter = 't'; + else $parameter = 'f'; + + return $parameter; + } + + /** + * Change a parameter from 't' or 'f' to a boolean, (others evaluate to false) + * @param $parameter the parameter + */ + function phpBool($parameter) { + $parameter = ($parameter == 't'); + return $parameter; + } + + // interfaces Statistics collector functions + + /** + * Fetches statistics for a database + * @param $database The database to fetch stats for + * @return A recordset + */ + function getStatsDatabase($database) { + $this->clean($database); + + $sql = "SELECT * FROM pg_stat_database WHERE datname='{$database}'"; + + return $this->selectSet($sql); + } + + /** + * Fetches tuple statistics for a table + * @param $table The table to fetch stats for + * @return A recordset + */ + function getStatsTableTuples($table) { + $this->clean($table); + + $sql = 'SELECT * FROM pg_stat_all_tables WHERE'; + if ($this->hasSchemas()) $sql .= " schemaname='{$this->_schema}' AND"; + $sql .= " relname='{$table}'"; + + return $this->selectSet($sql); + } + + /** + * Fetches I/0 statistics for a table + * @param $table The table to fetch stats for + * @return A recordset + */ + function getStatsTableIO($table) { + $this->clean($table); + + $sql = 'SELECT * FROM pg_statio_all_tables WHERE'; + if ($this->hasSchemas()) $sql .= " schemaname='{$this->_schema}' AND"; + $sql .= " relname='{$table}'"; + + return $this->selectSet($sql); + } + + /** + * Fetches tuple statistics for all indexes on a table + * @param $table The table to fetch index stats for + * @return A recordset + */ + function getStatsIndexTuples($table) { + $this->clean($table); + + $sql = 'SELECT * FROM pg_stat_all_indexes WHERE'; + if ($this->hasSchemas()) $sql .= " schemaname='{$this->_schema}' AND"; + $sql .= " relname='{$table}' ORDER BY indexrelname"; + + return $this->selectSet($sql); } - fclose($fd); + /** + * Fetches I/0 statistics for all indexes on a table + * @param $table The table to fetch index stats for + * @return A recordset + */ + function getStatsIndexIO($table) { + $this->clean($table); - return true; + $sql = 'SELECT * FROM pg_statio_all_indexes WHERE'; + if ($this->hasSchemas()) $sql .= " schemaname='{$this->_schema}' AND"; + $sql .= " relname='{$table}' ORDER BY indexrelname"; + + return $this->selectSet($sql); } // Capabilities - function hasAlterDatabaseOwner() { return false; } - function hasAlterDatabaseRename() { return false; } + + function hasAggregateSortOp() { return true; } + function hasAlterAggregate() { return true; } + function hasAlterColumnType() { return true; } + function hasAlterDatabaseOwner() { return true; } + function hasAlterDatabaseRename() { return true; } + function hasAlterSequenceOwner() { return true; } + function hasAlterSequenceProps() { return true; } + function hasAlterTableOwner() { return true; } + function hasAlterTableSchema() { return true; } + function hasAlterTrigger() { return true; } + function hasAnalyze() { return true; } + function hasAutovacuum() { return true; } + function hasCasts() { return true; } + function hasCompositeTypes() { return true; } + function hasConstraintsInfo() { return true; } + function hasConversions() { return true; } + function hasCreateTableLike() { return true; } + function hasCreateTableLikeWithConstraints() { return true; } + function hasCreateTableLikeWithIndexes() { return true; } + function hasDisableTriggers() { return true; } + function hasDomainConstraints() { return true; } + function hasDomains() { return true; } + function hasDropBehavior() { return true; } + function hasDropColumn() { return true; } + function hasEnumTypes() { return true; } + function hasFTS() { return true; } + function hasFullSubqueries() { return true; } + function hasFullVacuum() { return true; } + function hasFuncPrivs() { return true; } + function hasFunctionAlterOwner() { return true; } + function hasFunctionAlterSchema() { return true; } + function hasFunctionCosting() { return true; } + function hasFunctionGUC() { return true; } + function hasGrantOption() { return true; } + function hasIsClustered() { return true; } + function hasLocksView() { return true; } + function hasNamedParams() { return true; } + function hasObjectID() { return true; } + function hasPartialIndexes() { return true; } + function hasPrepare() { return true; } + function hasPreparedXacts() { return true; } + function hasProcesses() { return true; } + function hasReadOnlyQueries() { return true; } + function hasRecluster() { return true; } + function hasRoles() { return true; } + function hasSchemas() { return true; } + function hasSequenceAlterSchema() { return true; } + function hasServerAdminFuncs() { return true; } + function hasSharedComments() { return true; } + function hasSignals() { return true; } + function hasStatsCollector() { return true; } + function hasTablespaces() { return true; } + function hasUserAndDbVariables() { return true; } + function hasUserRename() { return true; } + function hasUserSessionDefaults() { return true; } + function hasVariables() { return true; } + function hasViewColumnRename() { return true; } + function hasVirtualTransactionId() { return true; } + function hasWithoutOIDs() { return true; } function hasAlterDatabase() { return $this->hasAlterDatabaseRename(); } - function hasSchemas() { return false; } - function hasConversions() { return false; } - function hasGrantOption() { return false; } - function hasIsClustered() { return false; } - function hasDropBehavior() { return false; } - function hasDropColumn() { return false; } - function hasDomains() { return false; } - function hasDomainConstraints() { return false; } - function hasAlterTrigger() { return false; } - function hasWithoutOIDs() { return false; } - function hasAlterTableOwner() { return false; } - function hasAlterTableSchema() { return false; } - function hasAlterSequenceOwner() { return false; } - function hasAlterSequenceProps() { return false; } - function hasSequenceAlterSchema() { return false; } - function hasPartialIndexes() { return false; } - function hasCasts() { return false; } - function hasFullSubqueries() { return false; } - function hasPrepare() { return false; } - function hasProcesses() { return false; } - function hasVariables() { return false; } - function hasStatsCollector() { return false; } - function hasAlterColumnType() { return false; } - function hasUserSessionDefaults() { return false; } - function hasUserRename() { return false; } - function hasRecluster() { return false; } - function hasFullVacuum() { return false; } - function hasConstraintsInfo() { return false; } function hasForeignKeysInfo() { return $this->hasConstraintsInfo(); } - function hasViewColumnRename() { return false; } - function hasTablespaces() { return false; } - function hasSignals() { return false; } - function hasNamedParams() { return false; } - function hasUserAndDbVariables() { return false; } - function hasCompositeTypes() { return false; } - function hasEnumTypes() {return false;} - function hasReadOnlyQueries() { return false; } - function hasFuncPrivs() { return false; } - function hasServerAdminFuncs() { return false; } - function hasRoles() { return false; } - function hasAutovacuum() { return false; } - function hasLocksView() { return false; } - function hasPreparedXacts() { return false; } - function hasDisableTriggers() { return false; } - function hasAlterAggregate() { return false; } - function hasAggregateSortOp() { return false; } - function hasSharedComments() {return false;} - function hasAnalyze() {return false;} - function hasCreateTableLike() {return false;} - function hasCreateTableLikeWithConstraints() {return false;} - function hasCreateTableLikeWithIndexes() {return false;} - function hasFTS() {return false;} - function hasVirtualTransactionId() {return false;} - function hasFunctionCosting() {return false;} - function hasFunctionGUC() {return false;} - function hasFunctionAlterSchema() { return false; } - function hasFunctionAlterOwner() { return false; } } - ?> diff --git a/classes/database/Postgres71.php b/classes/database/Postgres71.php deleted file mode 100644 index 247409b3..00000000 --- a/classes/database/Postgres71.php +++ /dev/null @@ -1,511 +0,0 @@ -<?php - -/** - * A class that implements the DB interface for Postgres - * Note: This class uses ADODB and returns RecordSets. - * - * $Id: Postgres71.php,v 1.80 2007/12/11 14:17:17 ioguix Exp $ - */ - -// @@@ THOUGHT: What about inherits? ie. use of ONLY??? - -include_once('./classes/database/Postgres.php'); - -class Postgres71 extends Postgres { - - var $major_version = 7.1; - var $_lastSystemOID = 18539; - var $_maxNameLen = 31; - - // List of all legal privileges that can be applied to different types - // of objects. - var $privlist = array( - 'table' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'ALL'), - 'view' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'ALL'), - 'sequence' => array('SELECT', 'UPDATE', 'ALL') - ); - - // List of characters in acl lists and the privileges they - // refer to. - var $privmap = array( - 'r' => 'SELECT', - 'w' => 'UPDATE', - 'a' => 'INSERT', - 'd' => 'DELETE', - 'R' => 'RULE', - 'x' => 'REFERENCES', - 't' => 'TRIGGER', - 'X' => 'EXECUTE', - 'U' => 'USAGE', - 'C' => 'CREATE', - 'T' => 'TEMPORARY' - ); - - // Function properties - var $funcprops = array(array('', 'ISSTRICT'), array('', 'ISCACHABLE')); - var $defaultprops = array('', ''); - - // Select operators - var $selectOps = array('=' => 'i', '!=' => 'i', '<' => 'i', '>' => 'i', '<=' => 'i', '>=' => 'i', '<<' => 'i', '>>' => 'i', '<<=' => 'i', '>>=' => 'i', - 'LIKE' => 'i', 'NOT LIKE' => 'i', 'ILIKE' => 'i', 'NOT ILIKE' => 'i', '~' => 'i', '!~' => 'i', '~*' => 'i', '!~*' => 'i', - 'IS NULL' => 'p', 'IS NOT NULL' => 'p', 'IN' => 'x', 'NOT IN' => 'x'); - // Supported join operations for use with view wizard - var $joinOps = array('INNER JOIN' => 'INNER JOIN', 'LEFT JOIN' => 'LEFT JOIN', 'RIGHT JOIN' => 'RIGHT JOIN', 'FULL JOIN' => 'FULL JOIN'); - - /** - * Constructor - * @param $conn The database connection - */ - function Postgres71($conn) { - $this->Postgres($conn); - } - - // Help functions - - function getHelpPages() { - include_once('./help/PostgresDoc71.php'); - return $this->help_page; - } - - /** - * Sets the client encoding - * @param $encoding The encoding to for the client - * @return 0 success - */ - function setClientEncoding($encoding) { - $this->clean($encoding); - - $sql = "SET CLIENT_ENCODING TO '{$encoding}'"; - - return $this->execute($sql); - } - - // Database functions - - /** - * Return all database available on the server - * @return A list of databases, sorted alphabetically - */ - function getDatabases($currentdatabase = NULL) { - global $conf, $misc; - - $server_info = $misc->getServerInfo(); - - if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser($server_info['username'])) { - $username = $server_info['username']; - $this->clean($username); - $clause = " AND pu.usename='{$username}'"; - } - else $clause = ''; - - if ($currentdatabase != NULL) - $orderby = "ORDER BY pdb.datname = '{$currentdatabase}' DESC, pdb.datname"; - else - $orderby = "ORDER BY pdb.datname"; - - if (!$conf['show_system']) - $where = ' AND NOT pdb.datistemplate'; - else - $where = ' AND pdb.datallowconn'; - - $sql = "SELECT pdb.datname AS datname, pu.usename AS datowner, pg_encoding_to_char(encoding) AS datencoding, - (SELECT description FROM pg_description pd WHERE pdb.oid=pd.objoid) AS datcomment - FROM pg_database pdb, pg_user pu - WHERE pdb.datdba = pu.usesysid - {$where} - {$clause} - {$orderby}"; - - return $this->selectSet($sql); - } - - // Table functions - - /** - * Protected method which alter a table - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $tblrs The table recordSet returned by getTable() - * @param $name The new name for the table - * @param $owner The new owner for the table - * @param $schema The new schema for the table - * @param $comment The comment on the table - * @param $tablespace The new tablespace for the table ('' means leave as is) - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 tablespace error - * @return -7 schema error - */ - /* protected */ - function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) { - - $status = parent::_alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace); - if ($status != 0) - return $status; - - // if name != tablename, table has been renamed in parent - $tablename = ($tblrs->fields['relname'] == $name) ? $tblrs->fields['relname'] : $name; - - /* $tablespace, schema not supported in pg71 */ - $this->fieldClean($owner); - - // Owner - if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) { - // If owner has been changed, then do the alteration. We are - // careful to avoid this generally as changing owner is a - // superuser only function. - $sql = "ALTER TABLE \"{$tablename}\" OWNER TO \"{$owner}\""; - - $status = $this->execute($sql); - if ($status != 0) return -5; - } - - return 0; - } - - /** - * Finds the number of rows that would be returned by a - * query. - * @param $query The SQL query - * @param $count The count query - * @return The count of rows - * @return -1 error - */ - function browseQueryCount($query, $count) { - return $this->selectField($count, 'total'); - } - - /** - * Retrieve the attribute definition of a table - * @param $table The name of the table - * @param $field (optional) The name of a field to return - * @return All attributes in order - */ - function getTableAttributes($table, $field = '') { - $this->clean($table); - $this->clean($field); - - if ($field == '') { - $sql = "SELECT - a.attname, t.typname as type, a.attlen, a.atttypmod, a.attnotnull, - a.atthasdef, adef.adsrc, -1 AS attstattarget, a.attstorage, t.typstorage, - false AS attisserial, - (SELECT description FROM pg_description d WHERE d.objoid = a.oid) as comment - FROM - pg_attribute a LEFT JOIN pg_attrdef adef - ON a.attrelid=adef.adrelid AND a.attnum=adef.adnum, - pg_class c, - pg_type t - WHERE - c.relname = '{$table}' AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid - ORDER BY a.attnum"; - } - else { - $sql = "SELECT - a.attname, t.typname as type, t.typname as base_type, - a.attlen, a.atttypmod, a.attnotnull, - a.atthasdef, adef.adsrc, -1 AS attstattarget, a.attstorage, t.typstorage, - (SELECT description FROM pg_description d WHERE d.objoid = a.oid) as comment - FROM - pg_attribute a LEFT JOIN pg_attrdef adef - ON a.attrelid=adef.adrelid AND a.attnum=adef.adnum, - pg_class c, - pg_type t - WHERE - c.relname = '{$table}' AND a.attname='{$field}' AND a.attrelid = c.oid AND a.atttypid = t.oid"; - } - - return $this->selectSet($sql); - } - - /** - * Formats a type correctly for display. This is a no-op in PostgreSQL 7.1+ - * @param $typname The name of the type - * @param $typmod The contents of the typmod field - */ - function formatType($typname, $typmod) { - return $typname; - } - - // View functions - - /** - * Protected method which alter a view - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $vwrs The view recordSet returned by getView() - * @param $name The new name for the view - * @param $owner The new owner for the view - * @param $comment The comment on the view - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 schema error - */ - function _alterView($vwrs, $name, $owner, $schema, $comment) { - - $status = parent::_alterView($vwrs, $name, $owner, $schema, $comment); - if ($status != 0) - return $status; - - $this->fieldClean($name); - // if name is not empty, view has been renamed in parent - $view = (!empty($name)) ? $name : $tblrs->fields['relname']; - - $this->fieldClean($owner); - // Owner - if ((!empty($owner)) && ($vwrs->fields['relowner'] != $owner)) { - // If owner has been changed, then do the alteration. We are - // careful to avoid this generally as changing owner is a - // superuser only function. - $sql = "ALTER TABLE \"{$view}\" OWNER TO \"{$owner}\""; - $status = $this->execute($sql); - if ($status != 0) return -5; - } - - return 0; - } - - // Sequence functions - - /** - * Protected method which alter a sequence - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $seqrs The sequence recordSet returned by getSequence() - * @param $name The new name for the sequence - * @param $comment The comment on the sequence - * @param $owner The new owner for the sequence - * @param $schema The new schema for the sequence - * @param $increment The increment - * @param $minvalue The min value - * @param $maxvalue The max value - * @param $startvalue The starting value - * @param $cachevalue The cache value - * @param $cycledvalue True if cycled, false otherwise - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - */ - /*protected*/ - function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue) { - - $status = parent::_alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue); - if ($status != 0) - return $status; - - /* $schema, $increment, $minvalue, $maxvalue, $startvalue, $cachevalue, - * $cycledvalue not supported in pg71 */ - // if name != seqname, sequence has been renamed in parent - $sequence = ($seqrs->fields['seqname'] == $name) ? $seqrs->fields['seqname'] : $name; - $this->fieldClean($owner); - - // Owner - if (!empty($owner)) { - - // If owner has been changed, then do the alteration. We are - // careful to avoid this generally as changing owner is a - // superuser only function. - if ($seqrs->fields['seqowner'] != $owner) { - $sql = "ALTER TABLE \"{$sequence}\" OWNER TO \"{$owner}\""; - $status = $this->execute($sql); - if ($status != 0) - return -5; - } - } - return 0; - } - - /** - * Resets a given sequence to min value of sequence - * @param $sequence Sequence name - * @return 0 success - * @return -1 sequence not found - */ - function resetSequence($sequence) { - // Get the minimum value of the sequence - $seq = $this->getSequence($sequence); - if ($seq->recordCount() != 1) return -1; - $minvalue = $seq->fields['min_value']; - - /* This double-cleaning is deliberate */ - $this->fieldClean($sequence); - $this->clean($sequence); - - $sql = "SELECT SETVAL('\"{$sequence}\"', {$minvalue}, FALSE)"; - - return $this->execute($sql); - } - - // Function functions - - /** - * Returns all details for a particular function - * @param $func The name of the function to retrieve - * @return Function info - */ - function getFunction($function_oid) { - $this->clean($function_oid); - - $sql = "SELECT - pc.oid AS prooid, - proname, - lanname as prolanguage, - format_type(prorettype, NULL) as proresult, - prosrc, - probin, - proretset, - proisstrict, - proiscachable, - oidvectortypes(pc.proargtypes) AS proarguments, - (SELECT description FROM pg_description pd WHERE pc.oid=pd.objoid) AS procomment - FROM - pg_proc pc, pg_language pl - WHERE - pc.oid = '$function_oid'::oid - AND pc.prolang = pl.oid - "; - - return $this->selectSet($sql); - } - - /** - * Returns an array containing a function's properties - * @param $f The array of data for the function - * @return An array containing the properties - */ - function getFunctionProperties($f) { - $temp = array(); - - // Strict - $f['proisstrict'] = $this->phpBool($f['proisstrict']); - if ($f['proisstrict']) - $temp[] = 'ISSTRICT'; - else - $temp[] = ''; - - // Cachable - $f['proiscachable'] = $this->phpBool($f['proiscachable']); - if ($f['proiscachable']) - $temp[] = 'ISCACHABLE'; - else - $temp[] = ''; - - return $temp; - } - - // Constraint functions - - /** - * Returns a list of all constraints on a table - * @param $table The table to find rules for - * @return A recordset - */ - function getConstraints($table) { - $this->clean($table); - - $status = $this->beginTransaction(); - if ($status != 0) return -1; - - $sql = " - SELECT conname, consrc, contype, indkey FROM ( - SELECT - rcname AS conname, - 'CHECK (' || rcsrc || ')' AS consrc, - 'c' AS contype, - rcrelid AS relid, - NULL AS indkey - FROM - pg_relcheck - UNION ALL - SELECT - pc.relname, - NULL, - CASE WHEN indisprimary THEN - 'p' - ELSE - 'u' - END, - pi.indrelid, - indkey - FROM - pg_class pc, - pg_index pi - WHERE - pc.oid=pi.indexrelid - AND (pi.indisunique OR pi.indisprimary) - ) AS sub - WHERE relid = (SELECT oid FROM pg_class WHERE relname='{$table}') - ORDER BY - 1 - "; - - return $this->selectSet($sql); - } - - // Trigger functions - - /** - * Grabs a list of triggers on a table - * @param $table The name of a table whose triggers to retrieve - * @return A recordset - */ - function getTriggers($table = '') { - $this->clean($table); - - // We include constraint triggers - $sql = "SELECT t.tgname, t.tgisconstraint, t.tgdeferrable, t.tginitdeferred, t.tgtype, - t.tgargs, t.tgnargs, t.tgconstrrelid, - (SELECT relname FROM pg_class c2 WHERE c2.oid=t.tgconstrrelid) AS tgconstrrelname, - p.proname AS tgfname, c.relname, NULL AS tgdef - FROM pg_trigger t LEFT JOIN pg_proc p - ON t.tgfoid=p.oid, pg_class c - WHERE t.tgrelid=c.oid - AND c.relname='{$table}'"; - - return $this->selectSet($sql); - } - - // Aggregate functions - - /** - * Gets all aggregates - * @return A recordset - */ - function getAggregates() { - global $conf; - - if ($conf['show_system']) - $where = ''; - else - $where = "WHERE a.oid > '{$this->_lastSystemOID}'::oid"; - - $sql = " - SELECT - a.aggname AS proname, - CASE a.aggbasetype - WHEN 0 THEN NULL - ELSE format_type(a.aggbasetype, NULL) - END AS proargtypes, - (SELECT description FROM pg_description pd WHERE a.oid=pd.objoid) AS aggcomment - FROM - pg_aggregate a - {$where} - ORDER BY - 1, 2; - "; - - return $this->selectSet($sql); - } - - // Capabilities - function hasAlterTableOwner() { return true; } - function hasAlterSequenceOwner() { return true; } - function hasFullSubqueries() { return true; } - -} - -?> diff --git a/classes/database/Postgres72.php b/classes/database/Postgres72.php deleted file mode 100644 index d6898717..00000000 --- a/classes/database/Postgres72.php +++ /dev/null @@ -1,688 +0,0 @@ -<?php - -/** - * A class that implements the DB interface for Postgres - * Note: This class uses ADODB and returns RecordSets. - * - * $Id: Postgres72.php,v 1.94 2007/12/12 04:11:10 xzilla Exp $ - */ - - -include_once('./classes/database/Postgres71.php'); - -class Postgres72 extends Postgres71 { - - var $major_version = 7.2; - - // Set the maximum built-in ID. - var $_lastSystemOID = 16554; - - // List of all legal privileges that can be applied to different types - // of objects. - var $privlist = array( - 'table' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), - 'view' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), - 'sequence' => array('SELECT', 'UPDATE', 'ALL PRIVILEGES') - ); - - // Extra "magic" types. BIGSERIAL was added in PostgreSQL 7.2. - var $extraTypes = array('SERIAL', 'BIGSERIAL'); - - /** - * Constructor - * @param $conn The database connection - */ - function Postgres72($conn) { - $this->Postgres71($conn); - - // Correct the error in the encoding tables, that was - // fixed in PostgreSQL 7.2 - $this->codemap['LATIN5'] = 'ISO-8859-9'; - } - - // Help functions - - function getHelpPages() { - include_once('./help/PostgresDoc72.php'); - return $this->help_page; - } - - // User functions - - /** - * Helper function that computes encypted PostgreSQL passwords - * @param $username The username - * @param $password The password - */ - function _encryptPassword($username, $password) { - return 'md5' . md5($password . $username); - } - - /** - * Changes a user's password - * @param $username The username - * @param $password The new password - * @return 0 success - */ - function changePassword($username, $password) { - $enc = $this->_encryptPassword($username, $password); - $this->fieldClean($username); - $this->clean($enc); - - $sql = "ALTER USER \"{$username}\" WITH ENCRYPTED PASSWORD '{$enc}'"; - - return $this->execute($sql); - } - - /** - * Creates a new user - * @param $username The username of the user to create - * @param $password A password for the user - * @param $createdb boolean Whether or not the user can create databases - * @param $createuser boolean Whether or not the user can create other users - * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire - * @param $group (array) The groups to create the user in - * @return 0 success - */ - function createUser($username, $password, $createdb, $createuser, $expiry, $groups) { - $enc = $this->_encryptPassword($username, $password); - $this->fieldClean($username); - $this->clean($enc); - $this->clean($expiry); - $this->fieldArrayClean($groups); - - $sql = "CREATE USER \"{$username}\""; - if ($password != '') $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; - $sql .= ($createdb) ? ' CREATEDB' : ' NOCREATEDB'; - $sql .= ($createuser) ? ' CREATEUSER' : ' NOCREATEUSER'; - if (is_array($groups) && sizeof($groups) > 0) $sql .= " IN GROUP \"" . join('", "', $groups) . "\""; - if ($expiry != '') $sql .= " VALID UNTIL '{$expiry}'"; - else $sql .= " VALID UNTIL 'infinity'"; - - return $this->execute($sql); - } - - /** - * Adjusts a user's info - * @param $username The username of the user to modify - * @param $password A new password for the user - * @param $createdb boolean Whether or not the user can create databases - * @param $createuser boolean Whether or not the user can create other users - * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire. - * @return 0 success - */ - function setUser($username, $password, $createdb, $createuser, $expiry) { - $enc = $this->_encryptPassword($username, $password); - $this->fieldClean($username); - $this->clean($enc); - $this->clean($expiry); - - $sql = "ALTER USER \"{$username}\""; - if ($password != '') $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; - $sql .= ($createdb) ? ' CREATEDB' : ' NOCREATEDB'; - $sql .= ($createuser) ? ' CREATEUSER' : ' NOCREATEUSER'; - if ($expiry != '') $sql .= " VALID UNTIL '{$expiry}'"; - else $sql .= " VALID UNTIL 'infinity'"; - - return $this->execute($sql); - } - - /** - * Returns all available process information. - * @param $database (optional) Find only connections to specified database - * @return A recordset - */ - function getProcesses($database = null) { - if ($database === null) - $sql = "SELECT * FROM pg_stat_activity ORDER BY datname, usename, procpid"; - else { - $this->clean($database); - $sql = "SELECT * FROM pg_stat_activity WHERE datname='{$database}' ORDER BY usename, procpid"; - } - - return $this->selectSet($sql); - } - - // Table functions - - /** - * Returns the SQL for changing the current user - * @param $user The user to change to - * @return The SQL - */ - function getChangeUserSQL($user) { - $this->clean($user); - return "SET SESSION AUTHORIZATION '{$user}';"; - } - - /** - * Checks to see whether or not a table has a unique id column - * @param $table The table name - * @return True if it has a unique id, false otherwise - * @return -99 error - */ - function hasObjectID($table) { - $this->clean($table); - - $sql = "SELECT relhasoids FROM pg_class WHERE relname='{$table}'"; - - $rs = $this->selectSet($sql); - if ($rs->recordCount() != 1) return -99; - else { - $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']); - return $rs->fields['relhasoids']; - } - } - - /** - * Returns table information - * @param $table The name of the table - * @return A recordset - */ - function getTable($table) { - $this->clean($table); - - $sql = "SELECT pc.relname, - pg_get_userbyid(pc.relowner) AS relowner, - (SELECT description FROM pg_description pd - WHERE pc.oid=pd.objoid AND objsubid = 0) AS relcomment - FROM pg_class pc - WHERE pc.relname='{$table}'"; - - return $this->selectSet($sql); - } - - /** - * Return all tables in current database - * @param $all True to fetch all tables, false for just in current schema - * @return All tables, sorted alphabetically - */ - function getTables($all = false) { - global $conf; - if (!$conf['show_system'] || $all) $where = "AND c.relname NOT LIKE 'pg\\\\_%' "; - else $where = ''; - - $sql = "SELECT NULL AS nspname, c.relname, - (SELECT usename FROM pg_user u WHERE u.usesysid=c.relowner) AS relowner, - (SELECT description FROM pg_description pd WHERE c.oid=pd.objoid AND objsubid = 0) AS relcomment, - reltuples::numeric - FROM pg_class c WHERE c.relkind='r' {$where}ORDER BY relname"; - return $this->selectSet($sql); - } - - /** - * Retrieve the attribute definition of a table - * @param $table The name of the table - * @param $field (optional) The name of a field to return - * @return All attributes in order - */ - function getTableAttributes($table, $field = '') { - $this->clean($table); - $this->clean($field); - - if ($field == '') { - $sql = " - SELECT - a.attname, - format_type(a.atttypid, a.atttypmod) as type, a.atttypmod, - a.attnotnull, a.atthasdef, adef.adsrc, - -1 AS attstattarget, a.attstorage, t.typstorage, false AS attisserial, - description as comment - FROM - pg_attribute a LEFT JOIN pg_attrdef adef - ON a.attrelid=adef.adrelid AND a.attnum=adef.adnum - LEFT JOIN pg_type t ON a.atttypid=t.oid - LEFT JOIN pg_description d ON (a.attrelid = d.objoid AND a.attnum = d.objsubid) - WHERE - a.attrelid = (SELECT oid FROM pg_class WHERE relname='{$table}') - AND a.attnum > 0 - ORDER BY a.attnum"; - } - else { - $sql = " - SELECT - a.attname, - format_type(a.atttypid, a.atttypmod) as type, - format_type(a.atttypid, NULL) as base_type, - a.atttypmod, - a.attnotnull, a.atthasdef, adef.adsrc, - -1 AS attstattarget, a.attstorage, t.typstorage, - description as comment - FROM - pg_attribute a LEFT JOIN pg_attrdef adef - ON a.attrelid=adef.adrelid AND a.attnum=adef.adnum - LEFT JOIN pg_type t ON a.atttypid=t.oid - LEFT JOIN pg_description d ON (a.attrelid = d.objoid AND a.attnum = d.objsubid) - WHERE - a.attrelid = (SELECT oid FROM pg_class WHERE relname='{$table}') - AND a.attname = '{$field}'"; - } - - return $this->selectSet($sql); - } - - // View functions - - /** - * Returns a list of all views in the database - * @return All views - */ - function getViews() { - global $conf; - - if (!$conf['show_system']) - $where = " WHERE viewname NOT LIKE 'pg\\\\_%'"; - else - $where = ''; - - $sql = "SELECT viewname AS relname, viewowner AS relowner, definition AS vwdefinition, - (SELECT description FROM pg_description pd, pg_class pc - WHERE pc.oid=pd.objoid AND pc.relname=v.viewname AND pd.objsubid = 0) AS relcomment - FROM pg_views v - {$where} - ORDER BY relname"; - - return $this->selectSet($sql); - } - - /** - * Returns all details for a particular view - * @param $view The name of the view to retrieve - * @return View info - */ - function getView($view) { - $this->clean($view); - - $sql = "SELECT viewname AS relname, NULL AS nspname, viewowner AS relowner, definition AS vwdefinition, - (SELECT description FROM pg_description pd, pg_class pc - WHERE pc.oid=pd.objoid AND pc.relname=v.viewname AND pd.objsubid = 0) AS relcomment - FROM pg_views v - WHERE viewname='{$view}'"; - - return $this->selectSet($sql); - } - - // Constraint functions - - /** - * Removes a constraint from a relation - * @param $constraint The constraint to drop - * @param $relation The relation from which to drop - * @param $type The type of constraint (c, f, u or p) - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - * @return -99 dropping foreign keys not supported - */ - function dropConstraint($constraint, $relation, $type, $cascade) { - $this->fieldClean($constraint); - $this->fieldClean($relation); - - switch ($type) { - case 'c': - // CHECK constraint - $sql = "ALTER TABLE \"{$relation}\" DROP CONSTRAINT \"{$constraint}\" RESTRICT"; - - return $this->execute($sql); - break; - case 'p': - case 'u': - // PRIMARY KEY or UNIQUE constraint - return $this->dropIndex($constraint, $cascade); - break; - case 'f': - // FOREIGN KEY constraint - return -99; - } - } - - /** - * Adds a unique constraint to a table - * @param $table The table to which to add the unique key - * @param $fields (array) An array of fields over which to add the unique key - * @param $name (optional) The name to give the key, otherwise default name is assigned - * @param $tablespace (optional) The tablespace for the schema, '' indicates default. - * @return 0 success - * @return -1 no fields given - */ - function addUniqueKey($table, $fields, $name = '', $tablespace = '') { - if (!is_array($fields) || sizeof($fields) == 0) return -1; - $this->fieldClean($table); - $this->fieldArrayClean($fields); - $this->fieldClean($name); - $this->fieldClean($tablespace); - - $schema = $this->schema(); - - $sql = "ALTER TABLE {$schema}\"{$table}\" ADD "; - if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; - $sql .= "UNIQUE (\"" . join('","', $fields) . "\")"; - - if ($tablespace != '' && $this->hasTablespaces()) - $sql .= " USING INDEX TABLESPACE \"{$tablespace}\""; - - return $this->execute($sql); - } - - /** - * Adds a primary key constraint to a table - * @param $table The table to which to add the primery key - * @param $fields (array) An array of fields over which to add the primary key - * @param $name (optional) The name to give the key, otherwise default name is assigned - * @param $tablespace (optional) The tablespace for the schema, '' indicates default. - * @return 0 success - * @return -1 no fields given - */ - function addPrimaryKey($table, $fields, $name = '', $tablespace = '') { - if (!is_array($fields) || sizeof($fields) == 0) return -1; - $this->fieldClean($table); - $this->fieldArrayClean($fields); - $this->fieldClean($name); - $this->fieldClean($tablespace); - - $schema = $this->schema(); - - $sql = "ALTER TABLE {$schema}\"{$table}\" ADD "; - if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; - $sql .= "PRIMARY KEY (\"" . join('","', $fields) . "\")"; - - if ($tablespace != '' && $this->hasTablespaces()) - $sql .= " USING INDEX TABLESPACE \"{$tablespace}\""; - - return $this->execute($sql); - } - - // Function functions - - /** - * Returns a list of all functions in the database - * @param $all If true, will find all available functions, if false just userland ones - * @return All functions - */ - function getFunctions($all = false) { - if ($all) - $where = ''; - else - $where = "AND p.oid > '{$this->_lastSystemOID}'"; - - $sql = "SELECT - p.oid AS prooid, - p.proname, - false AS proretset, - format_type(p.prorettype, NULL) AS proresult, - oidvectortypes(p.proargtypes) AS proarguments, - pl.lanname AS prolanguage, - (SELECT description FROM pg_description pd WHERE p.oid=pd.objoid) AS procomment, - p.proname || ' (' || oidvectortypes(p.proargtypes) || ')' AS proproto, - format_type(p.prorettype, NULL) AS proreturns - FROM - pg_proc p, pg_language pl - WHERE - p.prolang = pl.oid AND - (pronargs = 0 OR oidvectortypes(p.proargtypes) <> '') - {$where} - ORDER BY - p.proname, proresult - "; - - return $this->selectSet($sql); - } - - /** - * Updates (replaces) a function. - * @param $function_oid The OID of the function - * @param $funcname The name of the function to create - * @param $newname The new name for the function - * @param $args The array of argument types - * @param $returns The return type - * @param $definition The definition for the new function - * @param $language The language the function is written for - * @param $flags An array of optional flags - * @param $setof True if returns a set, false otherwise - * @param $comment The comment on the function - * @return 0 success - * @return -1 transaction error - * @return -2 drop function error - * @return -3 create function error - * @return -4 comment error - */ - function setFunction($function_oid, $funcname, $newname, $args, $returns, $definition, $language, $flags, $setof, $rows, $cost, $comment) { - // Begin a transaction - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Replace the existing function - if ($funcname != $newname) { - $status = $this->dropFunction($function_oid, false); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } - - $status = $this->createFunction($newname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, false); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - } else { - $status = $this->createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, true); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - } - - // Comment on the function - $this->fieldClean($newname); - $this->clean($comment); - $status = $this->setComment('FUNCTION', "\"{$newname}\"({$args})", null, $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - - return $this->endTransaction(); - } - - // Type functions - - /** - * Returns a list of all types in the database - * @param $all If true, will find all available functions, if false just those in search path - * @param $tabletypes If true, will include table types - * @param $domains Ignored - * @return A recordet - */ - function getTypes($all = false, $tabletypes = false, $domains = false) { - global $conf; - - if ($all || $conf['show_system']) { - $where = ''; - } else { - $where = "AND pt.oid > '{$this->_lastSystemOID}'::oid"; - } - // Never show system table types - $where2 = "AND c.oid > '{$this->_lastSystemOID}'::oid"; - - // Create type filter - $tqry = "'c'"; - if ($tabletypes) - $tqry .= ", 'r', 'v'"; - - $sql = "SELECT - pt.typname AS basename, - format_type(pt.oid, NULL) AS typname, - pu.usename AS typowner, - (SELECT description FROM pg_description pd WHERE pt.oid=pd.objoid) AS typcomment - FROM - pg_type pt, - pg_user pu - WHERE - pt.typowner = pu.usesysid - AND (pt.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_class c WHERE c.oid = pt.typrelid {$where2})) - AND typname !~ '^_' - {$where} - ORDER BY typname - "; - - return $this->selectSet($sql); - } - - // Opclass functions - - /** - * Gets all opclasses - * @return A recordset - */ - function getOpClasses() { - global $conf; - - if ($conf['show_system']) - $where = ''; - else - $where = "AND po.oid > '{$this->_lastSystemOID}'::oid"; - - $sql = " - SELECT DISTINCT - pa.amname, - po.opcname, - format_type(po.opcintype, NULL) AS opcintype, - TRUE AS opcdefault, - NULL::text AS opccomment - FROM - pg_opclass po, pg_am pa - WHERE - po.opcamid=pa.oid - {$where} - ORDER BY 1,2 - "; - - return $this->selectSet($sql); - } - - // Administration functions - - /** - * Vacuums a database - * @param $table The table to vacuum - * @param $analyze If true, also does analyze - * @param $full If true, selects "full" vacuum (PostgreSQL >= 7.2) - * @param $freeze If true, selects aggressive "freezing" of tuples (PostgreSQL >= 7.2) - */ - function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false) { - $sql = "VACUUM"; - if ($full) $sql .= " FULL"; - if ($freeze) $sql .= " FREEZE"; - if ($analyze) $sql .= " ANALYZE"; - if ($table != '') { - $this->fieldClean($table); - $sql .= " \"{$table}\""; - } - - return $this->execute($sql); - } - - /** - * Analyze a database - * @note PostgreSQL 7.2 finally had an independent ANALYZE command - * @param $table (optional) The table to analyze - */ - function analyzeDB($table = '') { - if ($table != '') { - $this->fieldClean($table); - $sql = "ANALYZE \"{$table}\""; - } - else - $sql = "ANALYZE"; - - return $this->execute($sql); - } - - // Statistics collector functions - - /** - * Fetches statistics for a database - * @param $database The database to fetch stats for - * @return A recordset - */ - function getStatsDatabase($database) { - $this->clean($database); - - $sql = "SELECT * FROM pg_stat_database WHERE datname='{$database}'"; - - return $this->selectSet($sql); - } - - /** - * Fetches tuple statistics for a table - * @param $table The table to fetch stats for - * @return A recordset - */ - function getStatsTableTuples($table) { - $this->clean($table); - - $sql = 'SELECT * FROM pg_stat_all_tables WHERE'; - if ($this->hasSchemas()) $sql .= " schemaname='{$this->_schema}' AND"; - $sql .= " relname='{$table}'"; - - return $this->selectSet($sql); - } - - /** - * Fetches I/0 statistics for a table - * @param $table The table to fetch stats for - * @return A recordset - */ - function getStatsTableIO($table) { - $this->clean($table); - - $sql = 'SELECT * FROM pg_statio_all_tables WHERE'; - if ($this->hasSchemas()) $sql .= " schemaname='{$this->_schema}' AND"; - $sql .= " relname='{$table}'"; - - return $this->selectSet($sql); - } - - /** - * Fetches tuple statistics for all indexes on a table - * @param $table The table to fetch index stats for - * @return A recordset - */ - function getStatsIndexTuples($table) { - $this->clean($table); - - $sql = 'SELECT * FROM pg_stat_all_indexes WHERE'; - if ($this->hasSchemas()) $sql .= " schemaname='{$this->_schema}' AND"; - $sql .= " relname='{$table}' ORDER BY indexrelname"; - - return $this->selectSet($sql); - } - - /** - * Fetches I/0 statistics for all indexes on a table - * @param $table The table to fetch index stats for - * @return A recordset - */ - function getStatsIndexIO($table) { - $this->clean($table); - - $sql = 'SELECT * FROM pg_statio_all_indexes WHERE'; - if ($this->hasSchemas()) $sql .= " schemaname='{$this->_schema}' AND"; - $sql .= " relname='{$table}' ORDER BY indexrelname"; - - return $this->selectSet($sql); - } - - // Capabilities - function hasWithoutOIDs() { return true; } - function hasPartialIndexes() { return true; } - function hasProcesses() { return true; } - function hasStatsCollector() { return true; } - function hasFullVacuum() { return true; } - function hasAnalyze() { return true; } - -} - -?> diff --git a/classes/database/Postgres73.php b/classes/database/Postgres73.php index 98811e58..d72ad8bf 100644 --- a/classes/database/Postgres73.php +++ b/classes/database/Postgres73.php @@ -9,50 +9,20 @@ // @@@ THOUGHT: What about inherits? ie. use of ONLY??? -include_once('./classes/database/Postgres72.php'); +include_once('./classes/database/Postgres74.php'); -class Postgres73 extends Postgres72 { +class Postgres73 extends Postgres74 { var $major_version = 7.3; - - // Store the current schema - var $_schema; - - // Last oid assigned to a system object - var $_lastSystemOID = 16974; - - // Max object name length - var $_maxNameLen = 63; - - // List of all legal privileges that can be applied to different types - // of objects. - var $privlist = array( - 'table' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), - 'view' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), - 'sequence' => array('SELECT', 'UPDATE', 'ALL PRIVILEGES'), - 'database' => array('CREATE', 'TEMPORARY', 'ALL PRIVILEGES'), - 'function' => array('EXECUTE', 'ALL PRIVILEGES'), - 'language' => array('USAGE', 'ALL PRIVILEGES'), - 'schema' => array('CREATE', 'USAGE', 'ALL PRIVILEGES') - ); - // Function properties - var $funcprops = array( array('', 'VOLATILE', 'IMMUTABLE', 'STABLE'), - array('', 'CALLED ON NULL INPUT', 'RETURNS NULL ON NULL INPUT'), - array('', 'SECURITY INVOKER', 'SECURITY DEFINER')); - var $defaultprops = array('', '', ''); - - // Select operators - var $selectOps = array('=' => 'i', '!=' => 'i', '<' => 'i', '>' => 'i', '<=' => 'i', '>=' => 'i', '<<' => 'i', '>>' => 'i', '<<=' => 'i', '>>=' => 'i', - 'LIKE' => 'i', 'NOT LIKE' => 'i', 'ILIKE' => 'i', 'NOT ILIKE' => 'i', 'SIMILAR TO' => 'i', - 'NOT SIMILAR TO' => 'i', '~' => 'i', '!~' => 'i', '~*' => 'i', '!~*' => 'i', - 'IS NULL' => 'p', 'IS NOT NULL' => 'p', 'IN' => 'x', 'NOT IN' => 'x'); + // How often to execute the trigger + var $triggerFrequency = array('ROW'); /** * Constructor * @param $conn The database connection */ function Postgres73($conn) { - $this->Postgres72($conn); + $this->Postgres74($conn); } // Help functions @@ -62,70 +32,7 @@ class Postgres73 extends Postgres72 { return $this->help_page; } - /** - * Returns the current schema to prepend on object names - */ - function schema() { - return "\"{$this->_schema}\"."; - } - - // Schema functions - - /** - * Sets the current working schema. Will also set class variable. - * @param $schema The the name of the schema to work in - * @return 0 success - */ - function setSchema($schema) { - // Get the current schema search path, including 'pg_catalog'. - $search_path = $this->getSearchPath(); - // Prepend $schema to search path - array_unshift($search_path, $schema); - $status = $this->setSearchPath($search_path); - if ($status == 0) { - $this->clean($schema); - $this->_schema = $schema; - return 0; - } - else return $status; - } - - /** - * Sets the current schema search path - * @param $paths An array of schemas in required search order - * @return 0 success - * @return -1 Array not passed - * @return -2 Array must contain at least one item - */ - function setSearchPath($paths) { - if (!is_array($paths)) return -1; - elseif (sizeof($paths) == 0) return -2; - elseif (sizeof($paths) == 1 && $paths[0] == '') { - // Need to handle empty paths in some cases - $paths[0] = 'pg_catalog'; - } - - // Loop over all the paths to check that none are empty - $temp = array(); - foreach ($paths as $schema) { - if ($schema != '') $temp[] = $schema; - } - $this->fieldArrayClean($temp); - - $sql = 'SET SEARCH_PATH TO "' . implode('","', $temp) . '"'; - - return $this->execute($sql); - } - - /** - * Return the current schema search path - * @return Array of schema names - */ - function getSearchPath() { - $sql = 'SELECT current_schemas(false) AS search_path'; - - return $this->phpArray($this->selectField($sql, 'search_path')); - } + // Database functions /** * Return all schemas in the current database @@ -143,170 +50,43 @@ class Postgres73 extends Postgres72 { } } else $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; - $sql = "SELECT pn.nspname, pu.usename AS nspowner, pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment + $sql = " + SELECT pn.nspname, pu.usename AS nspowner, + pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment FROM pg_catalog.pg_namespace pn LEFT JOIN pg_catalog.pg_user pu ON (pn.nspowner = pu.usesysid) - {$where} ORDER BY nspname"; - - return $this->selectSet($sql); - } - - /** - * Return all information relating to a schema - * @param $schema The name of the schema - * @return Schema information - */ - function getSchemaByName($schema) { - $this->clean($schema); - $sql = "SELECT nspname, nspowner, nspacl, pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment - FROM pg_catalog.pg_namespace pn - WHERE nspname='{$schema}'"; - return $this->selectSet($sql); - } - - /** - * Creates a new schema. - * @param $schemaname The name of the schema to create - * @param $authorization (optional) The username to create the schema for. - * @param $comment (optional) If omitted, defaults to nothing - * @return 0 success - */ - function createSchema($schemaname, $authorization = '', $comment = '') { - $this->fieldClean($schemaname); - $this->fieldClean($authorization); - $this->clean($comment); - - $sql = "CREATE SCHEMA \"{$schemaname}\""; - if ($authorization != '') $sql .= " AUTHORIZATION \"{$authorization}\""; - - if ($comment != '') { - $status = $this->beginTransaction(); - if ($status != 0) return -1; - } - - // Create the new schema - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Set the comment - if ($comment != '') { - $status = $this->setComment('SCHEMA', $schemaname, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - return $this->endTransaction(); - } - - return 0; - } - - /** - * Drops a schema. - * @param $schemaname The name of the schema to drop - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropSchema($schemaname, $cascade) { - $this->fieldClean($schemaname); - - $sql = "DROP SCHEMA \"{$schemaname}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } - - /** - * Updates a schema. - * @param $schemaname The name of the schema to drop - * @param $comment The new comment for this schema - * @return 0 success - */ - function updateSchema($schemaname, $comment, $name) { - $this->fieldClean($schemaname); - $this->fieldClean($name); - $this->clean($comment); - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $status = $this->setComment('SCHEMA', $schemaname, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Only if the name has changed - if ($name != $schemaname) { - $sql = "ALTER SCHEMA \"{$schemaname}\" RENAME TO \"{$name}\""; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - } - - return $this->endTransaction(); - } - - /** - * Returns the specified variable information. - * @return the field - */ - function getVariable($setting) { - $sql = "SHOW $setting"; - - return $this->selectSet($sql); - } - - /** - * Returns all available variable information. - * @return A recordset - */ - function getVariables() { - $sql = "SHOW ALL"; + {$where} + ORDER BY nspname"; return $this->selectSet($sql); } - /** - * Returns all available process information. - * @param $database (optional) Find only connections to specified database - * @return A recordset - */ - function getProcesses($database = null) { - if ($database === null) - $sql = "SELECT * FROM pg_catalog.pg_stat_activity ORDER BY datname, usename, procpid"; - else { - $this->clean($database); - $sql = "SELECT * FROM pg_catalog.pg_stat_activity WHERE datname='{$database}' ORDER BY usename, procpid"; - } - - return $this->selectSet($sql); - } + // Trigger functions /** - * Returns table locks information in the current database + * Grabs a list of triggers on a table + * @param $table The name of a table whose triggers to retrieve * @return A recordset */ - function getLocks() { - global $conf; - - if (!$conf['show_system']) - $where = "AND pn.nspname NOT LIKE 'pg\\\\_%'"; - else - $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; + function getTriggers($table = '') { + $this->clean($table); - $sql = "SELECT pn.nspname, pc.relname AS tablename, pl.transaction, pl.pid, pl.mode, pl.granted - FROM pg_catalog.pg_locks pl, pg_catalog.pg_class pc, pg_catalog.pg_namespace pn - WHERE pl.relation = pc.oid AND pc.relnamespace=pn.oid {$where} - ORDER BY nspname,tablename"; + $sql = "SELECT t.tgname, t.tgisconstraint, t.tgdeferrable, t.tginitdeferred, t.tgtype, + t.tgargs, t.tgnargs, t.tgconstrrelid, + (SELECT relname FROM pg_catalog.pg_class c2 WHERE c2.oid=t.tgconstrrelid) AS tgconstrrelname, + p.proname AS tgfname, c.relname, NULL AS tgdef, + p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, + ns.nspname AS pronamespace + FROM pg_catalog.pg_namespace ns, + pg_catalog.pg_trigger t LEFT JOIN pg_catalog.pg_proc p + ON t.tgfoid=p.oid, pg_catalog.pg_class c + WHERE t.tgrelid=c.oid + AND c.relname='{$table}' + AND c.relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}') + AND (NOT tgisconstraint OR NOT EXISTS + (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c + ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) + WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f')) + AND p.pronamespace = ns.oid"; return $this->selectSet($sql); } @@ -314,130 +94,6 @@ class Postgres73 extends Postgres72 { // Table functions /** - * Checks to see whether or not a table has a unique id column - * @param $table The table name - * @return True if it has a unique id, false otherwise - * @return -99 error - */ - function hasObjectID($table) { - $this->clean($table); - - $sql = "SELECT relhasoids FROM pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}')"; - - $rs = $this->selectSet($sql); - if ($rs->recordCount() != 1) return -99; - else { - $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']); - return $rs->fields['relhasoids']; - } - } - - /** - * Protected method which alter a table - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $tblrs The table recordSet returned by getTable() - * @param $name The new name for the table - * @param $owner The new owner for the table - * @param $schema The new schema for the table - * @param $comment The comment on the table - * @param $tablespace The new tablespace for the table ('' means leave as is) - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 tablespace error - * @return -7 schema error - */ - /* protected */ - function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) { - - $this->fieldClean($name); - $this->fieldClean($owner); - $this->clean($comment); - /* $schema, $owner, $tablespace not supported in pg70 */ - - $table = $tblrs->fields['relname']; - - // Comment - $status = $this->setComment('TABLE', '', $table, $comment); - if ($status != 0) return -4; - - // Rename (only if name has changed) - if ($name != $table) { - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" RENAME TO \"{$name}\""; - $status = $this->execute($sql); - if ($status != 0) - return -3; - $table = $name; - } - - // Owner - if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) { - // If owner has been changed, then do the alteration. We are - // careful to avoid this generally as changing owner is a - // superuser only function. - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" OWNER TO \"{$owner}\""; - - $status = $this->execute($sql); - if ($status != 0) return -5; - } - - return 0; - } - - /** - * Removes a table from the database - * @param $table The table to drop - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropTable($table, $cascade) { - $this->fieldClean($table); - - $sql = "DROP TABLE \"{$this->_schema}\".\"{$table}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } - - /** - * Given an array of attnums and a relation, returns an array mapping - * attribute number to attribute name. - * @param $table The table to get attributes for - * @param $atts An array of attribute numbers - * @return An array mapping attnum to attname - * @return -1 $atts must be an array - * @return -2 wrong number of attributes found - */ - function getAttributeNames($table, $atts) { - $this->clean($table); - $this->arrayClean($atts); - - if (!is_array($atts)) return -1; - - if (sizeof($atts) == 0) return array(); - - $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE - attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND - relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}')) - AND attnum IN ('" . join("','", $atts) . "')"; - - $rs = $this->selectSet($sql); - if ($rs->recordCount() != sizeof($atts)) { - return -2; - } - else { - $temp = array(); - while (!$rs->EOF) { - $temp[$rs->fields['attnum']] = $rs->fields['attname']; - $rs->moveNext(); - } - return $temp; - } - } - - /** * Get the fields for uniquely identifying a row in a table * @param $table The table for which to retrieve the identifier * @return An array mapping attribute number to attribute name, empty for no identifiers @@ -483,437 +139,7 @@ class Postgres73 extends Postgres72 { } } - /** - * Returns table information - * @param $table The name of the table - * @return A recordset - */ - function getTable($table) { - $this->clean($table); - - $sql = " - SELECT - c.relname, n.nspname, u.usename AS relowner, - pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind = 'r' - AND n.nspname = '{$this->_schema}' - AND c.relname = '{$table}'"; - - return $this->selectSet($sql); - } - - /** - * Return all tables in current database (and schema) - * @param $all True to fetch all tables, false for just in current schema - * @return All tables, sorted alphabetically - */ - function getTables($all = false) { - if ($all) { - // Exclude pg_catalog and information_schema tables - $sql = "SELECT schemaname AS nspname, tablename AS relname, tableowner AS relowner - FROM pg_catalog.pg_tables - WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') - ORDER BY schemaname, tablename"; - } else { - $sql = "SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, - pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment, - reltuples::bigint - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind = 'r' - AND nspname='{$this->_schema}' - ORDER BY c.relname"; - } - - return $this->selectSet($sql); - } - - /** - * Retrieve the attribute definition of a table - * @param $table The name of the table - * @param $field (optional) The name of a field to return - * @return All attributes in order - */ - function getTableAttributes($table, $field = '') { - $this->clean($table); - $this->clean($field); - - if ($field == '') { - // This query is made much more complex by the addition of the 'attisserial' field. - // The subquery to get that field checks to see if there is an internally dependent - // sequence on the field. - $sql = " - SELECT - a.attname, - pg_catalog.format_type(a.atttypid, a.atttypmod) as type, - a.atttypmod, - a.attnotnull, a.atthasdef, adef.adsrc, - a.attstattarget, a.attstorage, t.typstorage, - ( - SELECT 1 FROM pg_catalog.pg_depend pd, pg_catalog.pg_class pc - WHERE pd.objid=pc.oid - AND pd.classid=pc.tableoid - AND pd.refclassid=pc.tableoid - AND pd.refobjid=a.attrelid - AND pd.refobjsubid=a.attnum - AND pd.deptype='i' - AND pc.relkind='S' - ) IS NOT NULL AS attisserial, - pg_catalog.col_description(a.attrelid, a.attnum) AS comment - - FROM - pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef - ON a.attrelid=adef.adrelid - AND a.attnum=adef.adnum - LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid - WHERE - a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE - nspname = '{$this->_schema}')) - AND a.attnum > 0 AND NOT a.attisdropped - ORDER BY a.attnum"; - } - else { - $sql = " - SELECT - a.attname, - pg_catalog.format_type(a.atttypid, a.atttypmod) as type, - pg_catalog.format_type(a.atttypid, NULL) as base_type, - a.atttypmod, - a.attnotnull, a.atthasdef, adef.adsrc, - a.attstattarget, a.attstorage, t.typstorage, - pg_catalog.col_description(a.attrelid, a.attnum) AS comment - FROM - pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef - ON a.attrelid=adef.adrelid - AND a.attnum=adef.adnum - LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid - WHERE - a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE - nspname = '{$this->_schema}')) - AND a.attname = '{$field}'"; - } - - return $this->selectSet($sql); - } - - /** - * Sets default value of a column - * @param $table The table from which to drop - * @param $column The column name to set - * @param $default The new default value - * @return 0 success - */ - function setColumnDefault($table, $column, $default) { - $this->fieldClean($table); - $this->fieldClean($column); - - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" SET DEFAULT {$default}"; - - return $this->execute($sql); - } - - /** - * Drops default value of a column - * @param $table The table from which to drop - * @param $column The column name to drop default - * @return 0 success - */ - function dropColumnDefault($table, $column) { - $this->fieldClean($table); - $this->fieldClean($column); - - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT"; - - return $this->execute($sql); - } - - /** - * Drops a column from a table - * @param $table The table from which to drop a column - * @param $column The column to be dropped - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropColumn($table, $column, $cascade) { - $this->fieldClean($table); - $this->fieldClean($column); - - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" DROP COLUMN \"{$column}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } - - /** - * Renames a column in a table - * @param $table The table containing the column to be renamed - * @param $column The column to be renamed - * @param $newName The new name for the column - * @return 0 success - */ - function renameColumn($table, $column, $newName) { - $this->fieldClean($table); - $this->fieldClean($column); - $this->fieldClean($newName); - - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" RENAME COLUMN \"{$column}\" TO \"{$newName}\""; - - return $this->execute($sql); - } - - /** - * Sets whether or not a column can contain NULLs - * @param $table The table that contains the column - * @param $column The column to alter - * @param $state True to set null, false to set not null - * @return 0 success - */ - function setColumnNull($table, $column, $state) { - $this->fieldClean($table); - $this->fieldClean($column); - - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . (($state) ? 'DROP' : 'SET') . " NOT NULL"; - - return $this->execute($sql); - } - - // Row functions - - /** - * Empties a table in the database - * @param $table The table to be emptied - * @return 0 success - */ - function emptyTable($table) { - $this->fieldClean($table); - - $sql = "DELETE FROM \"{$this->_schema}\".\"{$table}\""; - - return $this->execute($sql); - } - - /** - * Adds a check constraint to a table - * @param $table The table to which to add the check - * @param $definition The definition of the check - * @param $name (optional) The name to give the check, otherwise default name is assigned - * @return 0 success - */ - function addCheckConstraint($table, $definition, $name = '') { - $this->fieldClean($table); - $this->fieldClean($name); - // @@ How the heck do you clean a definition??? - - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ADD "; - if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; - $sql .= "CHECK ({$definition})"; - - return $this->execute($sql); - } - - /** - * Drops a check constraint from a table - * @param $table The table from which to drop the check - * @param $name The name of the check to be dropped - * @return 0 success - * @return -2 transaction error - * @return -3 lock error - * @return -4 check drop error - */ - function dropCheckConstraint($table, $name) { - $this->clean($table); - $this->clean($name); - - // Begin transaction - $status = $this->beginTransaction(); - if ($status != 0) return -2; - - // Properly lock the table - $sql = "LOCK TABLE \"{$this->_schema}\".\"{$table}\" IN ACCESS EXCLUSIVE MODE"; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - - // Delete the check constraint - $sql = "DELETE FROM pg_relcheck WHERE rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE - nspname = '{$this->_schema}')) AND rcname='{$name}'"; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - - // Update the pg_class catalog to reflect the new number of checks - $sql = "UPDATE pg_class SET relchecks=(SELECT COUNT(*) FROM pg_relcheck WHERE - rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE - nspname = '{$this->_schema}'))) - WHERE relname='{$table}'"; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - - // Otherwise, close the transaction - return $this->endTransaction(); - } - - // Administration functions - - /** - * Vacuums a database - * @param $table The table to vacuum - * @param $analyze If true, also does analyze - * @param $full If true, selects "full" vacuum (PostgreSQL >= 7.2) - * @param $freeze If true, selects aggressive "freezing" of tuples (PostgreSQL >= 7.2) - */ - function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false) { - - $sql = "VACUUM"; - if ($full) $sql .= " FULL"; - if ($freeze) $sql .= " FREEZE"; - if ($analyze) $sql .= " ANALYZE"; - if ($table != '') { - $this->fieldClean($table); - $sql .= " \"{$this->_schema}\".\"{$table}\""; - } - - return $this->execute($sql); - } - - /** - * Analyze a database - * @note PostgreSQL 7.2 finally had an independent ANALYZE command - * @param $table (optional) The table to analyze - */ - function analyzeDB($table = '') { - if ($table != '') { - $this->fieldClean($table); - - $sql = "ANALYZE \"{$this->_schema}\".\"{$table}\""; - } - else - $sql = "ANALYZE"; - - return $this->execute($sql); - } - - // Inheritance functions - - /** - * Finds the names and schemas of parent tables (in order) - * @param $table The table to find the parents for - * @return A recordset - */ - function getTableParents($table) { - $this->clean($table); - - $sql = " - SELECT - pn.nspname, relname - FROM - pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn - WHERE - pc.oid=pi.inhparent - AND pc.relnamespace=pn.oid - AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$this->_schema}')) - ORDER BY - pi.inhseqno - "; - - return $this->selectSet($sql); - } - - - /** - * Finds the names and schemas of child tables - * @param $table The table to find the children for - * @return A recordset - */ - function getTableChildren($table) { - $this->clean($table); - - $sql = " - SELECT - pn.nspname, relname - FROM - pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn - WHERE - pc.oid=pi.inhrelid - AND pc.relnamespace=pn.oid - AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$this->_schema}')) - "; - - return $this->selectSet($sql); - } - - /** - * Protected method which alter a view - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $vwrs The view recordSet returned by getView() - * @param $name The new name for the view - * @param $owner The new owner for the view - * @param $comment The comment on the view - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 schema error - */ - /*protected*/ - function _alterView($vwrs, $name, $owner, $schema, $comment) { - - $view = $vwrs->fields['relname']; - - // Comment - $this->clean($comment); - if ($this->setComment('VIEW', $view, '', $comment) != 0) - return -4; - - // Owner - $this->fieldClean($owner); - if ((!empty($owner)) && ($vwrs->fields['relowner'] != $owner)) { - // If owner has been changed, then do the alteration. We are - // careful to avoid this generally as changing owner is a - // superuser only function. - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$view}\" OWNER TO \"{$owner}\""; - $status = $this->execute($sql); - if ($status != 0) return -5; - } - - // Rename (only if name has changed) - $this->fieldClean($name); - if ($name != $view) { - if ($this->renameView($view, $name) != 0) - return -3; - } - - return 0; - } - - /** - * Returns a list of all views in the database - * @return All views - */ - function getViews() { - $sql = "SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, - pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment - FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) - WHERE (n.nspname='{$this->_schema}') AND (c.relkind = 'v'::\"char\") ORDER BY relname"; - - return $this->selectSet($sql); - } + // View function /** * Returns all details for a particular view @@ -923,231 +149,65 @@ class Postgres73 extends Postgres72 { function getView($view) { $this->clean($view); - $sql = "SELECT c.relname, n.nspname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, - pg_catalog.pg_get_viewdef(c.oid) AS vwdefinition, pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment - FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) - WHERE (c.relname = '$view') - AND n.nspname='{$this->_schema}'"; + $sql = " + SELECT c.relname, n.nspname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, + pg_catalog.pg_get_viewdef(c.oid) AS vwdefinition, pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment + FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) + WHERE (c.relname = '$view') + AND n.nspname='{$this->_schema}'"; return $this->selectSet($sql); } - /** - * Updates a view. - * @param $viewname The name fo the view to update - * @param $definition The new definition for the view - * @return 0 success - * @return -1 transaction error - * @return -2 drop view error - * @return -3 create view error - */ - function setView($viewname, $definition,$comment) { - return $this->createView($viewname, $definition, true, $comment); - } - - /** - * Rename a view - * @param $view The current view's name - * @param $name The new view's name - * @return -1 Failed - * @return 0 success - */ - function renameView($view, $name) { - $this->fieldClean($name); - $this->fieldClean($view); - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$view}\" RENAME TO \"{$name}\""; - if ($this->execute($sql) != 0) - return -1; - return 0; - } - - // Sequence functions /** - * Resets a given sequence to min value of sequence - * @param $sequence Sequence name - * @return 0 success - * @return -1 sequence not found - */ - function resetSequence($sequence) { - // Get the minimum value of the sequence - $seq = $this->getSequence($sequence); - if ($seq->recordCount() != 1) return -1; - $minvalue = $seq->fields['min_value']; - - /* This double-cleaning is deliberate */ - $this->fieldClean($sequence); - $this->clean($sequence); - - $sql = "SELECT pg_catalog.SETVAL('\"{$this->_schema}\".\"{$sequence}\"', {$minvalue})"; - - return $this->execute($sql); - } - - /** - * Execute nextval on a given sequence - * @param $sequence Sequence name - * @return 0 success - * @return -1 sequence not found - */ - function nextvalSequence($sequence) { - /* This double-cleaning is deliberate */ - $this->fieldClean($sequence); - $this->clean($sequence); - - $sql = "SELECT pg_catalog.NEXTVAL('\"{$this->_schema}\".\"{$sequence}\"')"; - - return $this->execute($sql); - } - - /** - * Execute setval on a given sequence - * @param $sequence Sequence name - * @param $nextvalue The next value - * @return 0 success - * @return -1 sequence not found - */ - function setvalSequence($sequence, $nextvalue) { - /* This double-cleaning is deliberate */ - $this->fieldClean($sequence); - $this->clean($sequence); - $this->clean($nextvalue); - - $sql = "SELECT pg_catalog.SETVAL('\"{$this->_schema}\".\"{$sequence}\"', '{$nextvalue}')"; - - return $this->execute($sql); - } - - /** - * Returns all sequences in the current database - * @return A recordset - */ - 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 ); - } - - /** - * Returns properties of a single sequence - * @param $sequence Sequence name - * @return A recordset - */ - function getSequence($sequence) { - $this->fieldClean($sequence); - - $sql = "SELECT c.relname AS seqname, s.*, - pg_catalog.obj_description(s.tableoid, 'pg_class') AS seqcomment, - u.usename AS seqowner, n.nspname - FROM \"{$sequence}\" AS s, 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.relname = '{$sequence}' AND c.relkind = 'S' AND n.nspname='{$this->_schema}' - AND n.oid = c.relnamespace"; - - return $this->selectSet( $sql ); - } - - /** - * Rename a sequence - * @param $sequence The sequence name - * @param $name The new name for the sequence - * @return 0 success - */ - function renameSequence($sequence, $name) { - $this->fieldClean($name); - $this->fieldClean($sequence); - - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$sequence}\" RENAME TO \"{$name}\""; - return $this->execute($sql); - } - - /** - * Protected method which alter a sequence - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $seqrs The sequence recordSet returned by getSequence() - * @param $name The new name for the sequence - * @param $comment The comment on the sequence - * @param $owner The new owner for the sequence - * @param $schema The new schema for the sequence - * @param $increment The increment - * @param $minvalue The min value - * @param $maxvalue The max value - * @param $startvalue The starting value - * @param $cachevalue The cache value - * @param $cycledvalue True if cycled, false otherwise - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -7 schema error - */ - /*protected*/ - function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue) { - - $sequence = $seqrs->fields['seqname']; - $this->fieldClean($name); - $this->clean($comment); - $this->fieldClean($owner); + * Protected method which alter a sequence + * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION + * @param $seqrs The sequence recordSet returned by getSequence() + * @param $name The new name for the sequence + * @param $comment The comment on the sequence + * @param $owner The new owner for the sequence + * @param $schema The new schema for the sequence + * @param $increment The increment + * @param $minvalue The min value + * @param $maxvalue The max value + * @param $startvalue The starting value + * @param $cachevalue The cache value + * @param $cycledvalue True if cycled, false otherwise + * @return 0 success + * @return -3 rename error + * @return -4 comment error + * @return -5 owner error + */ + protected + function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, + $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue) { + /* $schema not supported in pg80- */ + $this->fieldArrayClean($seqrs->fields); // Comment - $status = $this->setComment('SEQUENCE', $sequence, '', $comment); + $this->clean($comment); + $status = $this->setComment('SEQUENCE', $seqrs->fields['seqname'], '', $comment); if ($status != 0) return -4; - // Rename (only if name has changed) - if ($name != $sequence) { - $status = $this->renameSequence($sequence, $name); - if ($status != 0) - return -3; - $sequence = $name; - } - // Owner - if (!empty($owner)) { - - // If owner has been changed, then do the alteration. We are - // careful to avoid this generally as changing owner is a - // superuser only function. - if ($seqrs->fields['seqowner'] != $owner) { - $sql = "ALTER TABLE \"{$sequence}\" OWNER TO \"{$owner}\""; - $status = $this->execute($sql); - if ($status != 0) - return -5; - } - } + $this->fieldClean($owner); + $status = $this->alterSequenceOwner($seqrs, $owner); + if ($status != 0) + return -5; + + // Rename + $this->fieldClean($name); + $status = $this->alterSequenceName($seqrs, $name); + if ($status != 0) + return -3; return 0; } - /** - * Drops a given sequence - * @param $sequence Sequence name - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropSequence($sequence, $cascade) { - $this->fieldClean($sequence); - - $sql = "DROP SEQUENCE \"{$this->_schema}\".\"{$sequence}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } + // Indexe functions /** * Grabs a list of indexes for a table @@ -1171,573 +231,93 @@ class Postgres73 extends Postgres72 { return $this->selectSet($sql); } - /** - * Removes an index from the database - * @param $index The index to drop - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropIndex($index, $cascade) { - $this->fieldClean($index); - - $sql = "DROP INDEX \"{$this->_schema}\".\"{$index}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } + // Role, User and Group functions /** - * Clusters an index - * @param $index The name of the index - * @param $table The table the index is on - * @return 0 success + * Returns users in a specific group + * @param $groname The name of the group + * @return All users in the group */ - function clusterIndex($index, $table) { - $this->fieldClean($index); - $this->fieldClean($table); + function getGroup($groname) { + $this->clean($groname); - // We don't bother with a transaction here, as there's no point rolling - // back an expensive cluster if a cheap analyze fails for whatever reason - $sql = "CLUSTER \"{$index}\" ON \"{$this->_schema}\".\"{$table}\""; + $sql = "SELECT grolist FROM pg_group WHERE groname = '{$groname}'"; - return $this->execute($sql); - } + $grodata = $this->selectSet($sql); + if ($grodata->fields['grolist'] !== null && $grodata->fields['grolist'] != '{}') { + $members = $grodata->fields['grolist']; + $members = ereg_replace("\{|\}","",$members); + $this->clean($members); - /** - * Rebuild indexes - * @param $type 'DATABASE' or 'TABLE' or 'INDEX' - * @param $name The name of the specific database, table, or index to be reindexed - * @param $force If true, recreates indexes forcedly in PostgreSQL 7.0-7.1, forces rebuild of system indexes in 7.2-7.3, ignored in >=7.4 - */ - function reindex($type, $name, $force = false) { - $this->fieldClean($name); - switch($type) { - case 'DATABASE': - $sql = "REINDEX {$type} \"{$name}\""; - if ($force) $sql .= ' FORCE'; - break; - case 'TABLE': - case 'INDEX': - $sql = "REINDEX {$type} \"{$this->_schema}\".\"{$name}\""; - if ($force) $sql .= ' FORCE'; - break; - default: - return -1; - } - - return $this->execute($sql); + $sql = "SELECT usename FROM pg_user WHERE usesysid IN ({$members}) ORDER BY usename"; } - - /** - * Grabs a single trigger - * @param $table The name of a table whose triggers to retrieve - * @param $trigger The name of the trigger to retrieve - * @return A recordset - */ - function getTrigger($table, $trigger) { - $this->clean($table); - $this->clean($trigger); - - $sql = "SELECT * FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c - WHERE t.tgrelid=c.oid - AND c.relname='{$table}' - AND t.tgname='{$trigger}' - AND c.relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}')"; + else $sql = "SELECT usename FROM pg_user WHERE false"; return $this->selectSet($sql); } - /** - * Grabs a list of triggers on a table - * @param $table The name of a table whose triggers to retrieve - * @return A recordset - */ - function getTriggers($table = '') { - $this->clean($table); - - $sql = "SELECT t.tgname, t.tgisconstraint, t.tgdeferrable, t.tginitdeferred, t.tgtype, - t.tgargs, t.tgnargs, t.tgconstrrelid, - (SELECT relname FROM pg_catalog.pg_class c2 WHERE c2.oid=t.tgconstrrelid) AS tgconstrrelname, - p.proname AS tgfname, c.relname, NULL AS tgdef, - p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, - ns.nspname AS pronamespace - FROM pg_catalog.pg_namespace ns, - pg_catalog.pg_trigger t LEFT JOIN pg_catalog.pg_proc p - ON t.tgfoid=p.oid, pg_catalog.pg_class c - WHERE t.tgrelid=c.oid - AND c.relname='{$table}' - AND c.relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}') - AND (NOT tgisconstraint OR NOT EXISTS - (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c - ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) - WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f')) - AND p.pronamespace = ns.oid"; - - return $this->selectSet($sql); - } - - /** - * Creates a trigger - * @param $tgname The name of the trigger to create - * @param $table The name of the table - * @param $tgproc The function to execute - * @param $tgtime BEFORE or AFTER - * @param $tgevent Event - * @param $tgargs The function arguments - * @return 0 success - */ - function createTrigger($tgname, $table, $tgproc, $tgtime, $tgevent, $tgfrequency, $tgargs) { - $this->fieldClean($tgname); - $this->fieldClean($table); - $this->fieldClean($tgproc); - - /* No Statement Level Triggers in PostgreSQL (by now) */ - $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime} - {$tgevent} ON \"{$this->_schema}\".\"{$table}\" - FOR EACH {$tgfrequency} EXECUTE PROCEDURE \"{$tgproc}\"({$tgargs})"; - - return $this->execute($sql); - } - - /** - * Drops a trigger - * @param $tgname The name of the trigger to drop - * @param $table The table from which to drop the trigger - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropTrigger($tgname, $table, $cascade) { - $this->fieldClean($tgname); - $this->fieldClean($table); - - $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$this->_schema}\".\"{$table}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } - - /** - * Alters a trigger - * @param $table The name of the table containing the trigger - * @param $trigger The name of the trigger to alter - * @param $name The new name for the trigger - * @return 0 success - */ - function alterTrigger($table, $trigger, $name) { - $this->fieldClean($table); - $this->fieldClean($trigger); - $this->fieldClean($name); - - $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$this->_schema}\".\"{$table}\" RENAME TO \"{$name}\""; - - return $this->execute($sql); - } - // Function functions /** - * Returns a list of all functions in the database - * @param $all If true, will find all available functions, if false just those in search path - * @param $type If not null, will find all functions with return value = type - * - * @return All functions - */ - function getFunctions($all = false, $type = null) { - if ($all) { - $where = 'pg_catalog.pg_function_is_visible(p.oid)'; - $distinct = 'DISTINCT ON (p.proname)'; - - if ($type) { - $where .= " AND p.prorettype = (select oid from pg_catalog.pg_type p where p.typname = 'trigger') "; - } - } - else { - $where = "n.nspname = '{$this->_schema}'"; - - $distinct = ''; - } - - $sql = "SELECT - {$distinct} - p.oid AS prooid, - p.proname, - p.proretset, - pg_catalog.format_type(p.prorettype, NULL) AS proresult, - pg_catalog.oidvectortypes(p.proargtypes) AS proarguments, - pl.lanname AS prolanguage, - pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment, - p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, - CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns, - u.usename AS proowner - FROM pg_catalog.pg_proc p - INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace - INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang - LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner - WHERE NOT p.proisagg - AND {$where} - ORDER BY p.proname, proresult - "; - - return $this->selectSet($sql); - } - - /** - * Returns all details for a particular function - * @param $func The name of the function to retrieve - * @return Function info - */ - function getFunction($function_oid) { - $this->clean($function_oid); - - $sql = "SELECT - pc.oid AS prooid, - proname, - pg_catalog.pg_get_userbyid(proowner) AS proowner, - nspname as proschema, - lanname as prolanguage, - pg_catalog.format_type(prorettype, NULL) as proresult, - prosrc, - probin, - proretset, - proisstrict, - provolatile, - prosecdef, - pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments, - pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment - FROM - pg_catalog.pg_proc pc, pg_catalog.pg_language pl, pg_catalog.pg_namespace n - WHERE - pc.oid = '$function_oid'::oid - AND pc.prolang = pl.oid - AND n.oid = pc.pronamespace - "; - - return $this->selectSet($sql); - } - - /** - * Returns an array containing a function's properties - * @param $f The array of data for the function - * @return An array containing the properties - */ - function getFunctionProperties($f) { - $temp = array(); - - // Volatility - if ($f['provolatile'] == 'v') - $temp[] = 'VOLATILE'; - elseif ($f['provolatile'] == 'i') - $temp[] = 'IMMUTABLE'; - elseif ($f['provolatile'] == 's') - $temp[] = 'STABLE'; - else - return -1; - - // Null handling - $f['proisstrict'] = $this->phpBool($f['proisstrict']); - if ($f['proisstrict']) - $temp[] = 'RETURNS NULL ON NULL INPUT'; - else - $temp[] = 'CALLED ON NULL INPUT'; - - // Security - $f['prosecdef'] = $this->phpBool($f['prosecdef']); - if ($f['prosecdef']) - $temp[] = 'SECURITY DEFINER'; - else - $temp[] = 'SECURITY INVOKER'; - - return $temp; - } - - /** - * Returns a list of all functions that can be used in triggers - */ - function getTriggerFunctions() { - return $this->getFunctions(true, 'trigger'); - } - - /** - * Creates a new function. + * Updates (replaces) a function. + * @param $function_oid The OID of the function * @param $funcname The name of the function to create - * @param $args A comma separated string of types + * @param $newname The new name for the function + * @param $args The array of argument types * @param $returns The return type * @param $definition The definition for the new function * @param $language The language the function is written for * @param $flags An array of optional flags - * @param $setof True if it returns a set, false otherwise - * @param $replace (optional) True if OR REPLACE, false for normal - * @return 0 success - */ - function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $replace = false) { - $this->fieldClean($funcname); - $this->clean($args); - $this->clean($language); - $this->arrayClean($flags); - - $sql = "CREATE"; - if ($replace) $sql .= " OR REPLACE"; - $sql .= " FUNCTION \"{$this->_schema}\".\"{$funcname}\" ("; - - if ($args != '') - $sql .= $args; - - // For some reason, the returns field cannot have quotes... - $sql .= ") RETURNS "; - if ($setof) $sql .= "SETOF "; - $sql .= "{$returns} AS "; - - if (is_array($definition)) { - $this->arrayClean($definition); - $sql .= "'" . $definition[0] . "'"; - if ($definition[1]) { - $sql .= ",'" . $definition[1] . "'"; - } - } else { - $this->clean($definition); - $sql .= "'" . $definition . "'"; - } - - $sql .= " LANGUAGE \"{$language}\""; - - // Add flags - foreach ($flags as $v) { - // Skip default flags - if ($v == '') continue; - else $sql .= "\n{$v}"; - } - - return $this->execute($sql); - } - - /** - * Drops a function. - * @param $function_oid The OID of the function to drop - * @param $cascade True to cascade drop, false to restrict + * @param $setof True if returns a set, false otherwise + * @param $comment The comment on the function * @return 0 success + * @return -1 transaction error + * @return -2 drop function error + * @return -3 create function error + * @return -4 comment error */ - function dropFunction($function_oid, $cascade) { - // Function comes in with $object as function OID - $fn = $this->getFunction($function_oid); - $this->fieldClean($fn->fields['proname']); - - $sql = "DROP FUNCTION \"{$this->_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})"; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } - - // Type functions - - /** - * Returns a list of all types in the database - * @param $all If true, will find all available functions, if false just those in search path - * @param $tabletypes If true, will include table types - * @param $domains If true, will include domains - * @return A recordet - */ - function getTypes($all = false, $tabletypes = false, $domains = false) { - if ($all) - $where = '1 = 1'; - else - $where = "n.nspname = '{$this->_schema}'"; - // Never show system table types - $where2 = "AND c.relnamespace NOT IN (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname LIKE 'pg@_%' ESCAPE '@')"; - - // Create type filter - $tqry = "'c'"; - if ($tabletypes) - $tqry .= ", 'r', 'v'"; - - // Create domain filter - if (!$domains) - $where .= " AND t.typtype != 'd'"; - - $sql = "SELECT - t.typname AS basename, - pg_catalog.format_type(t.oid, NULL) AS typname, - pu.usename AS typowner, - t.typtype, - pg_catalog.obj_description(t.oid, 'pg_type') AS typcomment - FROM (pg_catalog.pg_type t - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace) - LEFT JOIN pg_catalog.pg_user pu ON t.typowner = pu.usesysid - WHERE (t.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid {$where2})) - AND t.typname !~ '^_' - AND {$where} - ORDER BY typname - "; - - return $this->selectSet($sql); - } - - /** - * Drops a type. - * @param $typname The name of the type to drop - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropType($typname, $cascade) { - $this->fieldClean($typname); - - $sql = "DROP TYPE \"{$this->_schema}\".\"{$typname}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } - - /** - * Creates a new composite type in the database - * @param $name The name of the type - * @param $fields The number of fields - * @param $field An array of field names - * @param $type An array of field types - * @param $array An array of '' or '[]' for each type if it's an array or not - * @param $length An array of field lengths - * @param $colcomment An array of comments - * @param $typcomment Type comment - * @return 0 success - * @return -1 no fields supplied - */ - function createCompositeType($name, $fields, $field, $type, $array, $length, $colcomment, $typcomment) { - $this->fieldClean($name); - $this->clean($typcomment); - + function setFunction($function_oid, $funcname, $newname, $args, $returns, $definition, $language, $flags, $setof, $rows, $cost, $comment) { + // Begin a transaction $status = $this->beginTransaction(); - if ($status != 0) return -1; - - $found = false; - $first = true; - $comment_sql = ''; // Accumulate comments for the columns - $sql = "CREATE TYPE \"{$this->_schema}\".\"{$name}\" AS ("; - for ($i = 0; $i < $fields; $i++) { - $this->fieldClean($field[$i]); - $this->clean($type[$i]); - $this->clean($length[$i]); - $this->clean($colcomment[$i]); - - // Skip blank columns - for user convenience - if ($field[$i] == '' || $type[$i] == '') continue; - // If not the first column, add a comma - if (!$first) $sql .= ", "; - else $first = false; - - switch ($type[$i]) { - // Have to account for weird placing of length for with/without - // time zone types - case 'timestamp with time zone': - case 'timestamp without time zone': - $qual = substr($type[$i], 9); - $sql .= "\"{$field[$i]}\" timestamp"; - if ($length[$i] != '') $sql .= "({$length[$i]})"; - $sql .= $qual; - break; - case 'time with time zone': - case 'time without time zone': - $qual = substr($type[$i], 4); - $sql .= "\"{$field[$i]}\" time"; - if ($length[$i] != '') $sql .= "({$length[$i]})"; - $sql .= $qual; - break; - default: - $sql .= "\"{$field[$i]}\" {$type[$i]}"; - if ($length[$i] != '') $sql .= "({$length[$i]})"; - } - // Add array qualifier if necessary - if ($array[$i] == '[]') $sql .= '[]'; - - if ($colcomment[$i] != '') $comment_sql .= "COMMENT ON COLUMN \"{$this->_schema}\".\"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n"; - - $found = true; - } - - if (!$found) return -1; - - $sql .= ")"; - - $status = $this->execute($sql); - if ($status) { + if ($status != 0) { $this->rollbackTransaction(); return -1; } - if ($typcomment != '') { - $status = $this->setComment('TYPE', $name, '', $typcomment, true); - if ($status) { - $this->rollbackTransaction(); - return -1; - } + // Replace the existing function + if ($funcname != $newname) { + $status = $this->dropFunction($function_oid, false); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; } - if ($comment_sql != '') { - $status = $this->execute($comment_sql); - if ($status) { + $status = $this->createFunction($newname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, false); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } + } else { + $status = $this->createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, true); + if ($status != 0) { $this->rollbackTransaction(); - return -1; - } + return -3; } - return $this->endTransaction(); - - } - - // Rule functions - - /** - * Removes a rule from a table OR view - * @param $rule The rule to drop - * @param $relation The relation from which to drop - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropRule($rule, $relation, $cascade) { - $this->fieldClean($rule); - $this->fieldClean($relation); - - $sql = "DROP RULE \"{$rule}\" ON \"{$this->_schema}\".\"{$relation}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); } - /** - * Returns a list of all rules on a table OR view - * @param $table The table to find rules for - * @return A recordset - */ - function getRules($table) { - $this->clean($table); - - $sql = "SELECT - * - FROM - pg_catalog.pg_rules - WHERE - schemaname='{$this->_schema}' - AND tablename='{$table}' - ORDER BY - rulename - "; - - return $this->selectSet($sql); - } + // Comment on the function + $this->fieldClean($newname); + $this->clean($comment); + $status = $this->setComment('FUNCTION', "\"{$newname}\"({$args})", null, $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; + } - /** - * Edits a rule on a table OR view - * @param $name The name of the new rule - * @param $event SELECT, INSERT, UPDATE or DELETE - * @param $table Table on which to create the rule - * @param $where When to execute the rule, '' indicates always - * @param $instead True if an INSTEAD rule, false otherwise - * @param $type NOTHING for a do nothing rule, SOMETHING to use given action - * @param $action The action to take - * @return 0 success - * @return -1 invalid event - */ - function setRule($name, $event, $table, $where, $instead, $type, $action) { - return $this->createRule($name, $event, $table, $where, $instead, $type, $action, true); + return $this->endTransaction(); } - // Constraint functions + // Constraint functions /** * Returns a list of all constraints on a table @@ -1752,7 +332,7 @@ class Postgres73 extends Postgres72 { * primary key constraints. */ $sql = " SELECT conname, consrc, contype, indkey, indisclustered FROM ( - SELECT + SELECT conname, CASE WHEN contype='f' THEN pg_catalog.pg_get_constraintdef(oid) @@ -1763,12 +343,12 @@ class Postgres73 extends Postgres72 { conrelid AS relid, NULL AS indkey, FALSE AS indisclustered - FROM + FROM pg_catalog.pg_constraint - WHERE + WHERE contype IN ('f', 'c') UNION ALL - SELECT + SELECT pc.relname, NULL, CASE WHEN indisprimary THEN @@ -1779,10 +359,10 @@ class Postgres73 extends Postgres72 { pi.indrelid, indkey, pi.indisclustered - FROM + FROM pg_catalog.pg_class pc, pg_catalog.pg_index pi - WHERE + WHERE pc.oid=pi.indexrelid AND EXISTS ( SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c @@ -1800,13 +380,13 @@ class Postgres73 extends Postgres72 { return $this->selectSet($sql); } - /** + /** * Returns a list of all constraints on a table, * including constraint name, definition, related col and referenced namespace, * table and col if needed * @param $table the table where we are looking for fk * @return a recordset - */ + */ function getConstraintsWithFields($table) { global $data; @@ -1816,9 +396,9 @@ class Postgres73 extends Postgres72 { $sql = "SELECT DISTINCT max(SUBSTRING(array_dims(c.conkey) FROM '^\\\[.*:(.*)\\\]$')) as nb FROM - pg_catalog.pg_constraint AS c - JOIN pg_catalog.pg_class AS r ON (c.conrelid = r.oid) - JOIN pg_catalog.pg_namespace AS ns ON r.relnamespace=ns.oid + pg_catalog.pg_constraint AS c + JOIN pg_catalog.pg_class AS r ON (c.conrelid = r.oid) + JOIN pg_catalog.pg_namespace AS ns ON r.relnamespace=ns.oid WHERE r.relname = '$table' AND ns.nspname='". $this->_schema ."'"; @@ -1830,9 +410,9 @@ class Postgres73 extends Postgres72 { // get the max number of col used in a constraint for the table $sql = "SELECT i.indkey FROM - pg_catalog.pg_index AS i - JOIN pg_catalog.pg_class AS r ON (i.indrelid = r.oid) - JOIN pg_catalog.pg_namespace AS ns ON r.relnamespace=ns.oid + pg_catalog.pg_index AS i + JOIN pg_catalog.pg_class AS r ON (i.indrelid = r.oid) + JOIN pg_catalog.pg_namespace AS ns ON r.relnamespace=ns.oid WHERE r.relname = '$table' AND ns.nspname='". $this->_schema ."'"; @@ -1844,18 +424,18 @@ class Postgres73 extends Postgres72 { $tmp = count(explode(' ', $rs->fields['indkey'])); $max_col_ind = $tmp > $max_col_ind ? $tmp : $max_col_ind; $rs->MoveNext(); - } + } $sql = " SELECT contype, conname, consrc, ns1.nspname as p_schema, sub.relname as p_table, - f_schema, f_table, p_field, f_field, indkey + f_schema, f_table, p_field, f_field, indkey FROM ( - SELECT + SELECT contype, conname, CASE WHEN contype='f' THEN - pg_catalog.pg_get_constraintdef(c.oid) + pg_catalog.pg_get_constraintdef(c.oid) ELSE - 'CHECK (' || consrc || ')' + 'CHECK (' || consrc || ')' END AS consrc, r1.relname, f1.attname as p_field, ns2.nspname as f_schema, r2.relname as f_table, conrelid, r1.relnamespace, f2.attname as f_field, NULL AS indkey @@ -1865,774 +445,97 @@ class Postgres73 extends Postgres72 { JOIN pg_catalog.pg_attribute AS f1 ON ((f1.attrelid=c.conrelid) AND (f1.attnum=c.conkey[1]"; for ($i = 2; $i <= $max_col_cstr; $i++) { $sql.= " OR f1.attnum=c.conkey[$i]"; - } + } $sql .= ")) LEFT JOIN ( - pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid) + pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid) ) ON (c.confrelid=r2.oid) LEFT JOIN pg_catalog.pg_attribute AS f2 ON - ((f2.attrelid=r2.oid) AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)"; + ((f2.attrelid=r2.oid) AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)"; for ($i = 2; $i <= $max_col_cstr; $i++) $sql.= "OR (c.confkey[$i]=f2.attnum AND c.conkey[$i]=f1.attnum)"; $sql .= ")) - WHERE - contype IN ('f', 'c') - UNION ALL - SELECT - CASE WHEN indisprimary THEN - 'p' - ELSE - 'u' - END as contype, - pc.relname as conname, NULL as consrc, r2.relname, f1.attname as p_field, - NULL as f_schema, NULL as f_table, indrelid as conrelid, pc.relnamespace, - NULL as f_field, indkey - FROM - pg_catalog.pg_class pc, pg_catalog.pg_index pi - JOIN pg_catalog.pg_attribute AS f1 ON ((f1.attrelid=pi.indrelid) AND (f1.attnum=pi.indkey[0]"; + WHERE + contype IN ('f', 'c') + UNION ALL + SELECT + CASE WHEN indisprimary THEN + 'p' + ELSE + 'u' + END as contype, + pc.relname as conname, NULL as consrc, r2.relname, f1.attname as p_field, + NULL as f_schema, NULL as f_table, indrelid as conrelid, pc.relnamespace, + NULL as f_field, indkey + FROM + pg_catalog.pg_class pc, pg_catalog.pg_index pi + JOIN pg_catalog.pg_attribute AS f1 ON ((f1.attrelid=pi.indrelid) AND (f1.attnum=pi.indkey[0]"; for ($i = 1; $i <= $max_col_ind; $i++) { $sql.= " OR f1.attnum=pi.indkey[$i]"; - } + } $sql .= ")) - JOIN pg_catalog.pg_class r2 ON (pi.indrelid=r2.oid) - WHERE - pc.oid=pi.indexrelid - AND EXISTS ( - SELECT 1 FROM pg_catalog.pg_depend AS d JOIN pg_catalog.pg_constraint AS c - ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) - WHERE d.classid = pc.tableoid AND d.objid = pc.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p') - ) + JOIN pg_catalog.pg_class r2 ON (pi.indrelid=r2.oid) + WHERE + pc.oid=pi.indexrelid + AND EXISTS ( + SELECT 1 + FROM pg_catalog.pg_depend AS d + JOIN pg_catalog.pg_constraint AS c + ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) + WHERE d.classid = pc.tableoid AND d.objid = pc.oid + AND d.deptype = 'i' AND c.contype IN ('u', 'p') + ) ) AS sub - JOIN pg_catalog.pg_namespace AS ns1 ON sub.relnamespace=ns1.oid + JOIN pg_catalog.pg_namespace AS ns1 ON sub.relnamespace=ns1.oid WHERE conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace - WHERE nspname='{$this->_schema}')) + AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace + WHERE nspname='{$this->_schema}')) ORDER BY 1 "; return $this->selectSet($sql); } - /** - * A function for getting all columns linked by foreign keys given a group of tables - * @param $tables multi dimensional assoc array that holds schema and table name - * @return A recordset of linked tables and columns - * @return -1 $tables isn't an array - */ - function getLinkingKeys($tables) { - if (!is_array($tables)) return -1; - - - $tables_list = "'{$tables[0]['tablename']}'"; - $schema_list = "'{$tables[0]['schemaname']}'"; - $schema_tables_list = "'{$tables[0]['schemaname']}.{$tables[0]['tablename']}'"; - for ($i = 1; $i < sizeof($tables); $i++) { - $tables_list .= ", '{$tables[$i]['tablename']}'"; - $schema_list .= ", '{$tables[$i]['schemaname']}'"; - $schema_tables_list .= ", '{$tables[$i]['schemaname']}.{$tables[$i]['tablename']}'"; - } - $maxDimension = 1; - - $sql = " - SELECT DISTINCT - array_dims(pc.conkey) AS arr_dim, - pgc1.relname AS p_table - FROM - pg_catalog.pg_constraint AS pc, - pg_catalog.pg_class AS pgc1 - WHERE - pc.contype = 'f' - AND (pc.conrelid = pgc1.relfilenode OR pc.confrelid = pgc1.relfilenode) - AND pgc1.relname IN ($tables_list) - "; - - //parse our output to find the highest dimension of foreign keys since pc.conkey is stored in an array - $rs = $this->selectSet($sql); - while (!$rs->EOF) { - $arrData = explode(':', $rs->fields['arr_dim']); - $tmpDimension = intval(substr($arrData[1], 0, strlen($arrData[1] - 1))); - $maxDimension = $tmpDimension > $maxDimension ? $tmpDimension : $maxDimension; - $rs->MoveNext(); - } - - //we know the highest index for foreign keys that conkey goes up to, expand for us in an IN query - $cons_str = '( (pfield.attnum = conkey[1] AND cfield.attnum = confkey[1]) '; - for ($i = 2; $i <= $maxDimension; $i++) { - $cons_str .= "OR (pfield.attnum = conkey[{$i}] AND cfield.attnum = confkey[{$i}]) "; - } - $cons_str .= ') '; - - $sql = " - SELECT - pgc1.relname AS p_table, - pgc2.relname AS f_table, - pfield.attname AS p_field, - cfield.attname AS f_field, - pgns1.nspname AS p_schema, - pgns2.nspname AS f_schema - FROM - pg_catalog.pg_constraint AS pc, - pg_catalog.pg_class AS pgc1, - pg_catalog.pg_class AS pgc2, - pg_catalog.pg_attribute AS pfield, - pg_catalog.pg_attribute AS cfield, - (SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN ($schema_list) ) AS pgns1, - (SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN ($schema_list) ) AS pgns2 - WHERE - pc.contype = 'f' - AND pgc1.relnamespace = pgns1.ns_id - AND pgc2.relnamespace = pgns2.ns_id - AND pc.conrelid = pgc1.relfilenode - AND pc.confrelid = pgc2.relfilenode - AND pfield.attrelid = pc.conrelid - AND cfield.attrelid = pc.confrelid - AND $cons_str - AND pgns1.nspname || '.' || pgc1.relname IN ($schema_tables_list) - AND pgns2.nspname || '.' || pgc2.relname IN ($schema_tables_list) - "; - return $this->selectSet($sql); - } + // Misc functions /** - * Removes a constraint from a relation - * @param $constraint The constraint to drop - * @param $relation The relation from which to drop - * @param $type The type of constraint (c, f, u or p) - * @param $cascade True to cascade drop, false to restrict + * Sets up the data object for a dump. eg. Starts the appropriate + * transaction, sets variables, etc. * @return 0 success */ - function dropConstraint($constraint, $relation, $type, $cascade) { - $this->fieldClean($constraint); - $this->fieldClean($relation); - - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$relation}\" DROP CONSTRAINT \"{$constraint}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } - - /** - * Finds the foreign keys that refer to the specified table - * @param $table The table to find referrers for - * @return A recordset - */ - function getReferrers($table) { - $this->clean($table); - + function beginDump() { + // Begin serializable transaction (to dump consistent data) $status = $this->beginTransaction(); if ($status != 0) return -1; - $sql = " - SELECT - pn.nspname, - pl.relname, - pc.conname, - pg_catalog.pg_get_constraintdef(pc.oid) AS consrc - FROM - pg_catalog.pg_constraint pc, - pg_catalog.pg_namespace pn, - pg_catalog.pg_class pl - WHERE - pc.connamespace = pn.oid - AND pc.conrelid = pl.oid - AND pc.contype = 'f' - AND confrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace - WHERE nspname='{$this->_schema}')) - ORDER BY 1,2,3 - "; - - return $this->selectSet($sql); - } - - // Privilege functions - - /** - * Grabs an array of users and their privileges for an object, - * given its type. - * @param $object The name of the object whose privileges are to be retrieved - * @param $type The type of the object (eg. database, schema, relation, function or language) - * @return Privileges array - * @return -1 invalid type - * @return -2 object not found - * @return -3 unknown privilege type - */ - function getPrivileges($object, $type) { - $this->clean($object); - - switch ($type) { - case 'table': - case 'view': - case 'sequence': - $sql = "SELECT relacl AS acl FROM pg_catalog.pg_class WHERE relname='{$object}' - AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}')"; - break; - case 'database': - $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'"; - break; - case 'function': - // Since we fetch functions by oid, they are already constrained to - // the current schema. - $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'"; - break; - case 'language': - $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'"; - break; - case 'schema': - $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'"; - break; - case 'tablespace': - $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'"; - break; - default: - return -1; - } - - // Fetch the ACL for object - $acl = $this->selectField($sql, 'acl'); - if ($acl == -1) return -2; - elseif ($acl == '' || $acl == null) return array(); - else return $this->_parseACL($acl); - } - - // Domain functions - - /** - * Gets all information for a single domain - * @param $domain The name of the domain to fetch - * @return A recordset - */ - function getDomain($domain) { - $this->clean($domain); - - $sql = " - SELECT - t.typname AS domname, - pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype, - t.typnotnull AS domnotnull, - t.typdefault AS domdef, - pg_catalog.pg_get_userbyid(t.typowner) AS domowner, - pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment - FROM - pg_catalog.pg_type t - WHERE - t.typtype = 'd' - AND t.typname = '{$domain}' - AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace - WHERE nspname = '{$this->_schema}')"; - - return $this->selectSet($sql); - } - - /** - * Return all domains in current schema. Excludes domain constraints. - * @return All tables, sorted alphabetically - */ - function getDomains() { - $sql = " - SELECT - t.typname AS domname, - pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype, - t.typnotnull AS domnotnull, - t.typdefault AS domdef, - pg_catalog.pg_get_userbyid(t.typowner) AS domowner, - pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment - FROM - pg_catalog.pg_type t - WHERE - t.typtype = 'd' - AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace - WHERE nspname='{$this->_schema}') - ORDER BY t.typname"; - - return $this->selectSet($sql); - } - - /** - * Creates a domain - * @param $domain The name of the domain to create - * @param $type The base type for the domain - * @param $length Optional type length - * @param $array True for array type, false otherwise - * @param $notnull True for NOT NULL, false otherwise - * @param $default Default value for domain - * @param $check A CHECK constraint if there is one - * @return 0 success - */ - function createDomain($domain, $type, $length, $array, $notnull, $default, $check) { - $this->fieldClean($domain); - - $sql = "CREATE DOMAIN \"{$this->_schema}\".\"{$domain}\" AS "; - - if ($length == '') - $sql .= $type; - else { - switch ($type) { - // Have to account for weird placing of length for with/without - // time zone types - case 'timestamp with time zone': - case 'timestamp without time zone': - $qual = substr($type, 9); - $sql .= "timestamp({$length}){$qual}"; - break; - case 'time with time zone': - case 'time without time zone': - $qual = substr($type, 4); - $sql .= "time({$length}){$qual}"; - break; - default: - $sql .= "{$type}({$length})"; - } - } - - // Add array qualifier, if requested - if ($array) $sql .= '[]'; - - if ($notnull) $sql .= ' NOT NULL'; - if ($default != '') $sql .= " DEFAULT {$default}"; - if ($this->hasDomainConstraints() && $check != '') $sql .= " CHECK ({$check})"; - - return $this->execute($sql); - } - - /** - * Drops a domain. - * @param $domain The name of the domain to drop - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropDomain($domain, $cascade) { - $this->fieldClean($domain); - - $sql = "DROP DOMAIN \"{$this->_schema}\".\"{$domain}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); + // Set serializable + $sql = "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; } - // Find object functions - - /** - * Searches all system catalogs to find objects that match a certain name. - * @param $term The search term - * @param $filter The object type to restrict to ('' means no restriction) - * @return A recordset - */ - function findObject($term, $filter) { - global $conf; - - // Escape search term for ILIKE match - $term = str_replace('_', '\\_', $term); - $term = str_replace('%', '\\%', $term); - $this->clean($term); - $this->clean($filter); - - // Exclude system relations if necessary - if (!$conf['show_system']) { - // XXX: The mention of information_schema here is in the wrong place, but - // it's the quickest fix to exclude the info schema from 7.4 - $where = " AND pn.nspname NOT LIKE 'pg\\\\_%' AND pn.nspname != 'information_schema'"; - $lan_where = "AND pl.lanispl"; - } - else { - $where = ''; - $lan_where = ''; - } - - // Apply outer filter - $sql = ''; - if ($filter != '') { - $sql = "SELECT * FROM ("; - } - - $sql .= " - SELECT 'SCHEMA' AS type, oid, NULL AS schemaname, NULL AS relname, nspname AS name - FROM pg_catalog.pg_namespace pn WHERE nspname ILIKE '%{$term}%' {$where} - UNION ALL - SELECT CASE WHEN relkind='r' THEN 'TABLE' WHEN relkind='v' THEN 'VIEW' WHEN relkind='S' THEN 'SEQUENCE' END, pc.oid, - pn.nspname, NULL, pc.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn - WHERE pc.relnamespace=pn.oid AND relkind IN ('r', 'v', 'S') AND relname ILIKE '%{$term}%' {$where} - UNION ALL - SELECT CASE WHEN pc.relkind='r' THEN 'COLUMNTABLE' ELSE 'COLUMNVIEW' END, NULL, pn.nspname, pc.relname, pa.attname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, - pg_catalog.pg_attribute pa WHERE pc.relnamespace=pn.oid AND pc.oid=pa.attrelid - AND pa.attname ILIKE '%{$term}%' AND pa.attnum > 0 AND NOT pa.attisdropped AND pc.relkind IN ('r', 'v') {$where} - UNION ALL - SELECT 'FUNCTION', pp.oid, pn.nspname, NULL, pp.proname || '(' || pg_catalog.oidvectortypes(pp.proargtypes) || ')' FROM pg_catalog.pg_proc pp, pg_catalog.pg_namespace pn - WHERE pp.pronamespace=pn.oid AND NOT pp.proisagg AND pp.proname ILIKE '%{$term}%' {$where} - UNION ALL - SELECT 'INDEX', NULL, pn.nspname, pc.relname, pc2.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, - pg_catalog.pg_index pi, pg_catalog.pg_class pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pi.indrelid - AND pi.indexrelid=pc2.oid - AND NOT EXISTS ( - SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c - ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) - WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p') - ) - AND pc2.relname ILIKE '%{$term}%' {$where} - UNION ALL - SELECT 'CONSTRAINTTABLE', NULL, pn.nspname, pc.relname, pc2.conname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, - pg_catalog.pg_constraint pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pc2.conrelid AND pc2.conrelid != 0 - AND CASE WHEN pc2.contype IN ('f', 'c') THEN TRUE ELSE NOT EXISTS ( - SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c - ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) - WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p') - ) END - AND pc2.conname ILIKE '%{$term}%' {$where} - UNION ALL - SELECT 'CONSTRAINTDOMAIN', pt.oid, pn.nspname, pt.typname, pc.conname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn, - pg_catalog.pg_constraint pc WHERE pt.typnamespace=pn.oid AND pt.oid=pc.contypid AND pc.contypid != 0 - AND pc.conname ILIKE '%{$term}%' {$where} - UNION ALL - SELECT 'TRIGGER', NULL, pn.nspname, pc.relname, pt.tgname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, - pg_catalog.pg_trigger pt WHERE pc.relnamespace=pn.oid AND pc.oid=pt.tgrelid - AND (NOT pt.tgisconstraint OR NOT EXISTS - (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c - ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) - WHERE d.classid = pt.tableoid AND d.objid = pt.oid AND d.deptype = 'i' AND c.contype = 'f')) - AND pt.tgname ILIKE '%{$term}%' {$where} - UNION ALL - SELECT 'RULETABLE', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r - JOIN pg_catalog.pg_class c ON c.oid = r.ev_class - LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace - WHERE c.relkind='r' AND r.rulename != '_RETURN' AND r.rulename ILIKE '%{$term}%' {$where} - UNION ALL - SELECT 'RULEVIEW', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r - JOIN pg_catalog.pg_class c ON c.oid = r.ev_class - LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace - WHERE c.relkind='v' AND r.rulename != '_RETURN' AND r.rulename ILIKE '%{$term}%' {$where} - "; - - // Add advanced objects if show_advanced is set - if ($conf['show_advanced']) { - $sql .= " - UNION ALL - SELECT CASE WHEN pt.typtype='d' THEN 'DOMAIN' ELSE 'TYPE' END, pt.oid, pn.nspname, NULL, - pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn - WHERE pt.typnamespace=pn.oid AND typname ILIKE '%{$term}%' - AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid)) - {$where} - UNION ALL - SELECT 'OPERATOR', po.oid, pn.nspname, NULL, po.oprname FROM pg_catalog.pg_operator po, pg_catalog.pg_namespace pn - WHERE po.oprnamespace=pn.oid AND oprname ILIKE '%{$term}%' {$where} - UNION ALL - SELECT 'CONVERSION', pc.oid, pn.nspname, NULL, pc.conname FROM pg_catalog.pg_conversion pc, - pg_catalog.pg_namespace pn WHERE pc.connamespace=pn.oid AND conname ILIKE '%{$term}%' {$where} - UNION ALL - SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_catalog.pg_language pl - WHERE lanname ILIKE '%{$term}%' {$lan_where} - UNION ALL - SELECT DISTINCT ON (p.proname) 'AGGREGATE', p.oid, pn.nspname, NULL, p.proname FROM pg_catalog.pg_proc p - LEFT JOIN pg_catalog.pg_namespace pn ON p.pronamespace=pn.oid - WHERE p.proisagg AND p.proname ILIKE '%{$term}%' {$where} - UNION ALL - SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, pn.nspname, NULL, po.opcname FROM pg_catalog.pg_opclass po, - pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid - AND po.opcname ILIKE '%{$term}%' {$where} - "; - } - // Otherwise just add domains - else { - $sql .= " - UNION ALL - SELECT 'DOMAIN', pt.oid, pn.nspname, NULL, - pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn - WHERE pt.typnamespace=pn.oid AND pt.typtype='d' AND typname ILIKE '%{$term}%' - AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid)) - {$where} - "; - } - - if ($filter != '') { - // We use like to make RULE, CONSTRAINT and COLUMN searches work - $sql .= ") AS sub WHERE type LIKE '{$filter}%' "; + // Set datestyle to ISO + $sql = "SET DATESTYLE = ISO"; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; } - - $sql .= "ORDER BY type, schemaname, relname, name"; - - return $this->selectSet($sql); - } - - // Operator functions - - /** - * Returns a list of all operators in the database - * @return All operators - */ - function getOperators() { - // We stick with the subselects here, as you cannot ORDER BY a regtype - $sql = " - SELECT - po.oid, - po.oprname, - (SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname, - (SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname, - po.oprresult::pg_catalog.regtype AS resultname, - pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment - FROM - pg_catalog.pg_operator po - WHERE - po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}') - ORDER BY - po.oprname, oprleftname, oprrightname - "; - - return $this->selectSet($sql); - } - - /** - * Returns all details for a particular operator - * @param $operator_oid The oid of the operator - * @return Function info - */ - function getOperator($operator_oid) { - $this->clean($operator_oid); - - $sql = " - SELECT - po.oid, - po.oprname, - oprleft::pg_catalog.regtype AS oprleftname, - oprright::pg_catalog.regtype AS oprrightname, - oprresult::pg_catalog.regtype AS resultname, - po.oprcanhash, - oprcom::pg_catalog.regoperator AS oprcom, - oprnegate::pg_catalog.regoperator AS oprnegate, - oprlsortop::pg_catalog.regoperator AS oprlsortop, - oprrsortop::pg_catalog.regoperator AS oprrsortop, - oprltcmpop::pg_catalog.regoperator AS oprltcmpop, - oprgtcmpop::pg_catalog.regoperator AS oprgtcmpop, - po.oprcode::pg_catalog.regproc AS oprcode, - po.oprrest::pg_catalog.regproc AS oprrest, - po.oprjoin::pg_catalog.regproc AS oprjoin - FROM - pg_catalog.pg_operator po - WHERE - po.oid='{$operator_oid}' - "; - - return $this->selectSet($sql); - } - - // Cast functions - - /** - * Returns a list of all casts in the database - * @return All casts - */ - function getCasts() { - global $conf; - - if ($conf['show_system']) - $where = ''; - else - $where = " - AND n1.nspname NOT LIKE 'pg\\\\_%' - AND n2.nspname NOT LIKE 'pg\\\\_%' - AND n3.nspname NOT LIKE 'pg\\\\_%' - "; - - $sql = " - SELECT - c.castsource::pg_catalog.regtype AS castsource, - c.casttarget::pg_catalog.regtype AS casttarget, - CASE WHEN c.castfunc=0 THEN NULL - ELSE c.castfunc::pg_catalog.regprocedure END AS castfunc, - c.castcontext, - obj_description(c.oid, 'pg_cast') as castcomment - FROM - (pg_catalog.pg_cast c LEFT JOIN pg_catalog.pg_proc p ON c.castfunc=p.oid JOIN pg_catalog.pg_namespace n3 ON p.pronamespace=n3.oid), - pg_catalog.pg_type t1, - pg_catalog.pg_type t2, - pg_catalog.pg_namespace n1, - pg_catalog.pg_namespace n2 - WHERE - c.castsource=t1.oid - AND c.casttarget=t2.oid - AND t1.typnamespace=n1.oid - AND t2.typnamespace=n2.oid - {$where} - ORDER BY 1, 2 - "; - - return $this->selectSet($sql); - } - - // Conversion functions - - /** - * Returns a list of all conversions in the database - * @return All conversions - */ - function getConversions() { - $sql = " - SELECT - c.conname, - pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding, - pg_catalog.pg_encoding_to_char(c.contoencoding) AS contoencoding, - c.condefault, - pg_catalog.obj_description(c.oid, 'pg_conversion') AS concomment - FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n - WHERE n.oid = c.connamespace - AND n.nspname='{$this->_schema}' - ORDER BY 1; - "; - - return $this->selectSet($sql); - } - - // Language functions - - /** - * Gets all languages - * @param $all True to get all languages, regardless of show_system - * @return A recordset - */ - function getLanguages($all = false) { - global $conf; - - if ($conf['show_system'] || $all) - $where = ''; - else - $where = 'WHERE lanispl'; - - $sql = " - SELECT - lanname, - lanpltrusted, - lanplcallfoid::pg_catalog.regproc AS lanplcallf - FROM - pg_catalog.pg_language - {$where} - ORDER BY - lanname - "; - - return $this->selectSet($sql); - } - - // Aggregate functions - - /** - * Gets all information for an aggregate - * @param $name The name of the aggregate - * @param $basetype The input data type of the aggregate - * @return A recordset - */ - function getAggregate($name, $basetype) { - $this->fieldclean($name); - $this->fieldclean($basetype); - - $sql = " - SELECT p.proname, - CASE p.proargtypes[0] - WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL - ELSE pg_catalog.format_type(p.proargtypes[0], NULL) - END AS proargtypes, a.aggtransfn, format_type(a.aggtranstype, NULL) AS aggstype, - a.aggfinalfn, a.agginitval, u.usename, pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment - FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a - WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid - AND p.proisagg AND n.nspname='{$this->_schema}' - AND p.proname='" . $name . "' - AND CASE p.proargtypes[0] - WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN '' - ELSE pg_catalog.format_type(p.proargtypes[0], NULL) - END ='" . $basetype . "'"; - - return $this->selectSet($sql); - } - - /** - * Gets all aggregates - * @return A recordset - */ - function getAggregates() { - $sql = "SELECT p.proname, CASE p.proargtypes[0] WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL ELSE - pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes, a.aggtransfn, u.usename, - pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment - FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a - WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid - AND p.proisagg AND n.nspname='{$this->_schema}' ORDER BY 1, 2"; - - return $this->selectSet($sql); - } - - // Operator Class functions - - /** - * Gets all opclasses - * @return A recordset - */ - function getOpClasses() { - $sql = " - SELECT - pa.amname, - po.opcname, - po.opcintype::pg_catalog.regtype AS opcintype, - po.opcdefault, - pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment - FROM - pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn - WHERE - po.opcamid=pa.oid - AND po.opcnamespace=pn.oid - AND pn.nspname='{$this->_schema}' - ORDER BY 1,2 - "; - - return $this->selectSet($sql); - } - - /** - * Removes an aggregate function from the database - * @param $aggrname The name of the aggregate - * @param $aggrtype The input data type of the aggregate - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropAggregate($aggrname, $aggrtype, $cascade) { - $this->fieldClean($aggrname); - $this->fieldClean($aggrtype); - - $sql = "DROP AGGREGATE \"{$this->_schema}\".\"{$aggrname}\" (\"{$aggrtype}\")"; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } - - // Query functions - - /** - * Returns explained version of a query - * @param $query The query for which to get data - * @param $analyze True to analyze as well - */ - function getExplainSQL($query, $analyze) { - $temp = "EXPLAIN "; - if ($analyze) $temp .= "ANALYZE "; - $temp .= $query; - return $temp; } // Capabilities - function hasSchemas() { return true; } - function hasConversions() { return true; } - function hasIsClustered() { return true; } - function hasDropBehavior() { return true; } - function hasDropColumn() { return true; } - function hasDomains() { return true; } - function hasAlterTrigger() { return true; } - function hasCasts() { return true; } - function hasPrepare() { return true; } - function hasUserSessionDefaults() { return true; } - function hasVariables() { return true; } -// function hasForeignKeysInfo() { return true; } - function hasConstraintsInfo() { return true; } - function hasViewColumnRename() { return true; } - function hasUserAndDbVariables() { return true; } - function hasCompositeTypes() { return true; } - function hasFuncPrivs() { return true; } - function hasLocksView() { return true; } + function hasAlterAggregate() { return false; } + function hasAlterDatabaseRename() { return false; } + function hasAlterSequenceProps() { return false; } + function hasCreateTableLike() {return false;} + function hasDomainConstraints() { return false; } + function hasGrantOption() { return false; } + function hasReadOnlyQueries() { return false; } + function hasRecluster() { return false; } + function hasUserRename() { return false; } } ?> diff --git a/classes/database/Postgres74.php b/classes/database/Postgres74.php index 1502c21c..3fe7739f 100644 --- a/classes/database/Postgres74.php +++ b/classes/database/Postgres74.php @@ -7,27 +7,30 @@ * $Id: Postgres74.php,v 1.72 2008/02/20 21:06:18 ioguix Exp $ */ -include_once('./classes/database/Postgres73.php'); +include_once('./classes/database/Postgres80.php'); -class Postgres74 extends Postgres73 { +class Postgres74 extends Postgres80 { var $major_version = 7.4; + // List of all legal privileges that can be applied to different types + // of objects. + var $privlist = array( + 'table' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), + 'view' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), + 'sequence' => array('SELECT', 'UPDATE', 'ALL PRIVILEGES'), + 'database' => array('CREATE', 'TEMPORARY', 'ALL PRIVILEGES'), + 'function' => array('EXECUTE', 'ALL PRIVILEGES'), + 'language' => array('USAGE', 'ALL PRIVILEGES'), + 'schema' => array('CREATE', 'USAGE', 'ALL PRIVILEGES') + ); - // Last oid assigned to a system object - var $_lastSystemOID = 17137; - - // Max object name length - var $_maxNameLen = 63; - - // How often to execute the trigger - var $triggerFrequency = array('ROW','STATEMENT'); /** * Constructor * @param $conn The database connection */ function Postgres74($conn) { - $this->Postgres73($conn); + $this->Postgres80($conn); } // Help functions @@ -50,8 +53,7 @@ class Postgres74 extends Postgres73 { * @return -2 owner error * @return -3 rename error */ - function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') - { + function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') { //ignore $newowner, not supported pre 8.0 //ignore $comment, not supported pre 8.2 $this->clean($dbName); @@ -63,479 +65,143 @@ class Postgres74 extends Postgres73 { } /** - * Renames a database, note that this operation cannot be - * performed on a database that is currently being connected to - * @param string $oldName name of database to rename - * @param string $newName new name of database - * @return int 0 on success - */ - function alterDatabaseRename($oldName, $newName) { - $this->clean($oldName); - $this->clean($newName); - - if ($oldName != $newName) { - $sql = "ALTER DATABASE \"{$oldName}\" RENAME TO \"{$newName}\""; - return $this->execute($sql); - } - else //just return success, we're not going to do anything - return 0; - } - - // Table functions - - /** - * Get the fields for uniquely identifying a row in a table - * @param $table The table for which to retrieve the identifier - * @return An array mapping attribute number to attribute name, empty for no identifiers - * @return -1 error - */ - function getRowIdentifier($table) { - $oldtable = $table; - $this->clean($table); - - $status = $this->beginTransaction(); - if ($status != 0) return -1; - - // Get the first primary or unique index (sorting primary keys first) that - // is NOT a partial index. - $sql = "SELECT indrelid, indkey FROM pg_catalog.pg_index WHERE indisunique AND - indrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND - relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}')) - AND indpred IS NULL AND indexprs IS NULL ORDER BY indisprimary DESC LIMIT 1"; - $rs = $this->selectSet($sql); - - // If none, check for an OID column. Even though OIDs can be duplicated, the edit and delete row - // functions check that they're only modiying a single row. Otherwise, return empty array. - if ($rs->recordCount() == 0) { - // Check for OID column - $temp = array(); - if ($this->hasObjectID($table)) { - $temp = array('oid'); - } - $this->endTransaction(); - return $temp; - } - // Otherwise find the names of the keys - else { - $attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey'])); - if (!is_array($attnames)) { - $this->rollbackTransaction(); - return -1; - } - else { - $this->endTransaction(); - return $attnames; - } - } - } - - /** - * Sets up the data object for a dump. eg. Starts the appropriate - * transaction, sets variables, etc. - * @return 0 success - */ - function beginDump() { - $status = parent::beginDump(); - if ($status != 0) return $status; - - // Set extra_float_digits to 2 - $sql = "SET extra_float_digits TO 2"; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - } - - // Constraint functions - - /** - * Returns a list of all constraints on a table - * @param $table The table to find rules for - * @return A recordset - */ - function getConstraints($table) { - $this->clean($table); - - // This SQL is greatly complicated by the need to retrieve - // index clustering information for primary and unique constraints - $sql = "SELECT - pc.conname, - pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc, - pc.contype, - CASE WHEN pc.contype='u' OR pc.contype='p' THEN ( - SELECT - indisclustered - FROM - pg_catalog.pg_depend pd, - pg_catalog.pg_class pl, - pg_catalog.pg_index pi - WHERE - pd.refclassid=pc.tableoid - AND pd.refobjid=pc.oid - AND pd.objid=pl.oid - AND pl.oid=pi.indexrelid - ) ELSE - NULL - END AS indisclustered - FROM - pg_catalog.pg_constraint pc - WHERE - pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace - WHERE nspname='{$this->_schema}')) - ORDER BY - 1 - "; - - return $this->selectSet($sql); - } - - /** - * Returns a list of all constraints on a table, - * including constraint name, definition, related col and referenced namespace, - * table and col if needed - * @param $table the table where we are looking for fk - * @return a recordset - */ - function getConstraintsWithFields($table) { - global $data; - - $data->clean($table); - - // get the max number of col used in a constraint for the table - $sql = "SELECT DISTINCT - max(SUBSTRING(array_dims(c.conkey) FROM '^\\\[.*:(.*)\\\]$')) as nb - FROM - pg_catalog.pg_constraint AS c - JOIN pg_catalog.pg_class AS r ON (c.conrelid = r.oid) - JOIN pg_catalog.pg_namespace AS ns ON r.relnamespace=ns.oid - WHERE - r.relname = '$table' AND ns.nspname='". $this->_schema ."'"; - - $rs = $this->selectSet($sql); - - if ($rs->EOF) $max_col = 0; - else $max_col = $rs->fields['nb']; - - $sql = ' - SELECT - c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid,true) AS consrc, - ns1.nspname as p_schema, r1.relname as p_table, ns2.nspname as f_schema, - r2.relname as f_table, f1.attname as p_field, f2.attname as f_field, - pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment - FROM - pg_catalog.pg_constraint AS c - JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid) - JOIN pg_catalog.pg_attribute AS f1 ON (f1.attrelid=r1.oid AND (f1.attnum=c.conkey[1]'; - for ($i = 2; $i <= $rs->fields['nb']; $i++) { - $sql.= " OR f1.attnum=c.conkey[$i]"; - } - $sql.= ')) - JOIN pg_catalog.pg_namespace AS ns1 ON r1.relnamespace=ns1.oid - LEFT JOIN ( - pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid) - ) ON (c.confrelid=r2.oid) - LEFT JOIN pg_catalog.pg_attribute AS f2 ON - (f2.attrelid=r2.oid AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)'; - for ($i = 2; $i <= $rs->fields['nb']; $i++) - $sql.= "OR (c.confkey[$i]=f2.attnum AND c.conkey[$i]=f1.attnum)"; - - $sql .= sprintf(")) - WHERE - r1.relname = '%s' AND ns1.nspname='%s' - ORDER BY 1", $table, $this->_schema); - - return $this->selectSet($sql); - } - - /** - * Creates a new table in the database copying attribs and other properties from another table - * @param $name The name of the table - * @param $like an array giving the schema ans the name of the table from which attribs are copying from: - * array( - * 'table' => table name, - * 'schema' => the schema name, - * ) - * @param $defaults if true, copy the defaults values as well - * @param $constraints if true, copy the constraints as well (CHECK on table & attr) - * @param $tablespace The tablespace name ('' means none/default) - */ - function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '') { - $this->fieldClean($name); - - $this->fieldClean($like['schema']); - $this->fieldClean($like['table']); - $like = "\"{$like['schema']}\".\"{$like['table']}\""; - - $status = $this->beginTransaction(); - if ($status != 0) return -1; - - $sql = "CREATE TABLE \"{$this->_schema}\".\"{$name}\" (LIKE {$like}"; - - if ($defaults) $sql .= " INCLUDING DEFAULTS"; - if ($this->hasCreateTableLikeWithConstraints() && $constraints) $sql .= " INCLUDING CONSTRAINTS"; - if ($this->hasCreateTableLikeWithIndexes() && $idx) $sql .= " INCLUDING INDEXES"; - - $sql .= ")"; - - if ($this->hasTablespaces() && $tablespace != '') { - $this->fieldClean($tablespace); - $sql .= " TABLESPACE \"{$tablespace}\""; - } - - $status = $this->execute($sql); - if ($status) { - $this->rollbackTransaction(); - return -1; - } - - return $this->endTransaction(); - } - - // Group functions - - /** - * Return users in a specific group - * @param $groname The name of the group - * @return All users in the group + * Return all database available on the server + * @return A list of databases, sorted alphabetically */ - function getGroup($groname) { - $this->clean($groname); + function getDatabases($currentdatabase = NULL) { + global $conf, $misc; - $sql = "SELECT s.usename FROM pg_catalog.pg_user s, pg_catalog.pg_group g - WHERE g.groname='{$groname}' AND s.usesysid = ANY (g.grolist) - ORDER BY s.usename"; + $server_info = $misc->getServerInfo(); - return $this->selectSet($sql); + if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser($server_info['username'])) { + $username = $server_info['username']; + $this->clean($username); + $clause = " AND pu.usename='{$username}'"; } + else $clause = ''; - // Schema functions - - /** - * Return all schemas in the current database. This differs from the version - * in 7.3 only in that it considers the information_schema to be a system schema. - * @return All schemas, sorted alphabetically - */ - function getSchemas() { - global $conf, $slony; - - if (!$conf['show_system']) { - $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'"; - if (isset($slony) && $slony->isEnabled()) { - $temp = $slony->slony_schema; - $this->clean($temp); - $where .= " AND nspname != '{$temp}'"; - } - - } - else $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; - $sql = "SELECT pn.nspname, pu.usename AS nspowner, pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment - FROM pg_catalog.pg_namespace pn LEFT JOIN pg_catalog.pg_user pu ON (pn.nspowner = pu.usesysid) - {$where} ORDER BY nspname"; - - return $this->selectSet($sql); - } - - // Index functions - - /** - * 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 = '', $unique = false) { - $this->clean($table); - - $sql = "SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered, - pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef - 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 - "; - if ($unique) $sql .= " AND i.indisunique "; - $sql .= " ORDER BY c2.relname"; - - return $this->selectSet($sql); - } - - // View functions - - /** - * Returns all details for a particular view - * @param $view The name of the view to retrieve - * @return View info - */ - function getView($view) { - $this->clean($view); - - $sql = "SELECT c.relname, n.nspname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, - pg_catalog.pg_get_viewdef(c.oid, true) AS vwdefinition, pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment - FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) - WHERE (c.relname = '$view') - AND n.nspname='{$this->_schema}'"; - - return $this->selectSet($sql); - } - - // Trigger functions - - /** - * Grabs a list of triggers on a table - * @param $table The name of a table whose triggers to retrieve - * @return A recordset - */ - function getTriggers($table = '') { - $this->clean($table); - - $sql = "SELECT - t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef, t.tgenabled, p.oid AS prooid, - p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, - ns.nspname AS pronamespace - FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns - WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}')) - AND (NOT tgisconstraint OR NOT EXISTS - (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c - ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) - WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f')) - AND p.oid=t.tgfoid - AND p.pronamespace = ns.oid"; - - return $this->selectSet($sql); - } - - // Administration functions - - /** - * Recluster a table or all the tables in the current database - * @param $table (optional) The table to recluster - */ - function recluster($table = '') { - if ($table != '') { - $this->fieldClean($table); - $sql = "CLUSTER \"{$this->_schema}\".\"{$table}\""; - } + if ($currentdatabase != NULL) + $orderby = "ORDER BY pdb.datname = '{$currentdatabase}' DESC, pdb.datname"; else - $sql = "CLUSTER"; + $orderby = "ORDER BY pdb.datname"; - return $this->execute($sql); - } - - // Domain functions - - /** - * Get domain constraints - * @param $domain The name of the domain whose constraints to fetch - * @return A recordset - */ - function getDomainConstraints($domain) { - $this->clean($domain); + if (!$conf['show_system']) + $where = ' AND NOT pdb.datistemplate'; + else + $where = ' AND pdb.datallowconn'; - $sql = " - SELECT - conname, - contype, - pg_catalog.pg_get_constraintdef(oid, true) AS consrc - FROM - pg_catalog.pg_constraint - WHERE - contypid = (SELECT oid FROM pg_catalog.pg_type - WHERE typname='{$domain}' - AND typnamespace = (SELECT oid FROM pg_catalog.pg_namespace - WHERE nspname = '{$this->_schema}')) - ORDER BY - conname"; + $sql = "SELECT pdb.datname AS datname, pu.usename AS datowner, pg_encoding_to_char(encoding) AS datencoding, + (SELECT description FROM pg_description pd WHERE pdb.oid=pd.objoid) AS datcomment + FROM pg_database pdb, pg_user pu + WHERE pdb.datdba = pu.usesysid + {$where} + {$clause} + {$orderby}"; return $this->selectSet($sql); } + // Table functions + /** - * Drops a domain constraint - * @param $domain The domain from which to remove the constraint - * @param $constraint The constraint to remove - * @param $cascade True to cascade, false otherwise + * Protected method which alter a table + * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION + * @param $tblrs The table recordSet returned by getTable() + * @param $name The new name for the table + * @param $owner The new owner for the table + * @param $schema The new schema for the table + * @param $comment The comment on the table + * @param $tablespace The new tablespace for the table ('' means leave as is) * @return 0 success + * @return -3 rename error + * @return -4 comment error + * @return -5 owner error */ - function dropDomainConstraint($domain, $constraint, $cascade) { - $this->fieldClean($domain); - $this->fieldClean($constraint); + protected + function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) { - $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" DROP CONSTRAINT \"{$constraint}\""; - if ($cascade) $sql .= " CASCADE"; + /* $schema and tablespace not supported in pg74- */ + $this->fieldArrayClean($tblrs->fields); - return $this->execute($sql); - } + // Comment + $this->clean($comment); + $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment); + if ($status != 0) return -4; - /** - * Adds a check constraint to a domain - * @param $domain The domain to which to add the check - * @param $definition The definition of the check - * @param $name (optional) The name to give the check, otherwise default name is assigned - * @return 0 success - */ - function addDomainCheckConstraint($domain, $definition, $name = '') { - $this->fieldClean($domain); - $this->fieldClean($name); + // Owner + $this->fieldClean($owner); + $status = $this->alterTableOwner($tblrs, $owner); + if ($status != 0) return -5; - $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" ADD "; - if ($name != '') $sql .= "CONSTRAINT \"{$name}\" "; - $sql .= "CHECK ({$definition})"; + // Rename + $this->fieldClean($name); + $status = $this->alterTableName($tblrs, $name); + if ($status != 0) return -3; - return $this->execute($sql); + return 0; } /** - * Alters a domain - * @param $domain The domain to alter - * @param $domdefault The domain default - * @param $domnotnull True for NOT NULL, false otherwise - * @param $domowner The domain owner + * Alters a column in a table OR view + * @param $table The table in which the column resides + * @param $column The column to alter + * @param $name The new name for the column + * @param $notnull (boolean) True if not null, false otherwise + * @param $oldnotnull (boolean) True if column is already not null, false otherwise + * @param $default The new default for the column + * @param $olddefault The old default for the column + * @param $type The new type for the column + * @param $array True if array type, false otherwise + * @param $length The optional size of the column (ie. 30 for varchar(30)) + * @param $oldtype The old type for the column + * @param $comment Comment for the column * @return 0 success - * @return -1 transaction error - * @return -2 default error - * @return -3 not null error - * @return -4 owner error + * @return -1 set not null error + * @return -2 set default error + * @return -3 rename column error + * @return -4 comment error */ - function alterDomain($domain, $domdefault, $domnotnull, $domowner) { - $this->fieldClean($domain); - $this->fieldClean($domowner); + function alterColumn($table, $column, $name, $notnull, $oldnotnull, $default, $olddefault, + $type, $length, $array, $oldtype, $comment) { + $this->beginTransaction(); - $status = $this->beginTransaction(); + // @@ NEED TO HANDLE "NESTED" TRANSACTION HERE + if ($notnull != $oldnotnull) { + $status = $this->setColumnNull($table, $column, !$notnull); if ($status != 0) { $this->rollbackTransaction(); return -1; } + } - // Default - if ($domdefault == '') - $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" DROP DEFAULT"; + // Set default, if it has changed + if ($default != $olddefault) { + if ($default == '') + $status = $this->dropColumnDefault($table, $column); else - $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" SET DEFAULT {$domdefault}"; + $status = $this->setColumnDefault($table, $column, $default); - $status = $this->execute($sql); if ($status != 0) { $this->rollbackTransaction(); return -2; } + } - // NOT NULL - if ($domnotnull) - $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" SET NOT NULL"; - else - $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" DROP NOT NULL"; - - $status = $this->execute($sql); + // Rename the column, if it has been changed + if ($column != $name) { + $status = $this->renameColumn($table, $column, $name); if ($status != 0) { $this->rollbackTransaction(); return -3; } + } - // Owner - $sql = "ALTER DOMAIN \"{$this->_schema}\".\"{$domain}\" OWNER TO \"{$domowner}\""; - - $status = $this->execute($sql); + // Parameters must be cleaned for the setComment function. It's ok to do + // that here since this is the last time these variables are used. + $this->fieldClean($name); + $this->fieldClean($table); + $this->clean($comment); + $status = $this->setComment('COLUMN', $name, $table, $comment); if ($status != 0) { $this->rollbackTransaction(); return -4; @@ -544,326 +210,133 @@ class Postgres74 extends Postgres73 { return $this->endTransaction(); } - // User functions - /** - * Renames a user - * @param $username The username of the user to rename - * @param $newname The new name of the user - * @return 0 success + * Returns table information + * @param $table The name of the table + * @return A recordset */ - function renameUser($username, $newname){ - $this->fieldClean($username); - $this->fieldClean($newname); + function getTable($table) { + $this->clean($table); - $sql = "ALTER USER \"{$username}\" RENAME TO \"{$newname}\""; + $sql = " + SELECT + c.relname, n.nspname, u.usename AS relowner, + pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind = 'r' + AND n.nspname = '{$this->_schema}' + AND c.relname = '{$table}'"; - return $this->execute($sql); + return $this->selectSet($sql); } /** - * Adjusts a user's info and renames the user - * @param $username The username of the user to modify - * @param $password A new password for the user - * @param $createdb boolean Whether or not the user can create databases - * @param $createuser boolean Whether or not the user can create other users - * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire. - * @param $newname The new name of the user - * @return 0 success - * @return -1 transaction error - * @return -2 set user attributes error - * @return -3 rename error + * Return all tables in current database (and schema) + * @param $all True to fetch all tables, false for just in current schema + * @return All tables, sorted alphabetically */ - function setRenameUser($username, $password, $createdb, $createuser, $expiry, $newname) { - $status = $this->beginTransaction(); - if ($status != 0) return -1; - - $status = $this->setUser($username, $password, $createdb, $createuser, $expiry); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } - - if ($username != $newname){ - $status = $this->renameUser($username, $newname); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } + function getTables($all = false) { + if ($all) { + // Exclude pg_catalog and information_schema tables + $sql = "SELECT schemaname AS nspname, tablename AS relname, tableowner AS relowner + FROM pg_catalog.pg_tables + WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') + ORDER BY schemaname, tablename"; + } else { + $sql = "SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, + pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment, + reltuples::bigint + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind = 'r' + AND nspname='{$this->_schema}' + ORDER BY c.relname"; } - return $this->endTransaction(); - } - - // Function functions - - /** - * Updates (replaces) a function. - * @param $function_oid The OID of the function - * @param $funcname The name of the function to create - * @param $newname The new name for the function - * @param $args The array of argument types - * @param $returns The return type - * @param $definition The definition for the new function - * @param $language The language the function is written for - * @param $flags An array of optional flags - * @param $setof True if returns a set, false otherwise - * @param $comment The comment on the function - * @return 0 success - * @return -1 transaction error - * @return -3 create function error - * @return -4 comment error - * @return -5 rename function error - * @return -6 alter owner error - * @return -7 alter schema error - */ - function setFunction($function_oid, $funcname, $newname, $args, $returns, $definition, $language, $flags, $setof, $funcown, $newown, $funcschema, $newschema, $cost, $rows, $comment) { - // Begin a transaction - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Replace the existing function - $status = $this->createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, true); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - - // Comment on the function - $this->fieldClean($funcname); - $this->clean($comment); - $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - - // Rename the function, if necessary - $this->fieldClean($newname); - if ($funcname != $newname) { - $sql = "ALTER FUNCTION \"{$this->_schema}\".\"{$funcname}\"({$args}) RENAME TO \"{$newname}\""; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -5; - } - - $funcname = $newname; - } - - // Alter the owner, if necessary - if ($this->hasFunctionAlterOwner()) { - $this->fieldClean($newown); - if ($funcown != $newown) { - $sql = "ALTER FUNCTION \"{$this->_schema}\".\"{$funcname}\"({$args}) OWNER TO \"{$newown}\""; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -6; - } - } - - } - - // Alter the schema, if necessary - if ($this->hasFunctionAlterSchema()) { - $this->fieldClean($newschema); - if ($funcschema != $newschema) { - $sql = "ALTER FUNCTION \"{$this->_schema}\".\"{$funcname}\"({$args}) SET SCHEMA \"{$newschema}\""; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -7; - } - } - } - - return $this->endTransaction(); + return $this->selectSet($sql); } - // Sequences - /** - * Protected method which alter a sequence - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $seqrs The sequence recordSet returned by getSequence() - * @param $name The new name for the sequence - * @param $comment The comment on the sequence - * @param $owner The new owner for the sequence - * @param $schema The new schema for the sequence - * @param $increment The increment - * @param $minvalue The min value - * @param $maxvalue The max value - * @param $startvalue The starting value - * @param $cachevalue The cache value - * @param $cycledvalue True if cycled, false otherwise - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 get sequence error + * Returns the current default_with_oids setting + * @return default_with_oids setting */ - /*protected*/ - function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue) { - - $status = parent::_alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue); - if ($status != 0) - return $status; - - /* $schema not supported in pg74 */ - - // if name != seqname, sequence has been renamed in parent - $sequence = ($seqrs->fields['seqname'] == $name) ? $seqrs->fields['seqname'] : $name; - $this->clean($increment); - $this->clean($minvalue); - $this->clean($maxvalue); - $this->clean($startvalue); - $this->clean($cachevalue); - $this->clean($cycledvalue); - - // Props - $sql = ''; - if (!empty($increment) && ($increment != $seqrs->fields['increment_by'])) $sql .= " INCREMENT {$increment}"; - if (!empty($minvalue) && ($minvalue != $seqrs->fields['min_value'])) $sql .= " MINVALUE {$minvalue}"; - if (!empty($maxvalue) && ($maxvalue != $seqrs->fields['max_value'])) $sql .= " MAXVALUE {$maxvalue}"; - if (!empty($startvalue) && ($startvalue != $seqrs->fields['last_value'])) $sql .= " RESTART {$startvalue}"; - if (!empty($cachevalue) && ($cachevalue != $seqrs->fields['cache_value'])) $sql .= " CACHE {$cachevalue}"; - // toggle cycle yes/no - if (!is_null($cycledvalue)) - $sql .= (!$cycledvalue ? ' NO ' : '') . " CYCLE"; - if ($sql != '') { - $sql = "ALTER SEQUENCE \"{$this->_schema}\".\"{$sequence}\" $sql"; - $status = $this->execute($sql); - if ($status != 0) - return -6; + function getDefaultWithOid() { + // 8.0 is the first release to have this setting + // Prior releases don't have this setting... oids always activated + return 'on'; } - return 0; - } - - // Aggregates + // Sequence functions /** - * Alters an aggregate - * @param $aggrname The actual name of the aggregate - * @param $aggrtype The actual input data type of the aggregate - * @param $aggrowner The actual owner of the aggregate - * @param $aggrschema The actual schema the aggregate belongs to - * @param $aggrcomment The actual comment for the aggregate - * @param $newaggrname The new name of the aggregate - * @param $newaggrowner The new owner of the aggregate - * @param $newaggrschema The new schema where the aggregate will belong to - * @param $newaggrcomment The new comment for the aggregate - * @return 0 success - * @return -1 change owner error - * @return -2 change comment error - * @return -3 change schema error - * @return -4 change name error + * Returns all sequences in the current database + * @return A recordset */ - function alterAggregate($aggrname, $aggrtype, $aggrowner, $aggrschema, $aggrcomment, $newaggrname, $newaggrowner, $newaggrschema, $newaggrcomment) { - // Clean fields - $this->fieldClean($aggrname); - $this->fieldClean($aggrtype); - $this->fieldClean($aggrowner); - $this->fieldClean($aggrschema); - $this->clean($aggrcomment); - $this->fieldClean($newaggrname); - $this->fieldClean($newaggrowner); - $this->fieldClean($newaggrschema); - $this->clean($newaggrcomment); - - $this->beginTransaction(); - - // Change the owner, if it has changed - if($aggrowner != $newaggrowner) { - $status = $this->changeAggregateOwner($aggrname, $aggrtype, $newaggrowner); - if($status != 0) { - $this->rollbackTransaction(); - return -1; - } + 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"; } - // Set the comment, if it has changed - if($aggrcomment != $newaggrcomment) { - $status = $this->setComment('AGGREGATE', $aggrname, '', $newaggrcomment, $aggrtype); - if ($status) { - $this->rollbackTransaction(); - return -2; - } + return $this->selectSet( $sql ); } - // Change the schema, if it has changed - if($aggrschema != $newaggrschema) { - $status = $this->changeAggregateSchema($aggrname, $aggrtype, $newaggrschema); - if($status != 0) { - $this->rollbackTransaction(); - return -3; - } - } - - // Rename the aggregate, if it has changed - if($aggrname != $newaggrname) { - $status = $this->renameAggregate($newaggrschema, $aggrname, $aggrtype, $newaggrname); - if($status != 0) { - $this->rollbackTransaction(); - return -4; - } - } - - return $this->endTransaction(); - } + // Function functions /** - * Changes the owner of an aggregate function - * @param $aggrname The name of the aggregate - * @param $aggrtype The input data type of the aggregate - * @param $newaggrowner The new owner of the aggregate - * @return 0 success + * Returns all details for a particular function + * @param $func The name of the function to retrieve + * @return Function info */ - function changeAggregateOwner($aggrname, $aggrtype, $newaggrowner) { - $sql = "ALTER AGGREGATE \"{$this->_schema}\".\"{$aggrname}\" (\"{$aggrtype}\") OWNER TO \"{$newaggrowner}\""; - return $this->execute($sql); - } + function getFunction($function_oid) { + $this->clean($function_oid); - /** - * Changes the schema of an aggregate function - * @param $aggrname The name of the aggregate - * @param $aggrtype The input data type of the aggregate - * @param $newaggrschema The new schema for the aggregate - * @return 0 success - */ - function changeAggregateSchema($aggrname, $aggrtype, $newaggrschema) { - $sql = "ALTER AGGREGATE \"{$this->_schema}\".\"{$aggrname}\" (\"{$aggrtype}\") SET SCHEMA \"{$newaggrschema}\""; - return $this->execute($sql); - } + $sql = " + SELECT + pc.oid AS prooid, + proname, + pg_catalog.pg_get_userbyid(proowner) AS proowner, + nspname as proschema, + lanname as prolanguage, + pg_catalog.format_type(prorettype, NULL) as proresult, + prosrc, + probin, + proretset, + proisstrict, + provolatile, + prosecdef, + pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments, + pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment + FROM + pg_catalog.pg_proc pc, pg_catalog.pg_language pl, pg_catalog.pg_namespace n + WHERE + pc.oid = '$function_oid'::oid + AND pc.prolang = pl.oid + AND n.oid = pc.pronamespace + "; - /** - * Renames an aggregate function - * @param $aggrname The actual name of the aggregate - * @param $aggrtype The actual input data type of the aggregate - * @param $newaggrname The new name of the aggregate - * @return 0 success - */ - function renameAggregate($aggrschema, $aggrname, $aggrtype, $newaggrname) { - $sql = "ALTER AGGREGATE \"{$aggrschema}\"" . '.' . "\"{$aggrname}\" (\"{$aggrtype}\") RENAME TO \"{$newaggrname}\""; - return $this->execute($sql); + return $this->selectSet($sql); } // Capabilities - function hasAlterDatabaseRename() { return true; } - function hasGrantOption() { return true; } - function hasDomainConstraints() { return true; } - function hasUserRename() { return true; } - function hasRecluster() { return true; } - function hasReadOnlyQueries() { return true; } - function hasAlterSequenceProps() { return true; } - function hasAlterAggregate() { return true; } - function hasCreateTableLike() { return true; } -} + function hasAlterColumnType() { return false; } + function hasAlterDatabaseOwner() { return false; } + function hasFunctionAlterOwner() { return false; } + function hasNamedParams() { return false; } + function hasSignals() { return false; } + function hasTablespaces() { return false; } +} ?> diff --git a/classes/database/Postgres80.php b/classes/database/Postgres80.php index 78472601..ebc0cac7 100644 --- a/classes/database/Postgres80.php +++ b/classes/database/Postgres80.php @@ -6,34 +6,52 @@ * $Id: Postgres80.php,v 1.28 2007/12/12 04:11:10 xzilla Exp $ */ -include_once('./classes/database/Postgres74.php'); +include_once('./classes/database/Postgres81.php'); -class Postgres80 extends Postgres74 { +class Postgres80 extends Postgres81 { var $major_version = 8.0; - - // List of all legal privileges that can be applied to different types - // of objects. - var $privlist = array( - 'table' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), - 'view' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), - 'sequence' => array('SELECT', 'UPDATE', 'ALL PRIVILEGES'), - 'database' => array('CREATE', 'TEMPORARY', 'ALL PRIVILEGES'), - 'function' => array('EXECUTE', 'ALL PRIVILEGES'), - 'language' => array('USAGE', 'ALL PRIVILEGES'), - 'schema' => array('CREATE', 'USAGE', 'ALL PRIVILEGES'), - 'tablespace' => array('CREATE', 'ALL PRIVILEGES') + // Map of database encoding names to HTTP encoding names. If a + // database encoding does not appear in this list, then its HTTP + // encoding name is the same as its database encoding name. + var $codemap = array( + 'ALT' => 'CP866', + 'EUC_CN' => 'GB2312', + 'EUC_JP' => 'EUC-JP', + 'EUC_KR' => 'EUC-KR', + 'EUC_TW' => 'EUC-TW', + 'ISO_8859_5' => 'ISO-8859-5', + 'ISO_8859_6' => 'ISO-8859-6', + 'ISO_8859_7' => 'ISO-8859-7', + 'ISO_8859_8' => 'ISO-8859-8', + 'JOHAB' => 'CP1361', + 'KOI8' => 'KOI8-R', + 'LATIN1' => 'ISO-8859-1', + 'LATIN2' => 'ISO-8859-2', + 'LATIN3' => 'ISO-8859-3', + 'LATIN4' => 'ISO-8859-4', + // The following encoding map is a known error in PostgreSQL < 7.2 + // See the constructor for Postgres72. + 'LATIN5' => 'ISO-8859-5', + 'LATIN6' => 'ISO-8859-10', + 'LATIN7' => 'ISO-8859-13', + 'LATIN8' => 'ISO-8859-14', + 'LATIN9' => 'ISO-8859-15', + 'LATIN10' => 'ISO-8859-16', + 'SQL_ASCII' => 'US-ASCII', + 'TCVN' => 'CP1258', + 'UNICODE' => 'UTF-8', + 'WIN' => 'CP1251', + 'WIN874' => 'CP874', + 'WIN1256' => 'CP1256' ); - // Last oid assigned to a system object - var $_lastSystemOID = 17228; - /** * Constructor * @param $conn The database connection */ function Postgres80($conn) { - $this->Postgres74($conn); + $this->Postgres81($conn); } // Help functions @@ -83,131 +101,9 @@ class Postgres80 extends Postgres74 { return $this->selectSet($sql); } - /** - * Alters a database - * the multiple return vals are for postgres 8+ which support more functionality in alter database - * @param $dbName The name of the database - * @param $newName new name for the database - * @param $newOwner The new owner for the database - * @return 0 success - * @return -1 transaction error - * @return -2 owner error - * @return -3 rename error - */ - function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') - { - $this->clean($dbName); - $this->clean($newName); - $this->clean($newOwner); - //ignore $comment, not supported pre 8.2 - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - if ($dbName != $newName) { - $status = $this->alterDatabaseRename($dbName, $newName); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - } - - $status = $this->alterDatabaseOwner($newName, $newOwner); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } - return $this->endTransaction(); - } - - /** - * Changes ownership of a database - * This can only be done by a superuser or the owner of the database - * @param string $dbName database to change ownership of - * @param string $newOwner user that will own the database - * @return int 0 on success - */ - function alterDatabaseOwner($dbName, $newOwner) { - $this->clean($dbName); - $this->clean($newOwner); - - $sql = "ALTER DATABASE \"{$dbName}\" OWNER TO \"{$newOwner}\""; - return $this->execute($sql); - } - - /** - * Returns the current default_with_oids setting - * @return default_with_oids setting - */ - function getDefaultWithOid() { - // Try to avoid a query if at all possible (5) - if (function_exists('pg_parameter_status')) { - $default = pg_parameter_status($this->conn->_connectionID, 'default_with_oids'); - if ($default !== false) return $default; - } - - $sql = "SHOW default_with_oids"; - - return $this->selectField($sql, 'default_with_oids'); - } - // Table functions /** - * Return all tables in current database (and schema) - * @param $all True to fetch all tables, false for just in current schema - * @return All tables, sorted alphabetically - */ - function getTables($all = false) { - if ($all) { - // Exclude pg_catalog and information_schema tables - $sql = "SELECT schemaname AS nspname, tablename AS relname, tableowner AS relowner - FROM pg_catalog.pg_tables - WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') - ORDER BY schemaname, tablename"; - } else { - $sql = "SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, - pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment, - reltuples::bigint, - (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind = 'r' - AND nspname='{$this->_schema}' - ORDER BY c.relname"; - } - - return $this->selectSet($sql); - } - - /** - * Returns table information - * @param $table The name of the table - * @return A recordset - */ - function getTable($table) { - $this->clean($table); - - $sql = " - SELECT - c.relname, n.nspname, u.usename AS relowner, - pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment, - (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind = 'r' - AND n.nspname = '{$this->_schema}' - AND n.oid = c.relnamespace - AND c.relname = '{$table}'"; - - return $this->selectSet($sql); - } - - /** * Protected method which alter a table * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION * @param $tblrs The table recordSet returned by getTable() @@ -221,371 +117,194 @@ class Postgres80 extends Postgres74 { * @return -4 comment error * @return -5 owner error * @return -6 tablespace error - * @return -7 schema error */ - /* protected */ + protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) { - $status = parent::_alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace); - if ($status != 0) - return $status; + /* $schema not supported in pg80- */ - // if name != tablename, table has been renamed in parent - $tablename = ($tblrs->fields['relname'] == $name) ? $tblrs->fields['relname'] : $name; + // Comment + $this->clean($comment); + $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment); + if ($status != 0) return -4; - /* $schema not supported in pg80 */ - $this->fieldClean($tablespace); + // Owner + $this->fieldClean($owner); + $status = $this->alterTableOwner($tblrs, $owner); + if ($status != 0) return -5; // Tablespace - if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) { - - // If tablespace has been changed, then do the alteration. We - // don't want to do this unnecessarily. - $sql = "ALTER TABLE \"{$tablename}\" SET TABLESPACE \"{$tablespace}\""; - - $status = $this->execute($sql); + $this->fieldClean($tablespace); + $status = $this->alterTableTablespace($tblrs, $tablespace); if ($status != 0) return -6; - } + + // Rename + $this->fieldClean($name); + $status = $this->alterTableName($tblrs, $name); + if ($status != 0) return -3; return 0; } + // View functions + /** - * Alters a column in a table - * @param $table The table in which the column resides - * @param $column The column to alter - * @param $name The new name for the column - * @param $notnull (boolean) True if not null, false otherwise - * @param $oldnotnull (boolean) True if column is already not null, false otherwise - * @param $default The new default for the column - * @param $olddefault The old default for the column - * @param $type The new type for the column - * @param $array True if array type, false otherwise - * @param $length The optional size of the column (ie. 30 for varchar(30)) - * @param $oldtype The old type for the column - * @param $comment Comment for the column + * Protected method which alter a view + * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION + * @param $vwrs The view recordSet returned by getView() + * @param $name The new name for the view + * @param $owner The new owner for the view + * @param $comment The comment on the view * @return 0 success - * @return -1 batch alteration failed - * @return -3 rename column error + * @return -3 rename error * @return -4 comment error - * @return -6 transaction error - */ - function alterColumn($table, $column, $name, $notnull, $oldnotnull, $default, $olddefault, - $type, $length, $array, $oldtype, $comment) { - $this->fieldClean($table); - $this->fieldClean($column); - $this->clean($comment); - - // Initialise an empty SQL string - $sql = ''; - - // Create the command for changing nullability - if ($notnull != $oldnotnull) { - $sql .= "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . (($notnull) ? 'SET' : 'DROP') . " NOT NULL"; - } - - // Add default, if it has changed - if ($default != $olddefault) { - if ($default == '') { - if ($sql == '') $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" "; - else $sql .= ", "; - $sql .= "ALTER COLUMN \"{$column}\" DROP DEFAULT"; - } - else { - if ($sql == '') $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" "; - else $sql .= ", "; - $sql .= "ALTER COLUMN \"{$column}\" SET DEFAULT {$default}"; - } - } - - // Add type, if it has changed - if ($length == '') - $ftype = $type; - else { - switch ($type) { - // Have to account for weird placing of length for with/without - // time zone types - case 'timestamp with time zone': - case 'timestamp without time zone': - $qual = substr($type, 9); - $ftype = "timestamp({$length}){$qual}"; - break; - case 'time with time zone': - case 'time without time zone': - $qual = substr($type, 4); - $ftype = "time({$length}){$qual}"; - break; - default: - $ftype = "{$type}({$length})"; - } - } - - // Add array qualifier, if requested - if ($array) $ftype .= '[]'; - - if ($ftype != $oldtype) { - if ($sql == '') $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" "; - else $sql .= ", "; - $sql .= "ALTER COLUMN \"{$column}\" TYPE {$ftype}"; - } - - // Begin transaction - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -6; - } - - // Attempt to process the batch alteration, if anything has been changed - if ($sql != '') { - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - } - - // Update the comment on the column - $status = $this->setComment('COLUMN', $column, $table, $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - - // Rename the column, if it has been changed - if ($column != $name) { - $status = $this->renameColumn($table, $column, $name); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - } - - return $this->endTransaction(); - } - - // Sequence functions - - /** - * Returns all sequences in the current database - * @return A recordset - */ - 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 ); - } - - // Tablespace functions - - /** - * Retrieves information for all tablespaces - * @param $all Include all tablespaces (necessary when moving objects back to the default space) - * @return A recordset - */ - function getTablespaces($all = false) { - global $conf; - - $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation - FROM pg_catalog.pg_tablespace"; - - if (!$conf['show_system'] && !$all) { - $sql .= " WHERE spcname NOT LIKE 'pg\\\\_%'"; - } - - $sql .= " ORDER BY spcname"; - - return $this->selectSet($sql); - } - - /** - * Retrieves a tablespace's information - * @return A recordset + * @return -5 owner error */ - function getTablespace($spcname) { - $this->clean($spcname); + protected + function _alterView($vwrs, $name, $owner, $schema, $comment) { - $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation - FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'"; + /* $schema not supported in pg80- */ + $this->fieldArrayClean($vwrs->fields); - return $this->selectSet($sql); - } - - /** - * Creates a tablespace - * @param $spcname The name of the tablespace to create - * @param $spcowner The owner of the tablespace. '' for current - * @param $spcloc The directory in which to create the tablespace - * @return 0 success - */ - function createTablespace($spcname, $spcowner, $spcloc, $comment='') { - $this->fieldClean($spcname); - $this->clean($spcloc); + // Comment $this->clean($comment); + if ($this->setComment('VIEW', $vwrs->fields['relname'], '', $comment) != 0) + return -4; - $sql = "CREATE TABLESPACE \"{$spcname}\""; - - if ($spcowner != '') { - $this->fieldClean($spcowner); - $sql .= " OWNER \"{$spcowner}\""; - } - - $sql .= " LOCATION '{$spcloc}'"; - - $status = $this->execute($sql); - if ($status != 0) return -1; + // Owner + $this->fieldClean($owner); + $status = $this->alterViewOwner($vwrs, $owner); + if ($status != 0) return -5; - if ($comment != '' && $this->hasSharedComments()) { - $status = $this->setComment('TABLESPACE',$spcname,'',$comment); - if ($status != 0) return -2; - } + // Rename + $this->fieldClean($name); + $status = $this->alterViewName($vwrs, $name); + if ($status != 0) return -3; return 0; - } - /** - * Drops a tablespace - * @param $spcname The name of the domain to drop - * @return 0 success - */ - function dropTablespace($spcname) { - $this->fieldClean($spcname); - - $sql = "DROP TABLESPACE \"{$spcname}\""; - - return $this->execute($sql); - } + // Sequence functions /** - * Alters a tablespace - * @param $spcname The name of the tablespace - * @param $name The new name for the tablespace - * @param $owner The new owner for the tablespace + * Protected method which alter a sequence + * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION + * @param $seqrs The sequence recordSet returned by getSequence() + * @param $name The new name for the sequence + * @param $comment The comment on the sequence + * @param $owner The new owner for the sequence + * @param $schema The new schema for the sequence + * @param $increment The increment + * @param $minvalue The min value + * @param $maxvalue The max value + * @param $startvalue The starting value + * @param $cachevalue The cache value + * @param $cycledvalue True if cycled, false otherwise * @return 0 success - * @return -1 transaction error - * @return -2 owner error * @return -3 rename error * @return -4 comment error + * @return -5 owner error + * @return -6 get sequence props error */ - function alterTablespace($spcname, $name, $owner, $comment='') { - $this->fieldClean($spcname); - $this->fieldClean($name); - $this->fieldClean($owner); + protected + function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, + $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue) { - // Begin transaction - $status = $this->beginTransaction(); - if ($status != 0) return -1; + /* $schema not supported in pg80- */ + $this->fieldArrayClean($seqrs->fields); - // Owner - $sql = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\""; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } + // Comment + $this->clean($comment); + $status = $this->setComment('SEQUENCE', $seqrs->fields['seqname'], '', $comment); + if ($status != 0) + return -4; - // Rename (only if name has changed) - if ($name != $spcname) { - $sql = "ALTER TABLESPACE \"{$spcname}\" RENAME TO \"{$name}\""; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - } + // Owner + $this->fieldClean($owner); + $status = $this->alterSequenceOwner($seqrs, $owner); + if ($status != 0) + return -5; + + // Props + $this->clean($increment); + $this->clean($minvalue); + $this->clean($maxvalue); + $this->clean($startvalue); + $this->clean($cachevalue); + $this->clean($cycledvalue); + $status = $this->alterSequenceProps($seqrs, $increment, $minvalue, + $maxvalue, $startvalue, $cachevalue, $cycledvalue); + if ($status != 0) + return -6; - // Set comment if it has changed - if (trim($comment) != '' && $this->hasSharedComments()) { - $status = $this->setComment('TABLESPACE',$spcname,'',$comment); - if ($status != 0) return -4; - } + // Rename + $this->fieldClean($name); + $status = $this->alterSequenceName($seqrs, $name); + if ($status != 0) + return -3; - return $this->endTransaction(); + return 0; } - // Backend process signalling functions + // Role, User/group functions /** - * Sends a cancel or kill command to a process - * @param $pid The ID of the backend process - * @param $signal 'CANCEL' + * Changes a user's password + * @param $username The username + * @param $password The new password * @return 0 success - * @return -1 invalid signal type */ - function sendSignal($pid, $signal) { - // Clean - $pid = (int)$pid; + function changePassword($username, $password) { + $enc = $this->_encryptPassword($username, $password); + $this->fieldClean($username); + $this->clean($enc); - if ($signal == 'CANCEL') - $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val"; - else - return -1; + $sql = "ALTER USER \"{$username}\" WITH ENCRYPTED PASSWORD '{$enc}'"; - // Execute the query - $val = $this->selectField($sql, 'val'); - - if ($val === -1) return -1; - elseif ($val == '1') return 0; - else return -1; + return $this->execute($sql); } + // Aggregate functions + /** - * Returns all details for a particular function - * @param $func The name of the function to retrieve - * @return Function info + * Gets all information for an aggregate + * @param $name The name of the aggregate + * @param $basetype The input data type of the aggregate + * @return A recordset */ - function getFunction($function_oid) { - $this->clean($function_oid); - - $sql = "SELECT - pc.oid AS prooid, - proname, - pg_catalog.pg_get_userbyid(proowner) AS proowner, - nspname as proschema, - lanname as prolanguage, - pg_catalog.format_type(prorettype, NULL) as proresult, - prosrc, - probin, - proretset, - proisstrict, - provolatile, - prosecdef, - pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments, - proargnames AS proargnames, - pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment - FROM - pg_catalog.pg_proc pc, pg_catalog.pg_language pl, pg_catalog.pg_namespace pn - WHERE - pc.oid = '{$function_oid}'::oid - AND pc.prolang = pl.oid - AND pc.pronamespace = pn.oid - "; + function getAggregate($name, $basetype) { + $this->fieldclean($name); + $this->fieldclean($basetype); + + $sql = " + SELECT p.proname, + CASE p.proargtypes[0] + WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL + ELSE pg_catalog.format_type(p.proargtypes[0], NULL) + END AS proargtypes, a.aggtransfn, format_type(a.aggtranstype, NULL) AS aggstype, + a.aggfinalfn, a.agginitval, u.usename, pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment + FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a + WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid + AND p.proisagg AND n.nspname='{$this->_schema}' + AND p.proname='" . $name . "' + AND CASE p.proargtypes[0] + WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN '' + ELSE pg_catalog.format_type(p.proargtypes[0], NULL) + END ='" . $basetype . "'"; return $this->selectSet($sql); } // Capabilities - function hasAlterDatabaseOwner() { return true; } - function hasAlterColumnType() { return true; } - function hasTablespaces() { return true; } - function hasSignals() { return true; } - function hasNamedParams() { return true; } - function hasFunctionAlterOwner() { return true; } + function hasAggregateSortOp() { return false; } + function hasAlterTableSchema() { return false; } + function hasAutovacuum() { return false; } + function hasDisableTriggers() { return false; } + function hasFunctionAlterSchema() { return false; } + function hasPreparedXacts() { return false; } + function hasRoles() { return false; } + function hasSequenceAlterSchema() { return false; } + function hasServerAdminFuncs() { return false; } } ?> diff --git a/classes/database/Postgres81.php b/classes/database/Postgres81.php index 8bcded4b..3701c68d 100644 --- a/classes/database/Postgres81.php +++ b/classes/database/Postgres81.php @@ -6,61 +6,47 @@ * $Id: Postgres81.php,v 1.21 2008/01/19 13:46:15 ioguix Exp $ */ -include_once('./classes/database/Postgres80.php'); +include_once('./classes/database/Postgres82.php'); -class Postgres81 extends Postgres80 { +class Postgres81 extends Postgres82 { var $major_version = 8.1; - - // Map of database encoding names to HTTP encoding names. If a - // database encoding does not appear in this list, then its HTTP - // encoding name is the same as its database encoding name. - var $codemap = array( - 'BIG5' => 'BIG5', - 'EUC_CN' => 'GB2312', - 'EUC_JP' => 'EUC-JP', - 'EUC_KR' => 'EUC-KR', - 'EUC_TW' => 'EUC-TW', - 'GB18030' => 'GB18030', - 'GBK' => 'GB2312', - 'ISO_8859_5' => 'ISO-8859-5', - 'ISO_8859_6' => 'ISO-8859-6', - 'ISO_8859_7' => 'ISO-8859-7', - 'ISO_8859_8' => 'ISO-8859-8', - 'JOHAB' => 'CP1361', - 'KOI8' => 'KOI8-R', - 'LATIN1' => 'ISO-8859-1', - 'LATIN2' => 'ISO-8859-2', - 'LATIN3' => 'ISO-8859-3', - 'LATIN4' => 'ISO-8859-4', - 'LATIN5' => 'ISO-8859-9', - 'LATIN6' => 'ISO-8859-10', - 'LATIN7' => 'ISO-8859-13', - 'LATIN8' => 'ISO-8859-14', - 'LATIN9' => 'ISO-8859-15', - 'LATIN10' => 'ISO-8859-16', - 'SJIS' => 'SHIFT_JIS', - 'SQL_ASCII' => 'US-ASCII', - 'UHC' => 'WIN949', - 'UTF8' => 'UTF-8', - 'WIN866' => 'CP866', - 'WIN874' => 'CP874', - 'WIN1250' => 'CP1250', - 'WIN1251' => 'CP1251', - 'WIN1252' => 'CP1252', - 'WIN1256' => 'CP1256', - 'WIN1258' => 'CP1258' + // List of all legal privileges that can be applied to different types + // of objects. + var $privlist = array( + 'table' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), + 'view' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), + 'sequence' => array('SELECT', 'UPDATE', 'ALL PRIVILEGES'), + 'database' => array('CREATE', 'TEMPORARY', 'ALL PRIVILEGES'), + 'function' => array('EXECUTE', 'ALL PRIVILEGES'), + 'language' => array('USAGE', 'ALL PRIVILEGES'), + 'schema' => array('CREATE', 'USAGE', 'ALL PRIVILEGES'), + 'tablespace' => array('CREATE', 'ALL PRIVILEGES') ); - - // Last oid assigned to a system object - var $_lastSystemOID = 17231; + // List of characters in acl lists and the privileges they + // refer to. + var $privmap = array( + 'r' => 'SELECT', + 'w' => 'UPDATE', + 'a' => 'INSERT', + 'd' => 'DELETE', + 'R' => 'RULE', + 'x' => 'REFERENCES', + 't' => 'TRIGGER', + 'X' => 'EXECUTE', + 'U' => 'USAGE', + 'C' => 'CREATE', + 'T' => 'TEMPORARY' + ); + // Array of allowed index types + var $typIndexes = array('BTREE', 'RTREE', 'GIST', 'HASH'); /** * Constructor * @param $conn The database connection */ function Postgres81($conn) { - $this->Postgres80($conn); + $this->Postgres82($conn); } // Help functions @@ -70,7 +56,8 @@ class Postgres81 extends Postgres80 { return $this->help_page; } - // Database Functions + // Database functions + /** * Returns all databases available on the server * @return A list of databases, sorted alphabetically @@ -111,562 +98,143 @@ class Postgres81 extends Postgres80 { } /** - * Returns prepared transactions information - * @param $database (optional) Find only prepared transactions executed in a specific database - * @return A recordset - */ - function getPreparedXacts($database = null) { - if ($database === null) - $sql = "SELECT * FROM pg_prepared_xacts"; - else { - $this->clean($database); - $sql = "SELECT transaction, gid, prepared, owner FROM pg_prepared_xacts WHERE database='{$database}' - ORDER BY owner"; - } - - return $this->selectSet($sql); - } - - // Roles - - /** - * Returns all roles in the database cluster - * @param $rolename (optional) The role name to exclude from the select - * @return All roles - */ - function getRoles($rolename = '') { - $sql = 'SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit, rolcanlogin, rolconnlimit, rolvaliduntil, - rolconfig FROM pg_catalog.pg_roles'; - if($rolename) $sql .= " WHERE rolname!='{$rolename}'"; - $sql .= ' ORDER BY rolname'; - - return $this->selectSet($sql); - } - - /** - * Returns information about a single role - * @param $rolename The name of the role to retrieve - * @return The role's data - */ - function getRole($rolename) { - $this->clean($rolename); - - $sql = "SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit, rolcanlogin, rolconnlimit, rolvaliduntil, - rolconfig FROM pg_catalog.pg_roles WHERE rolname='{$rolename}'"; - - return $this->selectSet($sql); - } - - /** - * Creates a new role - * @param $rolename The name of the role to create - * @param $password A password for the role - * @param $superuser Boolean whether or not the role is a superuser - * @param $createdb Boolean whether or not the role can create databases - * @param $createrole Boolean whether or not the role can create other roles - * @param $inherits Boolean whether or not the role inherits the privileges from parent roles - * @param $login Boolean whether or not the role will be allowed to login - * @param $connlimit Number of concurrent connections the role can make - * @param $expiry String Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire - * @param $memberof (array) Roles to which the new role will be immediately added as a new member - * @param $members (array) Roles which are automatically added as members of the new role - * @param $adminmembers (array) Roles which are automatically added as admin members of the new role - * @return 0 success - */ - function createRole($rolename, $password, $superuser, $createdb, $createrole, $inherits, $login, $connlimit, $expiry, $memberof, $members, $adminmembers) { - $enc = $this->_encryptPassword($rolename, $password); - $this->fieldClean($rolename); - $this->clean($enc); - $this->clean($connlimit); - $this->clean($expiry); - $this->fieldArrayClean($memberof); - $this->fieldArrayClean($members); - $this->fieldArrayClean($adminmembers); - - $sql = "CREATE ROLE \"{$rolename}\""; - if ($password != '') $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; - $sql .= ($superuser) ? ' SUPERUSER' : ' NOSUPERUSER'; - $sql .= ($createdb) ? ' CREATEDB' : ' NOCREATEDB'; - $sql .= ($createrole) ? ' CREATEROLE' : ' NOCREATEROLE'; - $sql .= ($inherits) ? ' INHERIT' : ' NOINHERIT'; - $sql .= ($login) ? ' LOGIN' : ' NOLOGIN'; - if ($connlimit != '') $sql .= " CONNECTION LIMIT {$connlimit}"; else $sql .= ' CONNECTION LIMIT -1'; - if ($expiry != '') $sql .= " VALID UNTIL '{$expiry}'"; else $sql .= " VALID UNTIL 'infinity'"; - if (is_array($memberof) && sizeof($memberof) > 0) $sql .= ' IN ROLE "' . join('", "', $memberof) . '"'; - if (is_array($members) && sizeof($members) > 0) $sql .= ' ROLE "' . join('", "', $members) . '"'; - if (is_array($adminmembers) && sizeof($adminmembers) > 0) $sql .= ' ADMIN "' . join('", "', $adminmembers) . '"'; - - return $this->execute($sql); - } - - /** - * Removes a role - * @param $rolename The name of the role to drop - * @return 0 success - */ - function dropRole($rolename) { - $this->fieldClean($rolename); - - $sql = "DROP ROLE \"{$rolename}\""; - - return $this->execute($sql); - } - - /** - * Adjusts a role's info and renames it - * @param $rolename The name of the role to adjust - * @param $password A password for the role - * @param $superuser Boolean whether or not the role is a superuser - * @param $createdb Boolean whether or not the role can create databases - * @param $createrole Boolean whether or not the role can create other roles - * @param $inherits Boolean whether or not the role inherits the privileges from parent roles - * @param $login Boolean whether or not the role will be allowed to login - * @param $connlimit Number of concurrent connections the role can make - * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire - * @param $memberof (array) Roles to which the role will be immediately added as a new member - * @param $members (array) Roles which are automatically added as members of the role - * @param $adminmembers (array) Roles which are automatically added as admin members of the role - * @param $memberofold (array) Original roles whose the role belongs to - * @param $membersold (array) Original roles that are members of the role - * @param $adminmembersold (array) Original roles that are admin members of the role - * @param $newrolename The new name of the role + * Alters a database + * the multiple return vals are for postgres 8+ which support more functionality in alter database + * @param $dbName The name of the database + * @param $newName new name for the database + * @param $newOwner The new owner for the database * @return 0 success * @return -1 transaction error - * @return -2 set role attributes error + * @return -2 owner error * @return -3 rename error */ - function setRenameRole($rolename, $password, $superuser, $createdb, $createrole, $inherits, $login, $connlimit, $expiry, $memberof, $members, $adminmembers, $memberofold, $membersold, $adminmembersold, $newrolename) { + function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') { + $this->clean($dbName); + $this->clean($newName); + $this->clean($newOwner); + //ignore $comment, not supported pre 8.2 $status = $this->beginTransaction(); - if ($status != 0) return -1; - - $status = $this->setRole($rolename, $password, $superuser, $createdb, $createrole, $inherits, $login, $connlimit, $expiry, $memberof, $members, $adminmembers, $memberofold, $membersold, $adminmembersold); if ($status != 0) { $this->rollbackTransaction(); - return -2; + return -1; } - if ($rolename != $newrolename){ - $status = $this->renameRole($rolename, $newrolename); + if ($dbName != $newName) { + $status = $this->alterDatabaseRename($dbName, $newName); if ($status != 0) { $this->rollbackTransaction(); return -3; } } - return $this->endTransaction(); - } - - /** - * Adjusts a role's info - * @param $rolename The name of the role to adjust - * @param $password A password for the role - * @param $superuser Boolean whether or not the role is a superuser - * @param $createdb Boolean whether or not the role can create databases - * @param $createrole Boolean whether or not the role can create other roles - * @param $inherits Boolean whether or not the role inherits the privileges from parent roles - * @param $login Boolean whether or not the role will be allowed to login - * @param $connlimit Number of concurrent connections the role can make - * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire - * @param $memberof (array) Roles to which the role will be immediately added as a new member - * @param $members (array) Roles which are automatically added as members of the role - * @param $adminmembers (array) Roles which are automatically added as admin members of the role - * @param $memberofold (array) Original roles whose the role belongs to - * @param $membersold (array) Original roles that are members of the role - * @param $adminmembersold (array) Original roles that are admin members of the role - * @return 0 success - */ - function setRole($rolename, $password, $superuser, $createdb, $createrole, $inherits, $login, $connlimit, $expiry, $memberof, $members, $adminmembers, $memberofold, $membersold, $adminmembersold) { - $enc = $this->_encryptPassword($rolename, $password); - $this->fieldClean($rolename); - $this->clean($enc); - $this->clean($connlimit); - $this->clean($expiry); - $this->fieldArrayClean($memberof); - $this->fieldArrayClean($members); - $this->fieldArrayClean($adminmembers); - - $sql = "ALTER ROLE \"{$rolename}\""; - if ($password != '') $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; - $sql .= ($superuser) ? ' SUPERUSER' : ' NOSUPERUSER'; - $sql .= ($createdb) ? ' CREATEDB' : ' NOCREATEDB'; - $sql .= ($createrole) ? ' CREATEROLE' : ' NOCREATEROLE'; - $sql .= ($inherits) ? ' INHERIT' : ' NOINHERIT'; - $sql .= ($login) ? ' LOGIN' : ' NOLOGIN'; - if ($connlimit != '') $sql .= " CONNECTION LIMIT {$connlimit}"; else $sql .= ' CONNECTION LIMIT -1'; - if ($expiry != '') $sql .= " VALID UNTIL '{$expiry}'"; else $sql .= " VALID UNTIL 'infinity'"; - - $status = $this->execute($sql); - - if ($status != 0) return -1; - - //memberof - $old = explode(',', $memberofold); - foreach ($memberof as $m) { - if (!in_array($m, $old)) { - $status = $this->grantRole($m, $rolename); - if ($status != 0) return -1; - } - } - if($memberofold) - { - foreach ($old as $o) { - if (!in_array($o, $memberof)) { - $status = $this->revokeRole($o, $rolename, 0, 'CASCADE'); - if ($status != 0) return -1; - } - } - } - - //members - $old = explode(',', $membersold); - foreach ($members as $m) { - if (!in_array($m, $old)) { - $status = $this->grantRole($rolename, $m); - if ($status != 0) return -1; - } - } - if($membersold) - { - foreach ($old as $o) { - if (!in_array($o, $members)) { - $status = $this->revokeRole($rolename, $o, 0, 'CASCADE'); - if ($status != 0) return -1; - } - } - } - - //adminmembers - $old = explode(',', $adminmembersold); - foreach ($adminmembers as $m) { - if (!in_array($m, $old)) { - $status = $this->grantRole($rolename, $m, 1); - if ($status != 0) return -1; - } - } - if($adminmembersold) - { - foreach ($old as $o) { - if (!in_array($o, $adminmembers)) { - $status = $this->revokeRole($rolename, $o, 1, 'CASCADE'); - if ($status != 0) return -1; - } - } - } - - return $status; - } - - /** - * Renames a role - * @param $rolename The name of the role to rename - * @param $newrolename The new name of the role - * @return 0 success - */ - function renameRole($rolename, $newrolename){ - $this->fieldClean($rolename); - $this->fieldClean($newrolename); - - $sql = "ALTER ROLE \"{$rolename}\" RENAME TO \"{$newrolename}\""; - - return $this->execute($sql); + $status = $this->alterDatabaseOwner($newName, $newOwner); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; } - - /** - * Grants membership in a role - * @param $role The name of the target role - * @param $rolename The name of the role that will belong to the target role - * @param $admin (optional) Flag to grant the admin option - * @return 0 success - */ - function grantRole($role, $rolename, $admin=0) { - $this->fieldClean($role); - $this->fieldClean($rolename); - - $sql = "GRANT \"{$role}\" TO \"{$rolename}\""; - if($admin == 1) $sql .= ' WITH ADMIN OPTION'; - - return $this->execute($sql); + return $this->endTransaction(); } - - /** - * Revokes membership in a role - * @param $role The name of the target role - * @param $rolename The name of the role that will not belong to the target role - * @param $admin (optional) Flag to revoke only the admin option - * @param $type (optional) Type of revoke: RESTRICT | CASCADE - * @return 0 success - */ - function revokeRole($role, $rolename, $admin = 0, $type = 'RESTRICT') { - $this->fieldClean($role); - $this->fieldClean($rolename); - $sql = "REVOKE "; - if($admin == 1) $sql .= 'ADMIN OPTION FOR '; - $sql .= "\"{$role}\" FROM \"{$rolename}\" {$type}"; - - return $this->execute($sql); - } + // Constraint functions /** - * Changes a role's password - * @param $rolename The role name - * @param $password The new password - * @return 0 success + * Returns a list of all constraints on a table, + * including constraint name, definition, related col and referenced namespace, + * table and col if needed + * @param $table the table where we are looking for fk + * @return a recordset */ - function changePassword($rolename, $password) { - $enc = $this->_encryptPassword($rolename, $password); - $this->fieldClean($rolename); - $this->clean($enc); - - $sql = "ALTER ROLE \"{$rolename}\" WITH ENCRYPTED PASSWORD '{$enc}'"; - - return $this->execute($sql); - } + function getConstraintsWithFields($table) { + global $data; - /** - * Returns all role names which the role belongs to - * @param $rolename The role name - * @return All role names - */ - function getMemberOf($rolename) { - $this->clean($rolename); + $data->clean($table); - $sql = "SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M WHERE R.oid=M.roleid - AND member IN (SELECT oid FROM pg_catalog.pg_roles WHERE rolname='{$rolename}') ORDER BY rolname"; + // get the max number of col used in a constraint for the table + $sql = "SELECT DISTINCT + max(SUBSTRING(array_dims(c.conkey) FROM '^\\\[.*:(.*)\\\]$')) as nb + FROM + pg_catalog.pg_constraint AS c + JOIN pg_catalog.pg_class AS r ON (c.conrelid = r.oid) + JOIN pg_catalog.pg_namespace AS ns ON r.relnamespace=ns.oid + WHERE + r.relname = '$table' AND ns.nspname='". $this->_schema ."'"; - return $this->selectSet($sql); - } + $rs = $this->selectSet($sql); - /** - * Returns all role names that are members of a role - * @param $rolename The role name - * @param $admin (optional) Find only admin members - * @return All role names - */ - function getMembers($rolename, $admin = 'f') { - $this->clean($rolename); + if ($rs->EOF) $max_col = 0; + else $max_col = $rs->fields['nb']; - $sql = "SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M WHERE R.oid=M.member AND admin_option='{$admin}' - AND roleid IN (SELECT oid FROM pg_catalog.pg_roles WHERE rolname='{$rolename}') ORDER BY rolname"; + $sql = ' + SELECT + c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid,true) AS consrc, + ns1.nspname as p_schema, r1.relname as p_table, ns2.nspname as f_schema, + r2.relname as f_table, f1.attname as p_field, f2.attname as f_field, + pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment + FROM + pg_catalog.pg_constraint AS c + JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid) + JOIN pg_catalog.pg_attribute AS f1 ON (f1.attrelid=r1.oid AND (f1.attnum=c.conkey[1]'; + for ($i = 2; $i <= $rs->fields['nb']; $i++) { + $sql.= " OR f1.attnum=c.conkey[$i]"; + } + $sql.= ')) + JOIN pg_catalog.pg_namespace AS ns1 ON r1.relnamespace=ns1.oid + LEFT JOIN ( + pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid) + ) ON (c.confrelid=r2.oid) + LEFT JOIN pg_catalog.pg_attribute AS f2 ON + (f2.attrelid=r2.oid AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)'; + for ($i = 2; $i <= $rs->fields['nb']; $i++) + $sql.= "OR (c.confkey[$i]=f2.attnum AND c.conkey[$i]=f1.attnum)"; + + $sql .= sprintf(")) + WHERE + r1.relname = '%s' AND ns1.nspname='%s' + ORDER BY 1", $table, $this->_schema); return $this->selectSet($sql); } - // Database methods + // Tablespace functions /** - * Returns all available process information. + * Retrieves a tablespace's information * @return A recordset */ - function getAutovacuum() { - $sql = "SELECT vacrelid, nspname, relname, enabled, vac_base_thresh, vac_scale_factor, anl_base_thresh, anl_scale_factor, vac_cost_delay, vac_cost_limit - FROM pg_autovacuum join pg_class on (oid=vacrelid) join pg_namespace on (oid=relnamespace) ORDER BY nspname, relname"; - - return $this->selectSet($sql); - } - - // Table methods - - /** - * Protected method which alter a table - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $tblrs The table recordSet returned by getTable() - * @param $name The new name for the table - * @param $owner The new owner for the table - * @param $schema The new schema for the table - * @param $comment The comment on the table - * @param $tablespace The new tablespace for the table ('' means leave as is) - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 tablespace error - * @return -7 schema error - */ - /* protected */ - function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) { - - $status = parent::_alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace); - if ($status != 0) - return $status; - - // if name != tablename, table has been renamed in parent - $tablename = ($tblrs->fields['relname'] == $name) ? $tblrs->fields['relname'] : $name; - - $this->fieldClean($schema); - - // Schema - if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) { - - // If tablespace has been changed, then do the alteration. We - // don't want to do this unnecessarily. - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$tablename}\" SET SCHEMA \"{$schema}\""; - - $status = $this->execute($sql); - if ($status != 0) return -7; - } - - return 0; - } - - /** - * Enables a trigger - * @param $tgname The name of the trigger to enable - * @param $table The table in which to enable the trigger - * @return 0 success - */ - function enableTrigger($tgname, $table) { - $this->fieldClean($tgname); - $this->fieldClean($table); - - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" ENABLE TRIGGER \"{$tgname}\""; - - return $this->execute($sql); - } - - /** - * Disables a trigger - * @param $tgname The name of the trigger to disable - * @param $table The table in which to disable the trigger - * @return 0 success - */ - function disableTrigger($tgname, $table) { - $this->fieldClean($tgname); - $this->fieldClean($table); + function getTablespace($spcname) { + $this->clean($spcname); - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\""; + $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation + FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'"; - return $this->execute($sql); - } - - // View functions - - /** - * Protected method which alter a view - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $vwrs The view recordSet returned by getView() - * @param $name The new name for the view - * @param $owner The new owner for the view - * @param $comment The comment on the view - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 schema error - */ - /*protected*/ - function _alterView($vwrs, $name, $owner, $schema, $comment) { - - $status = parent::_alterView($vwrs, $name, $owner, $schema, $comment); - if ($status != 0) - return $status; - - $this->fieldClean($name); - // if name is not empty, view has been renamed in parent - $view = (!empty($name)) ? $name : $tblrs->fields['relname']; - - // Schema - $this->fieldClean($schema); - if (!empty($schema) && ($vwrs->fields['nspname'] != $schema)) { - - // If tablespace has been changed, then do the alteration. We - // don't want to do this unnecessarily. - $sql = "ALTER TABLE \"{$this->_schema}\".\"{$view}\" SET SCHEMA \"{$schema}\""; - - $status = $this->execute($sql); - if ($status != 0) return -6; - } - - return 0; + return $this->selectSet($sql); } - // Sequence methods - /** - * Protected method which alter a sequence - * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION - * @param $seqrs The sequence recordSet returned by getSequence() - * @param $name The new name for the sequence - * @param $comment The comment on the sequence - * @param $owner The new owner for the sequence - * @param $schema The new schema for the sequence - * @param $increment The increment - * @param $minvalue The min value - * @param $maxvalue The max value - * @param $startvalue The starting value - * @param $cachevalue The cache value - * @param $cycledvalue True if cycled, false otherwise - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 get sequence error - * @return -7 schema error + * Retrieves information for all tablespaces + * @param $all Include all tablespaces (necessary when moving objects back to the default space) + * @return A recordset */ - /*protected*/ - function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue) { - - $status = parent::_alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $startvalue, $cachevalue, $cycledvalue); - if ($status != 0) - return $status; + function getTablespaces($all = false) { + global $conf; - // if name != seqname, sequence has been renamed in parent - $sequence = ($seqrs->fields['seqname'] == $name) ? $seqrs->fields['seqname'] : $name; - - $this->clean($schema); - if ($seqrs->fields['nspname'] != $schema) { - $sql = "ALTER SEQUENCE \"{$this->_schema}\".\"{$sequence}\" SET SCHEMA $schema"; - $status = $this->execute($sql); - if ($status != 0) - return -7; - } + $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation + FROM pg_catalog.pg_tablespace"; - return 0; + if (!$conf['show_system'] && !$all) { + $sql .= " WHERE spcname NOT LIKE 'pg\\\\_%'"; } - // Aggregate functions - - /** - * Gets all information for an aggregate - * @param $name The name of the aggregate - * @param $basetype The input data type of the aggregate - * @return A recordset - */ - function getAggregate($name, $basetype) { - $this->fieldclean($name); - $this->fieldclean($basetype); - - $sql = " - SELECT p.proname, CASE p.proargtypes[0] - WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL - ELSE pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes, - a.aggtransfn, format_type(a.aggtranstype, NULL) AS aggstype, a.aggfinalfn, - a.agginitval, a.aggsortop, u.usename, pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment - FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a - WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid - AND p.proisagg AND n.nspname='{$this->_schema}' - AND p.proname='" . $name . "' - AND CASE p.proargtypes[0] - WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN '' - ELSE pg_catalog.format_type(p.proargtypes[0], NULL) - END ='" . $basetype . "'"; + $sql .= " ORDER BY spcname"; return $this->selectSet($sql); } // Capabilities - function hasServerAdminFuncs() { return true; } - function hasRoles() { return true; } - function hasAutovacuum() { return true; } - function hasPreparedXacts() { return true; } - function hasDisableTriggers() { return true; } - function hasFunctionAlterSchema() { return true; } - function hasAlterTableSchema() { return true; } - function hasSequenceAlterSchema() { return true; } - function hasAggregateSortOp() { return true; } + + function hasCreateTableLikeWithConstraints() {return false;} + function hasSharedComments() {return false;} } ?> diff --git a/classes/database/Postgres82.php b/classes/database/Postgres82.php index 5615a663..89de0399 100644 --- a/classes/database/Postgres82.php +++ b/classes/database/Postgres82.php @@ -6,54 +6,24 @@ * $Id: Postgres82.php,v 1.10 2007/12/28 16:21:25 ioguix Exp $ */ -include_once('./classes/database/Postgres81.php'); +include_once('./classes/database/Postgres.php'); -class Postgres82 extends Postgres81 { +class Postgres82 extends Postgres { var $major_version = 8.2; - // Array of allowed index types - var $typIndexes = array('BTREE', 'RTREE', 'GIST', 'GIN', 'HASH'); - - // Last oid assigned to a system object - var $_lastSystemOID = 17231; - - // List of all legal privileges that can be applied to different types - // of objects. - var $privlist = array( - 'table' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), - 'view' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'), - 'sequence' => array('SELECT', 'UPDATE', 'ALL PRIVILEGES'), - 'database' => array('CREATE', 'TEMPORARY', 'CONNECT', 'ALL PRIVILEGES'), - 'function' => array('EXECUTE', 'ALL PRIVILEGES'), - 'language' => array('USAGE', 'ALL PRIVILEGES'), - 'schema' => array('CREATE', 'USAGE', 'ALL PRIVILEGES'), - 'tablespace' => array('CREATE', 'ALL PRIVILEGES') - ); - - // List of characters in acl lists and the privileges they - // refer to. - var $privmap = array( - 'r' => 'SELECT', - 'w' => 'UPDATE', - 'a' => 'INSERT', - 'd' => 'DELETE', - 'R' => 'RULE', - 'x' => 'REFERENCES', - 't' => 'TRIGGER', - 'X' => 'EXECUTE', - 'U' => 'USAGE', - 'C' => 'CREATE', - 'T' => 'TEMPORARY', - 'c' => 'CONNECT' - ); + // Select operators + var $selectOps = array('=' => 'i', '!=' => 'i', '<' => 'i', '>' => 'i', '<=' => 'i', '>=' => 'i', '<<' => 'i', '>>' => 'i', '<<=' => 'i', '>>=' => 'i', + 'LIKE' => 'i', 'NOT LIKE' => 'i', 'ILIKE' => 'i', 'NOT ILIKE' => 'i', 'SIMILAR TO' => 'i', + 'NOT SIMILAR TO' => 'i', '~' => 'i', '!~' => 'i', '~*' => 'i', '!~*' => 'i', + 'IS NULL' => 'p', 'IS NOT NULL' => 'p', 'IN' => 'x', 'NOT IN' => 'x'); /** * Constructor * @param $conn The database connection */ function Postgres82($conn) { - $this->Postgres81($conn); + $this->Postgres($conn); } // Help functions @@ -64,205 +34,240 @@ class Postgres82 extends Postgres81 { } // Database functions + /** - * Return all database available on the server - * @return A list of databases, sorted alphabetically + * Returns table locks information in the current database + * @return A recordset */ - function getDatabases($currentdatabase = NULL) { - global $conf, $misc; - - $server_info = $misc->getServerInfo(); - - if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser($server_info['username'])) { - $username = $server_info['username']; - $this->clean($username); - $clause = " AND pr.rolname='{$username}'"; - } - else $clause = ''; - - if ($currentdatabase != NULL) - $orderby = "ORDER BY pdb.datname = '{$currentdatabase}' DESC, pdb.datname"; - else - $orderby = "ORDER BY pdb.datname"; + function getLocks() { + global $conf; if (!$conf['show_system']) - $where = ' AND NOT pdb.datistemplate'; + $where = "AND pn.nspname NOT LIKE 'pg\\\\_%'"; else - $where = ' AND pdb.datallowconn'; - - $sql = "SELECT pdb.datname AS datname, pr.rolname AS datowner, pg_encoding_to_char(encoding) AS datencoding, - (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pdb.oid=pd.objoid) AS datcomment, - (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace, - pg_catalog.pg_database_size(pdb.oid) as dbsize - FROM pg_catalog.pg_database pdb LEFT JOIN pg_catalog.pg_roles pr ON (pdb.datdba = pr.oid) - WHERE true - {$where} - {$clause} - {$orderby}"; + $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; + + $sql = "SELECT pn.nspname, pc.relname AS tablename, pl.transaction, pl.pid, pl.mode, pl.granted + FROM pg_catalog.pg_locks pl, pg_catalog.pg_class pc, pg_catalog.pg_namespace pn + WHERE pl.relation = pc.oid AND pc.relnamespace=pn.oid {$where} + ORDER BY nspname,tablename"; return $this->selectSet($sql); } + // Sequence functions + /** - * Alters a database - * the multiple return vals are for postgres 8+ which support more functionality in alter database - * @param $dbName The name of the database - * @param $newName new name for the database - * @param $newOwner The new owner for the database + * Rename a sequence + * @param $seqrs The sequence RecordSet returned by getSequence() + * @param $name The new name for the sequence * @return 0 success - * @return -1 transaction error - * @return -2 owner error - * @return -3 rename error - * @return -4 comment error */ - function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') - { - $this->clean($dbName); - $this->clean($newName); - $this->clean($newOwner); - $this->clean($comment); - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; + function alterSequenceName($seqrs, $name) { + if (!empty($name) && ($seqrs->fields['seqname'] != $name)) { + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$seqrs->fields['seqname']}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); + if ($status == 0) + $seqrs->fields['seqname'] = $name; + else + return $status; } - - if ($dbName != $newName) { - $status = $this->alterDatabaseRename($dbName, $newName); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - } - - $status = $this->alterDatabaseOwner($newName, $newOwner); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; + return 0; } - if (trim($comment) != '' ) { - $status = $this->setComment('DATABASE', $dbName, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - } - return $this->endTransaction(); - } + // View functions /** - * Return the database comment of a db from the shared description table - * @param string $database the name of the database to get the comment for - * @return recordset of the db comment info + * Rename a view + * @param $vwrs The view recordSet returned by getView() + * @param $name The new view's name + * @return -1 Failed + * @return 0 success */ - function getDatabaseComment($database) { - $this->clean($database); - $sql = "SELECT description FROM pg_catalog.pg_database JOIN pg_catalog.pg_shdescription ON (oid=objoid) WHERE pg_database.datname = '{$database}' "; - return $this->selectSet($sql); + function alterViewName($vwrs, $name) { + // Rename (only if name has changed) + if (!empty($name) && ($name != $vwrs->fields['relname'])) { + $sql = "ALTER TABLE \"{$this->_schema}\".\"{$vwrs->fields['relname']}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); + if ($status == 0) + $vwrs->fields['relname'] = $name; + else + return $status; + } + return 0; } - // Tablespace functions + // Trigger functions /** - * Retrieves information for all tablespaces - * @param $all Include all tablespaces (necessary when moving objects back to the default space) + * Grabs a list of triggers on a table + * @param $table The name of a table whose triggers to retrieve * @return A recordset */ - function getTablespaces($all = false) { - global $conf; + function getTriggers($table = '') { + $this->clean($table); + + $sql = "SELECT + t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef, t.tgenabled, p.oid AS prooid, + p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, + ns.nspname AS pronamespace + FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns + WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' + AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}')) + AND (NOT tgisconstraint OR NOT EXISTS + (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c + ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) + WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f')) + AND p.oid=t.tgfoid + AND p.pronamespace = ns.oid"; - $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation, - (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid) AS spccomment - FROM pg_catalog.pg_tablespace"; - - if (!$conf['show_system'] && !$all) { - $sql .= " WHERE spcname NOT LIKE 'pg\\\\_%'"; + return $this->selectSet($sql); } - $sql .= " ORDER BY spcname"; + // Function functions + + /** + * Returns all details for a particular function + * @param $func The name of the function to retrieve + * @return Function info + */ + function getFunction($function_oid) { + $this->clean($function_oid); + + $sql = "SELECT + pc.oid AS prooid, + proname, + pg_catalog.pg_get_userbyid(proowner) AS proowner, + nspname as proschema, + lanname as prolanguage, + pg_catalog.format_type(prorettype, NULL) as proresult, + prosrc, + probin, + proretset, + proisstrict, + provolatile, + prosecdef, + pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments, + proargnames AS proargnames, + pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment + FROM + pg_catalog.pg_proc pc, pg_catalog.pg_language pl, pg_catalog.pg_namespace pn + WHERE + pc.oid = '{$function_oid}'::oid + AND pc.prolang = pl.oid + AND pc.pronamespace = pn.oid + "; return $this->selectSet($sql); } /** - * Retrieves a tablespace's information - * @return A recordset + * Creates a new function. + * @param $funcname The name of the function to create + * @param $args A comma separated string of types + * @param $returns The return type + * @param $definition The definition for the new function + * @param $language The language the function is written for + * @param $flags An array of optional flags + * @param $setof True if it returns a set, false otherwise + * @param $replace (optional) True if OR REPLACE, false for normal + * @return 0 success */ - function getTablespace($spcname) { - $this->clean($spcname); + function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $replace = false) { + $this->fieldClean($funcname); + $this->clean($args); + $this->clean($language); + $this->arrayClean($flags); + + $sql = "CREATE"; + if ($replace) $sql .= " OR REPLACE"; + $sql .= " FUNCTION \"{$this->_schema}\".\"{$funcname}\" ("; + + if ($args != '') + $sql .= $args; + + // For some reason, the returns field cannot have quotes... + $sql .= ") RETURNS "; + if ($setof) $sql .= "SETOF "; + $sql .= "{$returns} AS "; + + if (is_array($definition)) { + $this->arrayClean($definition); + $sql .= "'" . $definition[0] . "'"; + if ($definition[1]) { + $sql .= ",'" . $definition[1] . "'"; + } + } else { + $this->clean($definition); + $sql .= "'" . $definition . "'"; + } - $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation, - (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid) AS spccomment - FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'"; + $sql .= " LANGUAGE \"{$language}\""; - return $this->selectSet($sql); + // Add flags + foreach ($flags as $v) { + // Skip default flags + if ($v == '') continue; + else $sql .= "\n{$v}"; + } + + return $this->execute($sql); } - // Constraints methods + // Index functions /** - * Returns a list of all constraints on a table, - * including constraint name, definition, related col and referenced namespace, - * table and col if needed - * @param $table the table where we are looking for fk - * @return a recordset + * Clusters an index + * @param $index The name of the index + * @param $table The table the index is on + * @return 0 success */ - function getConstraintsWithFields($table) { - global $data; - - $data->clean($table); + function clusterIndex($index, $table) { + $this->fieldClean($index); + $this->fieldClean($table); - // get the max number of col used in a constraint for the table - $sql = "SELECT DISTINCT - max(SUBSTRING(array_dims(c.conkey) FROM E'^\\\[.*:(.*)\\\]$')) as nb - FROM - pg_catalog.pg_constraint AS c - JOIN pg_catalog.pg_class AS r ON (c.conrelid = r.oid) - JOIN pg_catalog.pg_namespace AS ns ON r.relnamespace=ns.oid - WHERE - r.relname = '$table' AND ns.nspname='". $this->_schema ."'"; + // We don't bother with a transaction here, as there's no point rolling + // back an expensive cluster if a cheap analyze fails for whatever reason + $sql = "CLUSTER \"{$index}\" ON \"{$this->_schema}\".\"{$table}\""; - $rs = $this->selectSet($sql); + return $this->execute($sql); + } - if ($rs->EOF) $max_col = 0; - else $max_col = $rs->fields['nb']; + // Operator Class functions - $sql = ' + /** + * Gets all opclasses + * @return A recordset + */ + function getOpClasses() { + $sql = " SELECT - c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid, true) AS consrc, - ns1.nspname as p_schema, r1.relname as p_table, ns2.nspname as f_schema, - r2.relname as f_table, f1.attname as p_field, f2.attname as f_field, - pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment + pa.amname, + po.opcname, + po.opcintype::pg_catalog.regtype AS opcintype, + po.opcdefault, + pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment FROM - pg_catalog.pg_constraint AS c - JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid) - JOIN pg_catalog.pg_attribute AS f1 ON (f1.attrelid=r1.oid AND (f1.attnum=c.conkey[1]'; - for ($i = 2; $i <= $rs->fields['nb']; $i++) { - $sql.= " OR f1.attnum=c.conkey[$i]"; - } - $sql.= ')) - JOIN pg_catalog.pg_namespace AS ns1 ON r1.relnamespace=ns1.oid - LEFT JOIN ( - pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid) - ) ON (c.confrelid=r2.oid) - LEFT JOIN pg_catalog.pg_attribute AS f2 ON - (f2.attrelid=r2.oid AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)'; - for ($i = 2; $i <= $rs->fields['nb']; $i++) - $sql.= "OR (c.confkey[$i]=f2.attnum AND c.conkey[$i]=f1.attnum)"; - - $sql .= sprintf(")) + pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn WHERE - r1.relname = '%s' AND ns1.nspname='%s' - ORDER BY 1", $table, $this->_schema); + po.opcamid=pa.oid + AND po.opcnamespace=pn.oid + AND pn.nspname='{$this->_schema}' + ORDER BY 1,2 + "; return $this->selectSet($sql); } // Capabilities - function hasSharedComments() {return true;} - function hasCreateTableLikeWithConstraints() {return true;} + + function hasCreateTableLikeWithIndexes() {return false;} + function hasEnumTypes() {return false;} + function hasFTS() {return false;} + function hasFunctionCosting() {return false;} + function hasFunctionGUC() {return false;} + function hasVirtualTransactionId() {return false;} + } ?> diff --git a/classes/database/Postgres83.php b/classes/database/Postgres83.php deleted file mode 100644 index e999868b..00000000 --- a/classes/database/Postgres83.php +++ /dev/null @@ -1,798 +0,0 @@ -<?php - -/** - * PostgreSQL 8.3 support - * - * $Id: Postgres83.php,v 1.18 2008/03/17 21:35:48 ioguix Exp $ - */ - -include_once('./classes/database/Postgres82.php'); - -class Postgres83 extends Postgres82 { - - var $major_version = 8.3; - - // Last oid assigned to a system object - var $_lastSystemOID = 17231; // need to confirm this once we get to beta!!! - - // Select operators - var $selectOps = array('=' => 'i', '!=' => 'i', '<' => 'i', '>' => 'i', '<=' => 'i', '>=' => 'i', - '<<' => 'i', '>>' => 'i', '<<=' => 'i', '>>=' => 'i', - 'LIKE' => 'i', 'NOT LIKE' => 'i', 'ILIKE' => 'i', 'NOT ILIKE' => 'i', 'SIMILAR TO' => 'i', - 'NOT SIMILAR TO' => 'i', '~' => 'i', '!~' => 'i', '~*' => 'i', '!~*' => 'i', - 'IS NULL' => 'p', 'IS NOT NULL' => 'p', 'IN' => 'x', 'NOT IN' => 'x', - '@@' => 'i', '@@@' => 'i', '@>' => 'i', '<@' => 'i', - '@@ to_tsquery' => 't', '@@@ to_tsquery' => 't', '@> to_tsquery' => 't', '<@ to_tsquery' => 't', - '@@ plainto_tsquery' => 't', '@@@ plainto_tsquery' => 't', '@> plainto_tsquery' => 't', '<@ plainto_tsquery' => 't'); - - /** - * Constructor - * @param $conn The database connection - */ - function Postgres83($conn) { - $this->Postgres82($conn); - } - - // Help functions - - function getHelpPages() { - include_once('./help/PostgresDoc83.php'); - return $this->help_page; - } - - // Schemas functions - /** - * Returns table locks information in the current database - * @return A recordset - */ - - function getLocks() { - global $conf; - - if (!$conf['show_system']) - $where = "AND pn.nspname NOT LIKE 'pg\\\\_%'"; - else - $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; - - $sql = "SELECT - pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction, - (select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid' - and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction - FROM - pg_catalog.pg_locks pl, - pg_catalog.pg_class pc, - pg_catalog.pg_namespace pn - WHERE - pl.relation = pc.oid - AND - pc.relnamespace=pn.oid - {$where} - ORDER BY - pid,nspname,tablename"; - - return $this->selectSet($sql); - } - - // Views functions - - /** - * Rename a view - * @param $view The current view's name - * @param $name The new view's name - * @return -1 Failed - * @return 0 success - */ - function renameView($view, $name) { - $this->fieldClean($name); - $this->fieldClean($view); - $sql = "ALTER VIEW \"{$this->_schema}\".\"{$view}\" RENAME TO \"{$name}\""; - if ($this->execute($sql) != 0) - return -1; - return 0; - } - - // Index functions - - /** - * Clusters an index - * @param $index The name of the index - * @param $table The table the index is on - * @return 0 success - */ - function clusterIndex($index, $table) { - - $this->fieldClean($index); - $this->fieldClean($table); - - // We don't bother with a transaction here, as there's no point rolling - // back an expensive cluster if a cheap analyze fails for whatever reason - $sql = "CLUSTER \"{$this->_schema}\".\"{$table}\" USING \"{$index}\""; - - return $this->execute($sql); - } - - // Sequence functions - - /** - * Rename a sequence - * @param $sequence The sequence name - * @param $name The new name for the sequence - * @return 0 success - */ - function renameSequence($sequence, $name) { - $this->fieldClean($name); - $this->fieldClean($sequence); - - $sql = "ALTER SEQUENCE \"{$this->_schema}\".\"{$sequence}\" RENAME TO \"{$name}\""; - return $this->execute($sql); - } - - // Operator Class functions - - /** - * Gets all opclasses - * - * * @return A recordset - - */ - - function getOpClasses() { - - $sql = " - SELECT - pa.amname, po.opcname, - po.opcintype::pg_catalog.regtype AS opcintype, - po.opcdefault, - pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment - FROM - pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn - WHERE - po.opcmethod=pa.oid - AND po.opcnamespace=pn.oid - AND pn.nspname='{$this->_schema}' - ORDER BY 1,2 - "; - - return $this->selectSet($sql); - } - - // FTS functions - - /** - * Creates a new FTS configuration. - * @param string $cfgname The name of the FTS configuration to create - * @param string $parser The parser to be used in new FTS configuration - * @param string $locale Locale of the FTS configuration - * @param string $template The existing FTS configuration to be used as template for the new one - * @param string $withmap Should we copy whole map of existing FTS configuration to the new one - * @param string $makeDefault Should this configuration be the default for locale given - * @param string $comment If omitted, defaults to nothing - * @return 0 success - */ - function createFtsConfiguration($cfgname, $parser = '', $template = '', $comment = '') { - $this->fieldClean($cfgname); - - $sql = "CREATE TEXT SEARCH CONFIGURATION \"{$cfgname}\" ("; - if ($parser != '') { - $this->fieldClean($parser['schema']); - $this->fieldClean($parser['parser']); - $parser = "\"{$parser['schema']}\".\"{$parser['parser']}\""; - $sql .= " PARSER = {$parser}"; - } - if ($template != '') { - $this->fieldClean($template); - $sql .= " COPY = \"{$template}\""; - } - $sql .= ")"; - - if ($comment != '') { - $status = $this->beginTransaction(); - if ($status != 0) return -1; - } - - // Create the FTS configuration - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Set the comment - if ($comment != '') { - $this->clean($comment); - $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - return $this->endTransaction(); - } - - return 0; - } - - - /** - * Returns all FTS configurations available - */ - function getFtsConfigurations() { - $sql = "SELECT - n.nspname as schema, - c.cfgname as name, - pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment - FROM - pg_catalog.pg_ts_config c - JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace - WHERE - pg_catalog.pg_ts_config_is_visible(c.oid) - ORDER BY - schema, name"; - return $this->selectSet($sql); - } - - /** - * Return all information related to a FTS configuration - * @param $ftscfg The name of the FTS configuration - * @return FTS configuration information - */ - function getFtsConfigurationByName($ftscfg) { - $this->clean($ftscfg); - $sql = "SELECT - n.nspname as schema, - c.cfgname as name, - p.prsname as parser, - c.cfgparser as parser_id, - pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment - FROM pg_catalog.pg_ts_config c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace - LEFT JOIN pg_catalog.pg_ts_parser p ON p.oid = c.cfgparser - WHERE pg_catalog.pg_ts_config_is_visible(c.oid) - AND c.cfgname = '{$ftscfg}'"; - - return $this->selectSet($sql); - } - - - /** - * Returns the map of FTS configuration given (list of mappings (tokens) and their processing dictionaries) - * - * @param string $ftscfg Name of the FTS configuration - */ - function getFtsConfigurationMap($ftscfg) { - $this->fieldClean($ftscfg); - $getOidSql = "SELECT oid FROM pg_catalog.pg_ts_config WHERE cfgname = '{$ftscfg}'"; - $oidSet = $this->selectSet($getOidSql); - $oid = $oidSet->fields['oid']; - - $sql = " SELECT - (SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name, - (SELECT t.description FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS description, - c.cfgname AS cfgname - ,n.nspname ||'.'|| d.dictname as dictionaries - FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d, pg_catalog.pg_namespace n - WHERE c.oid = {$oid} AND m.mapcfg = c.oid and m.mapdict = d.oid and d.dictnamespace = n.oid - ORDER BY name - "; - return $this->selectSet($sql); - } - - /** - * Returns all FTS parsers available - */ - function getFtsParsers() { - $sql = "SELECT - n.nspname as schema, - p.prsname as name, - pg_catalog.obj_description(p.oid, 'pg_ts_parser') as comment - FROM pg_catalog.pg_ts_parser p - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace WHERE pg_catalog.pg_ts_parser_is_visible(p.oid) - ORDER BY schema, name"; - return $this->selectSet($sql); - } - - /** - * Returns all FTS dictionaries available - */ - function getFtsDictionaries() { - $sql = "SELECT - n.nspname as schema, - d.dictname as name, - pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment - FROM pg_catalog.pg_ts_dict d - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace - WHERE pg_catalog.pg_ts_dict_is_visible(d.oid) - ORDER BY schema, name;"; - return $this->selectSet($sql); - } - - /** - * Returns all FTS dictionary templates available - */ - function getFtsDictionaryTemplates() { - $sql = "SELECT - n.nspname as schema, - t.tmplname as name, - ( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname FROM - pg_catalog.pg_proc p - LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace - WHERE t.tmplinit = p.oid ) AS init, - ( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname FROM - pg_catalog.pg_proc p - LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace - WHERE t.tmpllexize = p.oid ) AS lexize, - pg_catalog.obj_description(t.oid, 'pg_ts_template') as comment - FROM pg_catalog.pg_ts_template t - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace - WHERE pg_catalog.pg_ts_template_is_visible(t.oid) - ORDER BY schema, name;"; - return $this->selectSet($sql); - } - - /** - * Drops FTS coniguration - */ - function dropFtsConfiguration($ftscfg, $cascade) { - $this->fieldClean($ftscfg); - - $sql = "DROP TEXT SEARCH CONFIGURATION \"{$ftscfg}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } - - /** - * Drops FTS dictionary - * - * @todo Support of dictionary templates dropping - */ - function dropFtsDictionary($ftsdict, $cascade = true) { - $this->fieldClean($ftsdict); - - $sql = "DROP TEXT SEARCH DICTIONARY"; - $sql .= " \"{$ftsdict}\""; - if ($cascade) $sql .= " CASCADE"; - - return $this->execute($sql); - } - - /** - * Alters FTS configuration - */ - function updateFtsConfiguration($cfgname, $comment, $name) { - $this->fieldClean($cfgname); - $this->fieldClean($name); - $this->clean($comment); - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Only if the name has changed - if ($name != $cfgname) { - $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$cfgname}\" RENAME TO \"{$name}\""; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - } - - return $this->endTransaction(); - } - - /** - * Creates a new FTS dictionary or FTS dictionary template. - * @param string $dictname The name of the FTS dictionary to create - * @param boolean $isTemplate Flag whether we create usual dictionary or dictionary template - * @param string $template The existing FTS dictionary to be used as template for the new one - * @param string $lexize The name of the function, which does transformation of input word - * @param string $init The name of the function, which initializes dictionary - * @param string $option Usually, it stores various options required for the dictionary - * @param string $comment If omitted, defaults to nothing - * @return 0 success - */ - function createFtsDictionary($dictname, $isTemplate = false, $template = '', $lexize = '', $init = '', $option = '', $comment = '') { - $this->fieldClean($dictname); - $this->fieldClean($template); - $this->fieldClean($lexize); - $this->fieldClean($init); - $this->fieldClean($option); - $this->clean($comment); - - $sql = "CREATE TEXT SEARCH"; - if ($isTemplate) { - $sql .= " TEMPLATE {$dictname} ("; - if ($lexize != '') $sql .= " LEXIZE = {$lexize}"; - if ($init != '') $sql .= ", INIT = {$init}"; - $sql .= ")"; - $whatToComment = 'TEXT SEARCH TEMPLATE'; - } else { - $sql .= " DICTIONARY {$dictname} ("; - if ($template != '') $sql .= " TEMPLATE = {$template}"; - if ($option != '') $sql .= ", {$option}"; - $sql .= ")"; - $whatToComment = 'TEXT SEARCH DICTIONARY'; - } - - if ($comment != '') { - $status = $this->beginTransaction(); - if ($status != 0) return -1; - } - - // Create the FTS dictionary - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Set the comment - if ($comment != '') { - $status = $this->setComment($whatToComment, $dictname, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - } - - return $this->endTransaction(); - } - - /** - * Alters FTS dictionary or dictionary template - */ - function updateFtsDictionary($dictname, $comment, $name) { - $this->fieldClean($dictname); - $this->fieldClean($name); - $this->clean($comment); - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $status = $this->setComment('TEXT SEARCH DICTIONARY', $dictname, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Only if the name has changed - if ($name != $dictname) { - $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$dictname}\" RENAME TO \"{$name}\""; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - } - - return $this->endTransaction(); - } - - /** - * Return all information relating to a FTS dictionary - * @param $ftsdict The name of the FTS dictionary - * @return FTS dictionary information - */ - function getFtsDictionaryByName($ftsdict) { - $this->clean($ftsdict); - $sql = "SELECT - n.nspname as schema, - d.dictname as name, - ( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM - pg_catalog.pg_ts_template t - LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = t.tmplnamespace - WHERE d.dicttemplate = t.oid ) AS template, - d.dictinitoption as init, - pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment - FROM pg_catalog.pg_ts_dict d - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace - WHERE d.dictname = '{$ftsdict}' - AND pg_catalog.pg_ts_dict_is_visible(d.oid) - ORDER BY schema, name"; - - return $this->selectSet($sql); - } - - /** - * Creates/updates/deletes FTS mapping. - * @param string $cfgname The name of the FTS configuration to alter - * @param array $mapping Array of tokens' names - * @param string $action What to do with the mapping: add, alter or drop - * @param string $dictname Dictionary that will process tokens given or null in case of drop action - * @return 0 success - */ - function changeFtsMapping($ftscfg, $mapping, $action, $dictname = null) { - $this->fieldClean($ftscfg); - $this->fieldClean($dictname); - $this->arrayClean($mapping); - - if (count($mapping) > 0) { - switch ($action) { - case 'alter': - $whatToDo = "ALTER"; - break; - case 'drop': - $whatToDo = "DROP"; - break; - default: - $whatToDo = "ADD"; - break; - } - $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$ftscfg}\" {$whatToDo} MAPPING FOR "; - $sql .= implode(",", $mapping); - if ($action != 'drop' && !empty($dictname)) { - $sql .= " WITH {$dictname}"; - } - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - return $this->endTransaction(); - } else { - return -1; - } - } - - /** - * Return all information related to a given FTS configuration's mapping - * @param $ftscfg The name of the FTS configuration - * @param $mapping The name of the mapping - * @return FTS configuration information - */ - function getFtsMappingByName($ftscfg, $mapping) { - $this->fieldClean($ftscfg); - $this->fieldClean($mapping); - - $getOidSql = "SELECT oid, cfgparser FROM pg_catalog.pg_ts_config WHERE cfgname = '{$ftscfg}'"; - $oidSet = $this->selectSet($getOidSql); - $oid = $oidSet->fields['oid']; - $cfgparser = $oidSet->fields['cfgparser']; - - $getTokenIdSql = "SELECT tokid FROM pg_catalog.ts_token_type({$cfgparser}) WHERE alias = '{$mapping}'"; - $tokenIdSet = $this->selectSet($getTokenIdSql); - $tokid = $tokenIdSet->fields['tokid']; - - $sql = "SELECT - (SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name, - d.dictname as dictionaries - FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d - WHERE c.oid = {$oid} AND m.mapcfg = c.oid AND m.maptokentype = {$tokid} AND m.mapdict = d.oid - LIMIT 1;"; - return $this->selectSet($sql); - } - - /** - * Return list of FTS mappings possible for given parser (specified by given configuration since configuration - * can only have 1 parser) - */ - function getFtsMappings($ftscfg) { - $cfg = $this->getFtsConfigurationByName($ftscfg); - $sql = "SELECT alias AS name, description FROM pg_catalog.ts_token_type({$cfg->fields['parser_id']}) ORDER BY name"; - return $this->selectSet($sql); - } - - // Type functions - - /** - * Creates a new enum type in the database - * @param $name The name of the type - * @param $values An array of values - * @param $typcomment Type comment - * @return 0 success - * @return -1 transaction error - * @return -2 no values supplied - */ - function createEnumType($name, $values, $typcomment) { - $this->fieldClean($name); - $this->clean($typcomment); - - if (empty($values)) return -2; - - $status = $this->beginTransaction(); - if ($status != 0) return -1; - - $values = array_unique($values); - - $nbval = count($values); - - for ($i = 0; $i < $nbval; $i++) - $this->clean($values[$i]); - - $sql = "CREATE TYPE \"{$this->_schema}\".\"{$name}\" AS ENUM ('"; - $sql.= implode("','", $values); - $sql .= "')"; - - $status = $this->execute($sql); - if ($status) { - $this->rollbackTransaction(); - return -1; - } - - if ($typcomment != '') { - $status = $this->setComment('TYPE', $name, '', $typcomment, true); - if ($status) { - $this->rollbackTransaction(); - return -1; - } - } - - return $this->endTransaction(); - - } - - /** - * Get defined values for a given enum - * @return A recordset - */ - function getEnumValues($name) { - $this->fieldClean($name); - - $sql = "SELECT enumlabel AS enumval - FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON (t.oid=e.enumtypid) - WHERE t.typname = '{$name}' ORDER BY e.oid"; - return $this->selectSet($sql); - } - - // Function methods - - /** - * Creates a new function. - * @param $funcname The name of the function to create - * @param $args A comma separated string of types - * @param $returns The return type - * @param $definition The definition for the new function - * @param $language The language the function is written for - * @param $flags An array of optional flags - * @param $setof True if it returns a set, false otherwise - * @param $rows number of rows planner should estimate will be returned - * @param $cost cost the planner should use in the function execution step - * @param $replace (optional) True if OR REPLACE, false for normal - * @return 0 success - */ - function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $replace = false) { - $this->fieldClean($funcname); - $this->clean($args); - $this->clean($language); - $this->arrayClean($flags); - $this->clean($cost); - $this->clean($rows); - - $sql = "CREATE"; - if ($replace) $sql .= " OR REPLACE"; - $sql .= " FUNCTION \"{$this->_schema}\".\"{$funcname}\" ("; - - if ($args != '') - $sql .= $args; - - // For some reason, the returns field cannot have quotes... - $sql .= ") RETURNS "; - if ($setof) $sql .= "SETOF "; - $sql .= "{$returns} AS "; - - if (is_array($definition)) { - $this->arrayClean($definition); - $sql .= "'" . $definition[0] . "'"; - if ($definition[1]) { - $sql .= ",'" . $definition[1] . "'"; - } - } else { - $this->clean($definition); - $sql .= "'" . $definition . "'"; - } - - $sql .= " LANGUAGE \"{$language}\""; - - // Add costs - if (!empty($cost)) - $sql .= " COST {$cost}"; - - if ($rows <> 0 ){ - $sql .= " ROWS {$rows}"; - } - - // Add flags - foreach ($flags as $v) { - // Skip default flags - if ($v == '') continue; - else $sql .= "\n{$v}"; - } - - return $this->execute($sql); - } - - /** - * Returns all details for a particular function - * @param $func The name of the function to retrieve - * @return Function info - */ - function getFunction($function_oid) { - $this->clean($function_oid); - - $sql = "SELECT - pc.oid AS prooid, - proname, - pg_catalog.pg_get_userbyid(proowner) AS proowner, - nspname as proschema, - lanname as prolanguage, - procost, - prorows, - pg_catalog.format_type(prorettype, NULL) as proresult, - prosrc, - probin, - proretset, - proisstrict, - provolatile, - prosecdef, - pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments, - proargnames AS proargnames, - pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment, - proconfig - FROM - pg_catalog.pg_proc pc, pg_catalog.pg_language pl, pg_catalog.pg_namespace pn - WHERE - pc.oid = '{$function_oid}'::oid - AND pc.prolang = pl.oid - AND pc.pronamespace = pn.oid - "; - - return $this->selectSet($sql); - } - - // Trigger functions - - /** - * Grabs a list of triggers on a table - * @param $table The name of a table whose triggers to retrieve - * @return A recordset - */ - function getTriggers($table = '') { - $this->clean($table); - - $sql = "SELECT - t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef, - CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid, - p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, - ns.nspname AS pronamespace - FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns - WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' - AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$this->_schema}')) - AND (NOT tgisconstraint OR NOT EXISTS - (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c - ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) - WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f')) - AND p.oid=t.tgfoid - AND p.pronamespace = ns.oid"; - - return $this->selectSet($sql); - } - - - - // Capabilities - function hasCreateTableLikeWithIndexes() {return true;} - function hasVirtualTransactionId() {return true;} - function hasEnumTypes() {return true;} - function hasFTS() {return true;} - function hasFunctionCosting() {return true;} - function hasFunctionGUC() {return true;} -} -?> diff --git a/libraries/lib.inc.php b/libraries/lib.inc.php index aa0825fc..acfcc722 100644 --- a/libraries/lib.inc.php +++ b/libraries/lib.inc.php @@ -20,7 +20,7 @@ // PostgreSQL and PHP minimum version $postgresqlMinVer = '7.0'; - $phpMinVer = '4.1'; + $phpMinVer = '5.0'; // Check the version of PHP if (version_compare(phpversion(), $phpMinVer, '<')) diff --git a/selenium/tests/config.inc.php b/selenium/tests/config.inc.php index 38fe0d25..17a7d12c 100644 --- a/selenium/tests/config.inc.php +++ b/selenium/tests/config.inc.php @@ -1,7 +1,7 @@ <?php require('../../lang/recoded/english.php'); -$webUrl = 'http://boox/~ioguix/ppa/ppa.test'; +$webUrl = 'http://boox/~ioguix/ppa.git/phppgadmin'; $serverName = '8.3'; // one of your $conf['servers'][*]['desc'] in conf/config.inc/php #deprecated $superuser = 'ppatests_super'; // according to your selenium/tests/data/config.sql #deprecated $superpass = 'super'; // according to your selenium/tests/data/config.sql |