diff options
author | Amenadiel <amenadiel@gmail.com> | 2017-07-26 15:16:00 +0300 |
---|---|---|
committer | Amenadiel <amenadiel@gmail.com> | 2017-07-26 15:16:00 +0300 |
commit | 783f1af1d990f182c8b7be3e7d38f6a192062548 (patch) | |
tree | f76e796b74fc4352fba8a0dd144be1b8a6a3bab8 | |
parent | 4a3fdb2bd95f04c9387a5a523db981db96bf549f (diff) |
Tag v 6.0.0-beta.14 transform help scripts into classes, adds them to autoloaderv6.0.0-beta.14
29 files changed, 10106 insertions, 9623 deletions
diff --git a/composer.json b/composer.json index 00362834..055b6db5 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "huasofoundries/phppgadmin6", - "version": "6.0.0-beta.13", + "version": "6.0.0-beta.14", "description": "Like phpmyadmin but for postgres", "type": "project", "license": "MIT", @@ -19,7 +19,8 @@ "PHPPgAdmin\\Middleware\\": "src/middleware", "PHPPgAdmin\\Database\\": "src/database", "PHPPgAdmin\\XHtml\\": "src/xhtml", - "PHPPgAdmin\\Decorators\\": "src/decorators" + "PHPPgAdmin\\Decorators\\": "src/decorators", + "PHPPgAdmin\\Help\\": "src/help" } }, "require": diff --git a/composer.lock b/composer.lock index 2f940ee0..953c0186 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "6764dc6ac14b453b098da17a1e92d8f5", - "content-hash": "0a0ebf9f4f434b5b3f4d38549440d300", + "hash": "c78297ee8abcbf4954948fbc6cc46080", + "content-hash": "f0d3558e928fc01c8fcfcdac4ada9b01", "packages": [ { "name": "adodb/adodb-php", @@ -246,16 +246,16 @@ }, { "name": "pimple/pimple", - "version": "v3.2.0", + "version": "v3.2.2", "source": { "type": "git", "url": "https://github.com/silexphp/Pimple.git", - "reference": "729436408ef52f9845f9b60d60b2563f314bf1a9" + "reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/729436408ef52f9845f9b60d60b2563f314bf1a9", - "reference": "729436408ef52f9845f9b60d60b2563f314bf1a9", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/4d45fb62d96418396ec58ba76e6f065bca16e10a", + "reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a", "shasum": "" }, "require": { @@ -292,7 +292,7 @@ "container", "dependency injection" ], - "time": "2017-07-17 17:05:21" + "time": "2017-07-23 07:32:15" }, { "name": "psr/container", diff --git a/src/database/Postgres.php b/src/database/Postgres.php index 12dc0e30..e621fb3c 100644 --- a/src/database/Postgres.php +++ b/src/database/Postgres.php @@ -1,5 +1,6 @@ <?php namespace PHPPgAdmin\Database; + /** * A Class that implements the DB Interface for Postgres * Note: This Class uses ADODB and returns RecordSets. @@ -7,499 +8,528 @@ namespace PHPPgAdmin\Database; * $Id: Postgres.php,v 1.320 2008/02/20 20:43:09 ioguix Exp $ */ -class Postgres extends ADODB_base { - - use \PHPPgAdmin\HelperTrait; - - var $major_version = 9.5; - // 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 = [ - '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', - ]; - var $defaultprops = ['', '', '']; - // Extra "magic" types. BIGSERIAL was added in PostgreSQL 7.2. - var $extraTypes = ['SERIAL', 'BIGSERIAL']; - // Foreign key stuff. First element MUST be the default. - var $fkactions = ['NO ACTION', 'RESTRICT', 'CASCADE', 'SET NULL', 'SET DEFAULT']; - var $fkdeferrable = ['NOT DEFERRABLE', 'DEFERRABLE']; - var $fkinitial = ['INITIALLY IMMEDIATE', 'INITIALLY DEFERRED']; - var $fkmatches = ['MATCH SIMPLE', 'MATCH FULL']; - // Function properties - var $funcprops = [['', 'VOLATILE', 'IMMUTABLE', 'STABLE'], - ['', 'CALLED ON NULL INPUT', 'RETURNS NULL ON NULL INPUT'], - ['', '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 = ['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 = [ - 'sql' => 'SQL', - 'plpgsql' => 'SQL', - 'php' => 'PHP', - 'phpu' => 'PHP', - 'plphp' => 'PHP', - 'plphpu' => 'PHP', - 'perl' => 'Perl', - 'perlu' => 'Perl', - 'plperl' => 'Perl', - 'plperlu' => 'Perl', - 'java' => 'Java', - 'javau' => 'Java', - 'pljava' => 'Java', - 'pljavau' => 'Java', - 'plj' => 'Java', - 'plju' => 'Java', - 'python' => 'Python', - 'pythonu' => 'Python', - 'plpython' => 'Python', - 'plpythonu' => 'Python', - 'ruby' => 'Ruby', - 'rubyu' => 'Ruby', - 'plruby' => 'Ruby', - 'plrubyu' => 'Ruby', - ]; - // Predefined size types - var $predefined_size_types = ['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 = [ - 'table' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], - 'view' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], - 'sequence' => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'], - 'database' => ['CREATE', 'TEMPORARY', 'CONNECT', 'ALL PRIVILEGES'], - 'function' => ['EXECUTE', 'ALL PRIVILEGES'], - 'language' => ['USAGE', 'ALL PRIVILEGES'], - 'schema' => ['CREATE', 'USAGE', 'ALL PRIVILEGES'], - 'tablespace' => ['CREATE', 'ALL PRIVILEGES'], - 'column' => ['SELECT', 'INSERT', 'UPDATE', 'REFERENCES', 'ALL PRIVILEGES'], - ]; - // List of characters in acl lists and the privileges they - // refer to. - var $privmap = [ - 'r' => 'SELECT', - 'w' => 'UPDATE', - 'a' => 'INSERT', - 'd' => 'DELETE', - 'D' => 'TRUNCATE', - 'R' => 'RULE', - 'x' => 'REFERENCES', - 't' => 'TRIGGER', - 'X' => 'EXECUTE', - 'U' => 'USAGE', - 'C' => 'CREATE', - 'T' => 'TEMPORARY', - 'c' => 'CONNECT', - ]; - // Rule action types - var $rule_events = ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; - // Select operators - var $selectOps = ['=' => '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 = ['INSERT', 'UPDATE', 'DELETE', 'INSERT OR UPDATE', 'INSERT OR DELETE', - 'DELETE OR UPDATE', 'INSERT OR DELETE OR UPDATE']; - // When to execute the trigger - var $triggerExecTimes = ['BEFORE', 'AFTER']; - // How often to execute the trigger - var $triggerFrequency = ['ROW', 'STATEMENT']; - // Array of allowed type alignments - var $typAligns = ['char', 'int2', 'int4', 'double']; - // The default type alignment - var $typAlignDef = 'int4'; - // Default index type - var $typIndexDef = 'BTREE'; - // Array of allowed index types - var $typIndexes = ['BTREE', 'RTREE', 'GIST', 'GIN', 'HASH']; - // Array of allowed type storage attributes - var $typStorages = ['plain', 'external', 'extended', 'main']; - // The default type storage - var $typStorageDef = 'plain'; - - function __construct(&$conn, $conf) { - $this->prtrace('major_version', $this->major_version); - $this->conn = $conn; - $this->conf = $conf; - } - // Formatting functions - - /** - * Cleans (escapes) a string - * @param $str The string to clean, by reference - * @return The cleaned string - */ - function clean(&$str) { - if ($str === null) { - return null; - } - - $str = str_replace("\r\n", "\n", $str); - $str = pg_escape_string($str); - return $str; - } - - /** - * Cleans (escapes) an object name (eg. table, field) - * @param $str The string to clean, by reference - * @return The cleaned string - */ - function fieldClean(&$str) { - if ($str === null) { - return null; - } - - $str = str_replace('"', '""', $str); - return $str; - } - - /** - * Cleans (escapes) an array of field names - * @param $arr The array to clean, by reference - * @return The cleaned array - */ - function fieldArrayClean(&$arr) { - foreach ($arr as $k => $v) { - if ($v === null) { - continue; - } - - $arr[$k] = str_replace('"', '""', $v); - } - return $arr; - } - - /** - * Cleans (escapes) an array - * @param $arr The array to clean, by reference - * @return The cleaned array - */ - function arrayClean(&$arr) { - foreach ($arr as $k => $v) { - if ($v === null) { - continue; - } - - $arr[$k] = pg_escape_string($v); - } - return $arr; - } - - /** - * Escapes bytea data for display on the screen - * @param $data The bytea data - * @return Data formatted for on-screen display - */ - function escapeBytea($data) { - return htmlentities($data, ENT_QUOTES, 'UTF-8'); - } - - /** - * Outputs the HTML code for a particular field - * @param $name The name to give the field - * @param $value The value of the field. Note this could be 'numeric(7,2)' sort of thing... - * @param $type The database type of the field - * @param $extras An array of attributes name as key and attributes' values as value - */ - function printField($name, $value, $type, $extras = []) { - $lang = $this->lang; - - // Determine actions string - $extra_str = ''; - foreach ($extras as $k => $v) { - $extra_str .= " {$k}=\"" . htmlspecialchars($v) . "\""; - } - - switch (substr($type, 0, 9)) { - case 'bool': - case 'boolean': - if ($value !== null && $value == '') { - $value = null; - } elseif ($value == 'true') { - $value = 't'; - } elseif ($value == 'false') { - $value = 'f'; - } - - // If value is null, 't' or 'f'... - if ($value === null || $value == 't' || $value == 'f') { - echo "<select name=\"", htmlspecialchars($name), "\"{$extra_str}>\n"; - echo "<option value=\"\"", ($value === null) ? ' selected="selected"' : '', "></option>\n"; - echo "<option value=\"t\"", ($value == 't') ? ' selected="selected"' : '', ">{$lang['strtrue']}</option>\n"; - echo "<option value=\"f\"", ($value == 'f') ? ' selected="selected"' : '', ">{$lang['strfalse']}</option>\n"; - echo "</select>\n"; - } else { - echo "<input name=\"", htmlspecialchars($name), "\" value=\"", htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n"; - } - break; - case 'bytea': - case 'bytea[]': - if (!is_null($value)) { - $value = $this->escapeBytea($value); - } - case 'text': - case 'text[]': - case 'json': - case 'jsonb': - case 'xml': - case 'xml[]': - $n = substr_count($value, "\n"); - $n = $n < 5 ? 5 : $n; - $n = $n > 20 ? 20 : $n; - echo "<textarea name=\"", htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"75\"{$extra_str}>\n"; - echo htmlspecialchars($value); - echo "</textarea>\n"; - break; - case 'character': - case 'character[]': - $n = substr_count($value, "\n"); - $n = $n < 5 ? 5 : $n; - $n = $n > 20 ? 20 : $n; - echo "<textarea name=\"", htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"35\"{$extra_str}>\n"; - echo htmlspecialchars($value); - echo "</textarea>\n"; - break; - default: - echo "<input name=\"", htmlspecialchars($name), "\" value=\"", htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n"; - break; - } - } - - /** - * Formats a value or expression for sql purposes - * @param $type The type of the field - * @param $format VALUE or EXPRESSION - * @param $value The actual value entered in the field. Can be NULL - * @return The suitably quoted and escaped value. - */ - function formatValue($type, $format, $value) { - switch ($type) { - case 'bool': - case 'boolean': - if ($value == 't') { - return 'TRUE'; - } elseif ($value == 'f') { - return 'FALSE'; - } elseif ($value == '') { - return 'NULL'; - } else { - return $value; - } - - break; - default: - // Checking variable fields is difficult as there might be a size - // attribute... - if (strpos($type, 'time') === 0) { - // Assume it's one of the time types... - if ($value == '') { - return "''"; - } elseif (strcasecmp($value, 'CURRENT_TIMESTAMP') == 0 - || strcasecmp($value, 'CURRENT_TIME') == 0 - || strcasecmp($value, 'CURRENT_DATE') == 0 - || strcasecmp($value, 'LOCALTIME') == 0 - || strcasecmp($value, 'LOCALTIMESTAMP') == 0) { - return $value; - } elseif ($format == 'EXPRESSION') { - return $value; - } else { - $this->clean($value); - return "'{$value}'"; - } - } else { - if ($format == 'VALUE') { - $this->clean($value); - return "'{$value}'"; - } - return $value; - } - } - } - - /** - * Formats a type correctly for display. Postgres 7.0 had no 'format_type' - * built-in function, and hence we need to do it manually. - * @param $typname The name of the type - * @param $typmod The contents of the typmod field - */ - function formatType($typname, $typmod) { - // This is a specific constant in the 7.0 source - $varhdrsz = 4; - - // If the first character is an underscore, it's an array type - $is_array = false; - if (substr($typname, 0, 1) == '_') { - $is_array = true; - $typname = substr($typname, 1); - } - - // Show lengths on bpchar and varchar - if ($typname == 'bpchar') { - $len = $typmod - $varhdrsz; - $temp = 'character'; - if ($len > 1) { - $temp .= "({$len})"; - } - - } elseif ($typname == 'varchar') { - $temp = 'character varying'; - if ($typmod != -1) { - $temp .= "(" . ($typmod - $varhdrsz) . ")"; - } - - } elseif ($typname == 'numeric') { - $temp = 'numeric'; - if ($typmod != -1) { - $tmp_typmod = $typmod - $varhdrsz; - $precision = ($tmp_typmod >> 16) & 0xffff; - $scale = $tmp_typmod & 0xffff; - $temp .= "({$precision}, {$scale})"; - } - } else { - $temp = $typname; - } - - // Add array qualifier if it's an array - if ($is_array) { - $temp .= '[]'; - } - - return $temp; - } - - // 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 = []; - 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 BASE_PATH . '/src/help/PostgresDoc95.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 - * @param $currentdatabase database name that should be on top of the resultset - * - * @return A list of databases, sorted alphabetically - */ - function getDatabases($currentdatabase = NULL) { - $conf = $this->conf; - $server_info = $this->server_info; - - if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { - $username = $server_info['username']; - $this->clean($username); - $clause = " AND pr.rolname='{$username}'"; - } else { - $clause = ''; - } - if (isset($server_info['useonlydefaultdb']) && $server_info['useonlydefaultdb']) { - $currentdatabase = $server_info['defaultdb']; - $clause .= " AND pdb.datname = '{$currentdatabase}' "; - } - - if (isset($server_info['hiddendbs']) && $server_info['hiddendbs']) { - $hiddendbs = $server_info['hiddendbs']; - $not_in = "('" . implode("','", $hiddendbs) . "')"; - $clause .= " AND pdb.datname NOT IN {$not_in} "; - } - - if ($currentdatabase != NULL) { - $this->clean($currentdatabase); - $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 = " +class Postgres extends ADODB_base +{ + + use \PHPPgAdmin\HelperTrait; + + public $major_version = 9.6; + // Max object name length + public $_maxNameLen = 63; + // Store the current schema + public $_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. + public $codemap = [ + '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', + ]; + public $defaultprops = ['', '', '']; + // Extra "magic" types. BIGSERIAL was added in PostgreSQL 7.2. + public $extraTypes = ['SERIAL', 'BIGSERIAL']; + // Foreign key stuff. First element MUST be the default. + public $fkactions = ['NO ACTION', 'RESTRICT', 'CASCADE', 'SET NULL', 'SET DEFAULT']; + public $fkdeferrable = ['NOT DEFERRABLE', 'DEFERRABLE']; + public $fkinitial = ['INITIALLY IMMEDIATE', 'INITIALLY DEFERRED']; + public $fkmatches = ['MATCH SIMPLE', 'MATCH FULL']; + // Function properties + public $funcprops = [['', 'VOLATILE', 'IMMUTABLE', 'STABLE'], + ['', 'CALLED ON NULL INPUT', 'RETURNS NULL ON NULL INPUT'], + ['', 'SECURITY INVOKER', 'SECURITY DEFINER']]; + + // Default help URL + public $help_base = null; + // Help sub pages + public $help_page = null; + // Name of id column + public $id = 'oid'; + + // Supported join operations for use with view wizard + public $joinOps = ['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 + public $langmap = [ + 'sql' => 'SQL', + 'plpgsql' => 'SQL', + 'php' => 'PHP', + 'phpu' => 'PHP', + 'plphp' => 'PHP', + 'plphpu' => 'PHP', + 'perl' => 'Perl', + 'perlu' => 'Perl', + 'plperl' => 'Perl', + 'plperlu' => 'Perl', + 'java' => 'Java', + 'javau' => 'Java', + 'pljava' => 'Java', + 'pljavau' => 'Java', + 'plj' => 'Java', + 'plju' => 'Java', + 'python' => 'Python', + 'pythonu' => 'Python', + 'plpython' => 'Python', + 'plpythonu' => 'Python', + 'ruby' => 'Ruby', + 'rubyu' => 'Ruby', + 'plruby' => 'Ruby', + 'plrubyu' => 'Ruby', + ]; + // Predefined size types + public $predefined_size_types = ['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. + public $privlist = [ + 'table' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], + 'view' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], + 'sequence' => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'], + 'database' => ['CREATE', 'TEMPORARY', 'CONNECT', 'ALL PRIVILEGES'], + 'function' => ['EXECUTE', 'ALL PRIVILEGES'], + 'language' => ['USAGE', 'ALL PRIVILEGES'], + 'schema' => ['CREATE', 'USAGE', 'ALL PRIVILEGES'], + 'tablespace' => ['CREATE', 'ALL PRIVILEGES'], + 'column' => ['SELECT', 'INSERT', 'UPDATE', 'REFERENCES', 'ALL PRIVILEGES'], + ]; + // List of characters in acl lists and the privileges they + // refer to. + public $privmap = [ + 'r' => 'SELECT', + 'w' => 'UPDATE', + 'a' => 'INSERT', + 'd' => 'DELETE', + 'D' => 'TRUNCATE', + 'R' => 'RULE', + 'x' => 'REFERENCES', + 't' => 'TRIGGER', + 'X' => 'EXECUTE', + 'U' => 'USAGE', + 'C' => 'CREATE', + 'T' => 'TEMPORARY', + 'c' => 'CONNECT', + ]; + // Rule action types + public $rule_events = ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; + // Select operators + public $selectOps = ['=' => '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 + public $triggerEvents = ['INSERT', 'UPDATE', 'DELETE', 'INSERT OR UPDATE', 'INSERT OR DELETE', + 'DELETE OR UPDATE', 'INSERT OR DELETE OR UPDATE']; + // When to execute the trigger + public $triggerExecTimes = ['BEFORE', 'AFTER']; + // How often to execute the trigger + public $triggerFrequency = ['ROW', 'STATEMENT']; + // Array of allowed type alignments + public $typAligns = ['char', 'int2', 'int4', 'double']; + // The default type alignment + public $typAlignDef = 'int4'; + // Default index type + public $typIndexDef = 'BTREE'; + // Array of allowed index types + public $typIndexes = ['BTREE', 'RTREE', 'GIST', 'GIN', 'HASH']; + // Array of allowed type storage attributes + public $typStorages = ['plain', 'external', 'extended', 'main']; + // The default type storage + public $typStorageDef = 'plain'; + + public function __construct(&$conn, $conf) + { + $this->prtrace('major_version', $this->major_version); + $this->conn = $conn; + $this->conf = $conf; + } + + /** + * Fetch a URL (or array of URLs) for a given help page. + */ + public function getHelp($help) + { + $this->getHelpPages(); + + if (isset($this->help_page[$help])) { + if (is_array($this->help_page[$help])) { + $urls = []; + 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; + } + + } + + /** + * get help page by instancing the corresponding help class + * if $this->help_page and $this->help_base are set, this function is a noop + */ + public function getHelpPages() + { + if ($this->help_page === null || $this->help_base === null) { + $help_classname = '\PHPPgAdmin\Help\PostgresDoc' . $this->major_version; + + $help_class = new $help_classname($this->conf, $this->major_version); + + $this->help_base = $help_class->getHelpBase(); + $this->help_page = $help_class->getHelpPage(); + + } + + } + + // Formatting functions + + /** + * Cleans (escapes) a string + * @param $str The string to clean, by reference + * @return The cleaned string + */ + public function clean(&$str) + { + if ($str === null) { + return null; + } + + $str = str_replace("\r\n", "\n", $str); + $str = pg_escape_string($str); + return $str; + } + + /** + * Cleans (escapes) an object name (eg. table, field) + * @param $str The string to clean, by reference + * @return The cleaned string + */ + public function fieldClean(&$str) + { + if ($str === null) { + return null; + } + + $str = str_replace('"', '""', $str); + return $str; + } + + /** + * Cleans (escapes) an array of field names + * @param $arr The array to clean, by reference + * @return The cleaned array + */ + public function fieldArrayClean(&$arr) + { + foreach ($arr as $k => $v) { + if ($v === null) { + continue; + } + + $arr[$k] = str_replace('"', '""', $v); + } + return $arr; + } + + /** + * Cleans (escapes) an array + * @param $arr The array to clean, by reference + * @return The cleaned array + */ + public function arrayClean(&$arr) + { + foreach ($arr as $k => $v) { + if ($v === null) { + continue; + } + + $arr[$k] = pg_escape_string($v); + } + return $arr; + } + + /** + * Escapes bytea data for display on the screen + * @param $data The bytea data + * @return Data formatted for on-screen display + */ + public function escapeBytea($data) + { + return htmlentities($data, ENT_QUOTES, 'UTF-8'); + } + + /** + * Outputs the HTML code for a particular field + * @param $name The name to give the field + * @param $value The value of the field. Note this could be 'numeric(7,2)' sort of thing... + * @param $type The database type of the field + * @param $extras An array of attributes name as key and attributes' values as value + */ + public function printField($name, $value, $type, $extras = []) + { + $lang = $this->lang; + + // Determine actions string + $extra_str = ''; + foreach ($extras as $k => $v) { + $extra_str .= " {$k}=\"" . htmlspecialchars($v) . "\""; + } + + switch (substr($type, 0, 9)) { + case 'bool': + case 'boolean': + if ($value !== null && $value == '') { + $value = null; + } elseif ($value == 'true') { + $value = 't'; + } elseif ($value == 'false') { + $value = 'f'; + } + + // If value is null, 't' or 'f'... + if ($value === null || $value == 't' || $value == 'f') { + echo "<select name=\"", htmlspecialchars($name), "\"{$extra_str}>\n"; + echo "<option value=\"\"", ($value === null) ? ' selected="selected"' : '', "></option>\n"; + echo "<option value=\"t\"", ($value == 't') ? ' selected="selected"' : '', ">{$lang['strtrue']}</option>\n"; + echo "<option value=\"f\"", ($value == 'f') ? ' selected="selected"' : '', ">{$lang['strfalse']}</option>\n"; + echo "</select>\n"; + } else { + echo "<input name=\"", htmlspecialchars($name), "\" value=\"", htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n"; + } + break; + case 'bytea': + case 'bytea[]': + if (!is_null($value)) { + $value = $this->escapeBytea($value); + } + case 'text': + case 'text[]': + case 'json': + case 'jsonb': + case 'xml': + case 'xml[]': + $n = substr_count($value, "\n"); + $n = $n < 5 ? 5 : $n; + $n = $n > 20 ? 20 : $n; + echo "<textarea name=\"", htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"75\"{$extra_str}>\n"; + echo htmlspecialchars($value); + echo "</textarea>\n"; + break; + case 'character': + case 'character[]': + $n = substr_count($value, "\n"); + $n = $n < 5 ? 5 : $n; + $n = $n > 20 ? 20 : $n; + echo "<textarea name=\"", htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"35\"{$extra_str}>\n"; + echo htmlspecialchars($value); + echo "</textarea>\n"; + break; + default: + echo "<input name=\"", htmlspecialchars($name), "\" value=\"", htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n"; + break; + } + } + + /** + * Formats a value or expression for sql purposes + * @param $type The type of the field + * @param $format VALUE or EXPRESSION + * @param $value The actual value entered in the field. Can be NULL + * @return The suitably quoted and escaped value. + */ + public function formatValue($type, $format, $value) + { + switch ($type) { + case 'bool': + case 'boolean': + if ($value == 't') { + return 'TRUE'; + } elseif ($value == 'f') { + return 'FALSE'; + } elseif ($value == '') { + return 'NULL'; + } else { + return $value; + } + + break; + default: + // Checking variable fields is difficult as there might be a size + // attribute... + if (strpos($type, 'time') === 0) { + // Assume it's one of the time types... + if ($value == '') { + return "''"; + } elseif (strcasecmp($value, 'CURRENT_TIMESTAMP') == 0 + || strcasecmp($value, 'CURRENT_TIME') == 0 + || strcasecmp($value, 'CURRENT_DATE') == 0 + || strcasecmp($value, 'LOCALTIME') == 0 + || strcasecmp($value, 'LOCALTIMESTAMP') == 0) { + return $value; + } elseif ($format == 'EXPRESSION') { + return $value; + } else { + $this->clean($value); + return "'{$value}'"; + } + } else { + if ($format == 'VALUE') { + $this->clean($value); + return "'{$value}'"; + } + return $value; + } + } + } + + /** + * Formats a type correctly for display. Postgres 7.0 had no 'format_type' + * built-in function, and hence we need to do it manually. + * @param $typname The name of the type + * @param $typmod The contents of the typmod field + */ + public function formatType($typname, $typmod) + { + // This is a specific constant in the 7.0 source + $varhdrsz = 4; + + // If the first character is an underscore, it's an array type + $is_array = false; + if (substr($typname, 0, 1) == '_') { + $is_array = true; + $typname = substr($typname, 1); + } + + // Show lengths on bpchar and varchar + if ($typname == 'bpchar') { + $len = $typmod - $varhdrsz; + $temp = 'character'; + if ($len > 1) { + $temp .= "({$len})"; + } + + } elseif ($typname == 'varchar') { + $temp = 'character varying'; + if ($typmod != -1) { + $temp .= "(" . ($typmod - $varhdrsz) . ")"; + } + + } elseif ($typname == 'numeric') { + $temp = 'numeric'; + if ($typmod != -1) { + $tmp_typmod = $typmod - $varhdrsz; + $precision = ($tmp_typmod >> 16) & 0xffff; + $scale = $tmp_typmod & 0xffff; + $temp .= "({$precision}, {$scale})"; + } + } else { + $temp = $typname; + } + + // Add array qualifier if it's an array + if ($is_array) { + $temp .= '[]'; + } + + return $temp; + } + + // Help functions + + // Database functions + + /** + * Return all information about a particular database + * @param $database The name of the database to retrieve + * @return The database info + */ + public 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 + * @param $currentdatabase database name that should be on top of the resultset + * + * @return A list of databases, sorted alphabetically + */ + public function getDatabases($currentdatabase = null) + { + $conf = $this->conf; + $server_info = $this->server_info; + + if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { + $username = $server_info['username']; + $this->clean($username); + $clause = " AND pr.rolname='{$username}'"; + } else { + $clause = ''; + } + if (isset($server_info['useonlydefaultdb']) && $server_info['useonlydefaultdb']) { + $currentdatabase = $server_info['defaultdb']; + $clause .= " AND pdb.datname = '{$currentdatabase}' "; + } + + if (isset($server_info['hiddendbs']) && $server_info['hiddendbs']) { + $hiddendbs = $server_info['hiddendbs']; + $not_in = "('" . implode("','", $hiddendbs) . "')"; + $clause .= " AND pdb.datname NOT IN {$not_in} "; + } + + if ($currentdatabase != null) { + $this->clean($currentdatabase); + $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, pr.rolname AS datowner, pg_encoding_to_char(encoding) AS datencoding, (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pdb.oid=pd.objoid AND pd.classoid='pg_database'::regclass) AS datcomment, (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace, @@ -514,255 +544,265 @@ class Postgres extends ADODB_base { {$clause} {$orderby}"; - return $this->selectSet($sql); - } - - /** - * 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 getDatabaseComment($database) { - $this->clean($database); - $sql = "SELECT description FROM pg_catalog.pg_database JOIN pg_catalog.pg_shdescription ON (oid=objoid AND classoid='pg_database'::regclass) WHERE pg_database.datname = '{$database}' "; - return $this->selectSet($sql); - } - - /** - * 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 getDatabaseOwner($database) { - $this->clean($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); - } - - /** - * Returns the current database encoding - * @return The encoding. eg. SQL_ASCII, UTF-8, etc. - */ - function getDatabaseEncoding() { - return pg_parameter_status($this->conn->_connectionID, 'server_encoding'); - } - - /** - * Returns the current default_with_oids setting - * @return default_with_oids setting - */ - function getDefaultWithOid() { - - $sql = "SHOW default_with_oids"; - - return $this->selectField($sql, 'default_with_oids'); - } - - /** - * Creates a database - * @param $database The name of the database to create - * @param $encoding Encoding of the database - * @param $tablespace (optional) The tablespace name - * @return 0 success - * @return -1 tablespace error - * @return -2 comment error - */ - function createDatabase($database, $encoding, $tablespace = '', $comment = '', $template = 'template1', - $lc_collate = '', $lc_ctype = '') { - $this->fieldClean($database); - $this->clean($encoding); - $this->fieldClean($tablespace); - $this->fieldClean($template); - $this->clean($lc_collate); - $this->clean($lc_ctype); - - $sql = "CREATE DATABASE \"{$database}\" WITH TEMPLATE=\"{$template}\""; - - if ($encoding != '') { - $sql .= " ENCODING='{$encoding}'"; - } - - if ($lc_collate != '') { - $sql .= " LC_COLLATE='{$lc_collate}'"; - } - - if ($lc_ctype != '') { - $sql .= " LC_CTYPE='{$lc_ctype}'"; - } - - if ($tablespace != '' && $this->hasTablespaces()) { - $sql .= " TABLESPACE \"{$tablespace}\""; - } - - $status = $this->execute($sql); - if ($status != 0) { - return -1; - } - - if ($comment != '' && $this->hasSharedComments()) { - $status = $this->setComment('DATABASE', $database, '', $comment); - if ($status != 0) { - return -2; - } - - } - - 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->fieldClean($oldName); - $this->fieldClean($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; - } - - } - - /** - * Drops a database - * @param $database The name of the database to drop - * @return 0 success - */ - function dropDatabase($database) { - $this->fieldClean($database); - $sql = "DROP DATABASE \"{$database}\""; - return $this->execute($sql); - } - - /** - * 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->fieldClean($dbName); - $this->fieldClean($newOwner); - - $sql = "ALTER DATABASE \"{$dbName}\" OWNER TO \"{$newOwner}\""; - return $this->execute($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 - * @return -4 comment error - */ - function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') { - - $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; - } - $dbName = $newName; - } - - if ($newOwner != '') { - $status = $this->alterDatabaseOwner($newName, $newOwner); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } - } - - $this->fieldClean($dbName); - $status = $this->setComment('DATABASE', $dbName, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - return $this->endTransaction(); - } - - /** - * 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 + return $this->selectSet($sql); + } + + /** + * 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 + */ + public function getDatabaseComment($database) + { + $this->clean($database); + $sql = "SELECT description FROM pg_catalog.pg_database JOIN pg_catalog.pg_shdescription ON (oid=objoid AND classoid='pg_database'::regclass) WHERE pg_database.datname = '{$database}' "; + return $this->selectSet($sql); + } + + /** + * 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 + */ + public function getDatabaseOwner($database) + { + $this->clean($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); + } + + /** + * Returns the current database encoding + * @return The encoding. eg. SQL_ASCII, UTF-8, etc. + */ + public function getDatabaseEncoding() + { + return pg_parameter_status($this->conn->_connectionID, 'server_encoding'); + } + + /** + * Returns the current default_with_oids setting + * @return default_with_oids setting + */ + public function getDefaultWithOid() + { + + $sql = "SHOW default_with_oids"; + + return $this->selectField($sql, 'default_with_oids'); + } + + /** + * Creates a database + * @param $database The name of the database to create + * @param $encoding Encoding of the database + * @param $tablespace (optional) The tablespace name + * @return 0 success + * @return -1 tablespace error + * @return -2 comment error + */ + public function createDatabase($database, $encoding, $tablespace = '', $comment = '', $template = 'template1', + $lc_collate = '', $lc_ctype = '') { + $this->fieldClean($database); + $this->clean($encoding); + $this->fieldClean($tablespace); + $this->fieldClean($template); + $this->clean($lc_collate); + $this->clean($lc_ctype); + + $sql = "CREATE DATABASE \"{$database}\" WITH TEMPLATE=\"{$template}\""; + + if ($encoding != '') { + $sql .= " ENCODING='{$encoding}'"; + } + + if ($lc_collate != '') { + $sql .= " LC_COLLATE='{$lc_collate}'"; + } + + if ($lc_ctype != '') { + $sql .= " LC_CTYPE='{$lc_ctype}'"; + } + + if ($tablespace != '' && $this->hasTablespaces()) { + $sql .= " TABLESPACE \"{$tablespace}\""; + } + + $status = $this->execute($sql); + if ($status != 0) { + return -1; + } + + if ($comment != '' && $this->hasSharedComments()) { + $status = $this->setComment('DATABASE', $database, '', $comment); + if ($status != 0) { + return -2; + } + + } + + 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 + */ + public function alterDatabaseRename($oldName, $newName) + { + $this->fieldClean($oldName); + $this->fieldClean($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; + } + + } + + /** + * Drops a database + * @param $database The name of the database to drop + * @return 0 success + */ + public function dropDatabase($database) + { + $this->fieldClean($database); + $sql = "DROP DATABASE \"{$database}\""; + return $this->execute($sql); + } + + /** + * 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 + */ + public function alterDatabaseOwner($dbName, $newOwner) + { + $this->fieldClean($dbName); + $this->fieldClean($newOwner); + + $sql = "ALTER DATABASE \"{$dbName}\" OWNER TO \"{$newOwner}\""; + return $this->execute($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 + * @return -4 comment error + */ + public function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') + { + + $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; + } + $dbName = $newName; + } + + if ($newOwner != '') { + $status = $this->alterDatabaseOwner($newName, $newOwner); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; + } + } + + $this->fieldClean($dbName); + $status = $this->setComment('DATABASE', $dbName, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; + } + return $this->endTransaction(); + } + + /** + * Returns prepared transactions information + * @param $database (optional) Find only prepared transactions executed in a specific database + * @return A recordset + */ + public 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); - } - - /** - * 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) { - $conf = $this->conf; - - /*about escaping: - * SET standard_conforming_string is not available before 8.2 - * So we must use PostgreSQL specific notation :/ - * E'' notation is not available before 8.1 - * $$ is available since 8.0 - * Nothing specific from 7.4 - */ - - // Escape search term for ILIKE match - $this->clean($term); - $this->clean($filter); - $term = str_replace('_', '\_', $term); - $term = str_replace('%', '\%', $term); - - // 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 \$_PATERN_\$pg\_%\$_PATERN_\$ AND pn.nspname != 'information_schema'"; - $lan_where = "AND pl.lanispl"; - } else { - $where = ''; - $lan_where = ''; - } - - // Apply outer filter - $sql = ''; - if ($filter != '') { - $sql = "SELECT * FROM ("; - } - - $term = "\$_PATERN_\$%{$term}%\$_PATERN_\$"; - - $sql .= " + } + + return $this->selectSet($sql); + } + + /** + * 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 + */ + public function findObject($term, $filter) + { + $conf = $this->conf; + + /*about escaping: + * SET standard_conforming_string is not available before 8.2 + * So we must use PostgreSQL specific notation :/ + * E'' notation is not available before 8.1 + * $$ is available since 8.0 + * Nothing specific from 7.4 + */ + + // Escape search term for ILIKE match + $this->clean($term); + $this->clean($filter); + $term = str_replace('_', '\_', $term); + $term = str_replace('%', '\%', $term); + + // 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 \$_PATERN_\$pg\_%\$_PATERN_\$ AND pn.nspname != 'information_schema'"; + $lan_where = "AND pl.lanispl"; + } else { + $where = ''; + $lan_where = ''; + } + + // Apply outer filter + $sql = ''; + if ($filter != '') { + $sql = "SELECT * FROM ("; + } + + $term = "\$_PATERN_\$%{$term}%\$_PATERN_\$"; + + $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 @@ -819,9 +859,9 @@ class Postgres extends ADODB_base { 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 .= " + // 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 @@ -846,10 +886,10 @@ class Postgres extends ADODB_base { pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid AND po.opcname ILIKE {$term} {$where} "; - } - // Otherwise just add domains - else { - $sql .= " + } + // 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 @@ -857,45 +897,47 @@ class Postgres extends ADODB_base { 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}%' "; - } + 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"; + $sql .= "ORDER BY type, schemaname, relname, name"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - /** - * Returns all available variable information. - * @return A recordset - */ - function getVariables() { - $sql = "SHOW ALL"; + /** + * Returns all available variable information. + * @return A recordset + */ + public function getVariables() + { + $sql = "SHOW ALL"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Schema functons + // Schema functons - /** - * Return all schemas in the current database. - * @return All schemas, sorted alphabetically - */ - function getSchemas() { - $conf = $this->conf; + /** + * Return all schemas in the current database. + * @return All schemas, sorted alphabetically + */ + public function getSchemas() + { + $conf = $this->conf; - if (!$conf['show_system']) { - $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'"; + if (!$conf['show_system']) { + $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'"; - } else { - $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; - } + } else { + $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; + } - $sql = " + $sql = " SELECT pn.nspname, pu.rolname AS nspowner, pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment FROM pg_catalog.pg_namespace pn @@ -903,233 +945,242 @@ class Postgres extends ADODB_base { {$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 = " + return $this->selectSet($sql); + } + + /** + * Return all information relating to a schema + * @param $schema The name of the schema + * @return Schema information + */ + public function getSchemaByName($schema) + { + $this->clean($schema); + $sql = " SELECT nspname, nspowner, r.rolname AS ownername, nspacl, pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment FROM pg_catalog.pg_namespace pn LEFT JOIN pg_roles as r ON pn.nspowner = r.oid WHERE nspname='{$schema}'"; - return $this->selectSet($sql); - } - - /** - * 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->_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 = []; - foreach ($paths as $schema) { - if ($schema != '') { - $temp[] = $schema; - } - - } - $this->fieldArrayClean($temp); - - $sql = 'SET SEARCH_PATH TO "' . implode('","', $temp) . '"'; - - return $this->execute($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); - - $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; - } - - /** - * Updates a schema. - * @param $schemaname The name of the schema to drop - * @param $comment The new comment for this schema - * @param $owner The new owner for this schema - * @return 0 success - */ - function updateSchema($schemaname, $comment, $name, $owner) { - $this->fieldClean($schemaname); - $this->fieldClean($name); - $this->fieldClean($owner); - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $status = $this->setComment('SCHEMA', $schemaname, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $schema_rs = $this->getSchemaByName($schemaname); - /* Only if the owner change */ - if ($schema_rs->fields['ownername'] != $owner) { - $sql = "ALTER SCHEMA \"{$schemaname}\" OWNER TO \"{$owner}\""; - $status = $this->execute($sql); - 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(); - } - - /** - * 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); - } - - /** - * 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')); - } - - // 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 null error - **/ - function hasObjectID($table) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $sql = "SELECT relhasoids FROM pg_catalog.pg_class WHERE relname='{$table}' + return $this->selectSet($sql); + } + + /** + * Sets the current working schema. Will also set Class variable. + * @param $schema The the name of the schema to work in + * @return 0 success + */ + public 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->_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 + */ + public 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 = []; + foreach ($paths as $schema) { + if ($schema != '') { + $temp[] = $schema; + } + + } + $this->fieldArrayClean($temp); + + $sql = 'SET SEARCH_PATH TO "' . implode('","', $temp) . '"'; + + return $this->execute($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 + */ + public function createSchema($schemaname, $authorization = '', $comment = '') + { + $this->fieldClean($schemaname); + $this->fieldClean($authorization); + + $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; + } + + /** + * Updates a schema. + * @param $schemaname The name of the schema to drop + * @param $comment The new comment for this schema + * @param $owner The new owner for this schema + * @return 0 success + */ + public function updateSchema($schemaname, $comment, $name, $owner) + { + $this->fieldClean($schemaname); + $this->fieldClean($name); + $this->fieldClean($owner); + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $status = $this->setComment('SCHEMA', $schemaname, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $schema_rs = $this->getSchemaByName($schemaname); + /* Only if the owner change */ + if ($schema_rs->fields['ownername'] != $owner) { + $sql = "ALTER SCHEMA \"{$schemaname}\" OWNER TO \"{$owner}\""; + $status = $this->execute($sql); + 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(); + } + + /** + * Drops a schema. + * @param $schemaname The name of the schema to drop + * @param $cascade True to cascade drop, false to restrict + * @return 0 success + */ + public function dropSchema($schemaname, $cascade) + { + $this->fieldClean($schemaname); + + $sql = "DROP SCHEMA \"{$schemaname}\""; + if ($cascade) { + $sql .= " CASCADE"; + } + + return $this->execute($sql); + } + + /** + * Return the current schema search path + * @return Array of schema names + */ + public function getSearchPath() + { + $sql = 'SELECT current_schemas(false) AS search_path'; + + return $this->phpArray($this->selectField($sql, 'search_path')); + } + + // 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 null error + **/ + public function hasObjectID($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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='{$c_schema}')"; - $rs = $this->selectSet($sql); - if ($rs->recordCount() != 1) { - return null; - } 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $sql = " + $rs = $this->selectSet($sql); + if ($rs->recordCount() != 1) { + return null; + } 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 + */ + public function getTable($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + + $sql = " SELECT c.relname, n.nspname, u.usename AS relowner, pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment, @@ -1142,25 +1193,26 @@ class Postgres extends ADODB_base { AND n.oid = c.relnamespace 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - if ($all) { - // Exclude pg_catalog and information_schema tables - $sql = "SELECT schemaname AS nspname, tablename AS relname, tableowner AS relowner + 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 + */ + public function getTables($all = false) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + 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, + } 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 @@ -1169,28 +1221,29 @@ class Postgres extends ADODB_base { WHERE c.relkind = 'r' AND nspname='{$c_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 = '') { - $c_schema = $this->_schema; - $this->clean($c_schema); - $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 = " + } + + 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 + */ + public function getTableAttributes($table, $field = '') + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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, a.attnum, pg_catalog.format_type(a.atttypid, a.atttypmod) as type, @@ -1219,8 +1272,8 @@ class Postgres extends ADODB_base { nspname = '{$c_schema}')) AND a.attnum > 0 AND NOT a.attisdropped ORDER BY a.attnum"; - } else { - $sql = " + } else { + $sql = " SELECT a.attname, a.attnum, pg_catalog.format_type(a.atttypid, a.atttypmod) as type, @@ -1239,22 +1292,23 @@ class Postgres extends ADODB_base { AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}')) AND a.attname = '{$field}'"; - } - - return $this->selectSet($sql); - } - - /** - * 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $sql = " + } + + return $this->selectSet($sql); + } + + /** + * Finds the names and schemas of parent tables (in order) + * @param $table The table to find the parents for + * @return A recordset + */ + public function getTableParents($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + + $sql = " SELECT pn.nspname, relname FROM @@ -1268,20 +1322,21 @@ class Postgres extends ADODB_base { 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $sql = " + return $this->selectSet($sql); + } + + /** + * Finds the names and schemas of child tables + * @param $table The table to find the children for + * @return A recordset + */ + public function getTableChildren($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + + $sql = " SELECT pn.nspname, relname FROM @@ -1293,1256 +1348,1278 @@ class Postgres extends ADODB_base { AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}')) "; - return $this->selectSet($sql); - } - - /** - * Returns the SQL definition for the table. - * @pre MUST be run within a transaction - * @param $table The table to define - * @param $clean True to issue drop command, false otherwise - * @return A string containing the formatted SQL code - * @return null On error - */ - function getTableDefPrefix($table, $clean = false) { - // Fetch table - $t = $this->getTable($table); - if (!is_object($t) || $t->recordCount() != 1) { - $this->rollbackTransaction(); - return null; - } - $this->fieldClean($t->fields['relname']); - $this->fieldClean($t->fields['nspname']); - - // Fetch attributes - $atts = $this->getTableAttributes($table); - if (!is_object($atts)) { - $this->rollbackTransaction(); - return null; - } - - // Fetch constraints - $cons = $this->getConstraints($table); - if (!is_object($cons)) { - $this->rollbackTransaction(); - return null; - } - - // Output a reconnect command to create the table as the correct user - $sql = $this->getChangeUserSQL($t->fields['relowner']) . "\n\n"; - - // Set schema search path - $sql .= "SET search_path = \"{$t->fields['nspname']}\", pg_catalog;\n\n"; - - // Begin CREATE TABLE definition - $sql .= "-- Definition\n\n"; - // DROP TABLE must be fully qualified in case a table with the same name exists - // in pg_catalog. - if (!$clean) { - $sql .= "-- "; - } - - $sql .= "DROP TABLE "; - $sql .= "\"{$t->fields['nspname']}\".\"{$t->fields['relname']}\";\n"; - $sql .= "CREATE TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" (\n"; - - // Output all table columns - $col_comments_sql = ''; // Accumulate comments on columns - $num = $atts->recordCount() + $cons->recordCount(); - $i = 1; - while (!$atts->EOF) { - $this->fieldClean($atts->fields['attname']); - $sql .= " \"{$atts->fields['attname']}\""; - // Dump SERIAL and BIGSERIAL columns correctly - if ($this->phpBool($atts->fields['attisserial']) && - ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) { - if ($atts->fields['type'] == 'integer') { - $sql .= " SERIAL"; - } else { - $sql .= " BIGSERIAL"; - } - - } else { - $sql .= " " . $this->formatType($atts->fields['type'], $atts->fields['atttypmod']); - - // Add NOT NULL if necessary - if ($this->phpBool($atts->fields['attnotnull'])) { - $sql .= " NOT NULL"; - } - - // Add default if necessary - if ($atts->fields['adsrc'] !== null) { - $sql .= " DEFAULT {$atts->fields['adsrc']}"; - } - - } - - // Output comma or not - if ($i < $num) { - $sql .= ",\n"; - } else { - $sql .= "\n"; - } - - // Does this column have a comment? - if ($atts->fields['comment'] !== null) { - $this->clean($atts->fields['comment']); - $col_comments_sql .= "COMMENT ON COLUMN \"{$t->fields['relname']}\".\"{$atts->fields['attname']}\" IS '{$atts->fields['comment']}';\n"; - } - - $atts->moveNext(); - $i++; - } - // Output all table constraints - while (!$cons->EOF) { - $this->fieldClean($cons->fields['conname']); - $sql .= " CONSTRAINT \"{$cons->fields['conname']}\" "; - // Nasty hack to support pre-7.4 PostgreSQL - if ($cons->fields['consrc'] !== null) { - $sql .= $cons->fields['consrc']; - } else { - switch ($cons->fields['contype']) { - case 'p': - $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey'])); - $sql .= "PRIMARY KEY (" . join(',', $keys) . ")"; - break; - case 'u': - $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey'])); - $sql .= "UNIQUE (" . join(',', $keys) . ")"; - break; - default: - // Unrecognised constraint - $this->rollbackTransaction(); - return null; - } - } - - // Output comma or not - if ($i < $num) { - $sql .= ",\n"; - } else { - $sql .= "\n"; - } - - $cons->moveNext(); - $i++; - } - - $sql .= ")"; - - // @@@@ DUMP CLUSTERING INFORMATION - - // Inherits - /* - * XXX: This is currently commented out as handling inheritance isn't this simple. - * You also need to make sure you don't dump inherited columns and defaults, as well - * as inherited NOT NULL and CHECK constraints. So for the time being, we just do - * not claim to support inheritance. - $parents = $this->getTableParents($table); - if ($parents->recordCount() > 0) { - $sql .= " INHERITS ("; - while (!$parents->EOF) { - $this->fieldClean($parents->fields['relname']); - // Qualify the parent table if it's in another schema - if ($parents->fields['schemaname'] != $this->_schema) { - $this->fieldClean($parents->fields['schemaname']); - $sql .= "\"{$parents->fields['schemaname']}\"."; - } - $sql .= "\"{$parents->fields['relname']}\""; - - $parents->moveNext(); - if (!$parents->EOF) $sql .= ', '; - } - $sql .= ")"; - } - */ - - // Handle WITHOUT OIDS - if ($this->hasObjectID($table)) { - $sql .= " WITH OIDS"; - } else { - $sql .= " WITHOUT OIDS"; - } - - $sql .= ";\n"; - - // Column storage and statistics - $atts->moveFirst(); - $first = true; - while (!$atts->EOF) { - $this->fieldClean($atts->fields['attname']); - // Statistics first - if ($atts->fields['attstattarget'] >= 0) { - if ($first) { - $sql .= "\n"; - $first = false; - } - $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n"; - } - // Then storage - if ($atts->fields['attstorage'] != $atts->fields['typstorage']) { - switch ($atts->fields['attstorage']) { - case 'p': - $storage = 'PLAIN'; - break; - case 'e': - $storage = 'EXTERNAL'; - break; - case 'm': - $storage = 'MAIN'; - break; - case 'x': - $storage = 'EXTENDED'; - break; - default: - // Unknown storage type - $this->rollbackTransaction(); - return null; - } - $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n"; - } - - $atts->moveNext(); - } - - // Comment - if ($t->fields['relcomment'] !== null) { - $this->clean($t->fields['relcomment']); - $sql .= "\n-- Comment\n\n"; - $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n"; - } - - // Add comments on columns, if any - if ($col_comments_sql != '') { - $sql .= $col_comments_sql; - } - - // Privileges - $privs = $this->getPrivileges($table, 'table'); - if (!is_array($privs)) { - $this->rollbackTransaction(); - return null; - } - - if (sizeof($privs) > 0) { - $sql .= "\n-- Privileges\n\n"; - /* - * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to - * wire-in knowledge about the default public privileges for different - * kinds of objects. - */ - $sql .= "REVOKE ALL ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" FROM PUBLIC;\n"; - foreach ($privs as $v) { - // Get non-GRANT OPTION privs - $nongrant = array_diff($v[2], $v[4]); - - // Skip empty or owner ACEs - if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $t->fields['relowner'])) { - continue; - } - - // Change user if necessary - if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { - $grantor = $v[3]; - $this->clean($grantor); - $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n"; - } - - // Output privileges with no GRANT OPTION - $sql .= "GRANT " . join(', ', $nongrant) . " ON TABLE \"{$t->fields['relname']}\" TO "; - switch ($v[0]) { - case 'public': - $sql .= "PUBLIC;\n"; - break; - case 'user': - $this->fieldClean($v[1]); - $sql .= "\"{$v[1]}\";\n"; - break; - case 'group': - $this->fieldClean($v[1]); - $sql .= "GROUP \"{$v[1]}\";\n"; - break; - default: - // Unknown privilege type - fail - $this->rollbackTransaction(); - return null; - } - - // Reset user if necessary - if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { - $sql .= "RESET SESSION AUTHORIZATION;\n"; - } - - // Output privileges with GRANT OPTION - - // Skip empty or owner ACEs - if (!$this->hasGrantOption() || sizeof($v[4]) == 0) { - continue; - } - - // Change user if necessary - if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { - $grantor = $v[3]; - $this->clean($grantor); - $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n"; - } - - $sql .= "GRANT " . join(', ', $v[4]) . " ON \"{$t->fields['relname']}\" TO "; - switch ($v[0]) { - case 'public': - $sql .= "PUBLIC"; - break; - case 'user': - $this->fieldClean($v[1]); - $sql .= "\"{$v[1]}\""; - break; - case 'group': - $this->fieldClean($v[1]); - $sql .= "GROUP \"{$v[1]}\""; - break; - default: - // Unknown privilege type - fail - return null; - } - $sql .= " WITH GRANT OPTION;\n"; - - // Reset user if necessary - if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { - $sql .= "RESET SESSION AUTHORIZATION;\n"; - } - - } - } - - // Add a newline to separate data that follows (if any) - $sql .= "\n"; - - return $sql; - } - - /** - * Returns extra table definition information that is most usefully - * dumped after the table contents for speed and efficiency reasons - * @param $table The table to define - * @return A string containing the formatted SQL code - * @return null On error - */ - function getTableDefSuffix($table) { - $sql = ''; - - // Indexes - $indexes = $this->getIndexes($table); - if (!is_object($indexes)) { - $this->rollbackTransaction(); - return null; - } - - if ($indexes->recordCount() > 0) { - $sql .= "\n-- Indexes\n\n"; - while (!$indexes->EOF) { - $sql .= $indexes->fields['inddef'] . ";\n"; - - $indexes->moveNext(); - } - } - - // Triggers - $triggers = $this->getTriggers($table); - if (!is_object($triggers)) { - $this->rollbackTransaction(); - return null; - } - - if ($triggers->recordCount() > 0) { - $sql .= "\n-- Triggers\n\n"; - while (!$triggers->EOF) { - - $sql .= $triggers->fields['tgdef']; - $sql .= ";\n"; - - $triggers->moveNext(); - } - } - - // Rules - $rules = $this->getRules($table); - if (!is_object($rules)) { - $this->rollbackTransaction(); - return null; - } - - if ($rules->recordCount() > 0) { - $sql .= "\n-- Rules\n\n"; - while (!$rules->EOF) { - $sql .= $rules->fields['definition'] . "\n"; - - $rules->moveNext(); - } - } - - return $sql; - } - - /** - * 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($name); - - $status = $this->beginTransaction(); - if ($status != 0) { - return -1; - } - - $found = false; - $first = true; - $comment_sql = ''; //Accumulate comments for the columns - $sql = "CREATE TABLE \"{$f_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 = []; - 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 ($withoutoids) { - $sql .= ' WITHOUT OIDS'; - } else { - $sql .= ' WITH 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 = '') { - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $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 \"{$f_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 - * /!\ this function is called from _alterTable which take care of escaping fields - * @param $tblrs The table RecordSet returned by getTable() - * @param $name The new table's name - * @return 0 success - */ - function alterTableName($tblrs, $name = null) { - /* vars cleaned in _alterTable */ - // Rename (only if name has changed) - if (!empty($name) && ($name != $tblrs->fields['relname'])) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - - $sql = "ALTER TABLE \"{$f_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 - * /!\ this function is called from _alterTable which take care of escaping fields - * @param $tblrs The table RecordSet returned by getTable() - * @param $name The new table's owner - * @return 0 success - */ - function alterTableOwner($tblrs, $owner = null) { - /* vars cleaned in _alterTable */ - if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - // 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 \"{$f_schema}\".\"{$tblrs->fields['relname']}\" OWNER TO \"{$owner}\""; - - return $this->execute($sql); - } - return 0; - } - - /** - * Alter a table's tablespace - * /!\ this function is called from _alterTable which take care of escaping fields - * @param $tblrs The table RecordSet returned by getTable() - * @param $name The new table's tablespace - * @return 0 success - */ - function alterTableTablespace($tblrs, $tablespace = null) { - /* vars cleaned in _alterTable */ - if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - - // If tablespace has been changed, then do the alteration. We - // don't want to do this unnecessarily. - $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET TABLESPACE \"{$tablespace}\""; - - return $this->execute($sql); - } - return 0; - } - - /** - * Alter a table's schema - * /!\ this function is called from _alterTable which take care of escaping fields - * @param $tblrs The table RecordSet returned by getTable() - * @param $name The new table's schema - * @return 0 success - */ - function alterTableSchema($tblrs, $schema = null) { - /* vars cleaned in _alterTable */ - if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - // If tablespace has been changed, then do the alteration. We - // don't want to do this unnecessarily. - $sql = "ALTER TABLE \"{$f_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 - $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) { - - $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 - * 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - $this->arrayClean($atts); - - if (!is_array($atts)) { - return -1; - } - - if (sizeof($atts) == 0) { - return []; - } - - $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE + return $this->selectSet($sql); + } + + /** + * Returns the SQL definition for the table. + * @pre MUST be run within a transaction + * @param $table The table to define + * @param $clean True to issue drop command, false otherwise + * @return A string containing the formatted SQL code + * @return null On error + */ + public function getTableDefPrefix($table, $clean = false) + { + // Fetch table + $t = $this->getTable($table); + if (!is_object($t) || $t->recordCount() != 1) { + $this->rollbackTransaction(); + return null; + } + $this->fieldClean($t->fields['relname']); + $this->fieldClean($t->fields['nspname']); + + // Fetch attributes + $atts = $this->getTableAttributes($table); + if (!is_object($atts)) { + $this->rollbackTransaction(); + return null; + } + + // Fetch constraints + $cons = $this->getConstraints($table); + if (!is_object($cons)) { + $this->rollbackTransaction(); + return null; + } + + // Output a reconnect command to create the table as the correct user + $sql = $this->getChangeUserSQL($t->fields['relowner']) . "\n\n"; + + // Set schema search path + $sql .= "SET search_path = \"{$t->fields['nspname']}\", pg_catalog;\n\n"; + + // Begin CREATE TABLE definition + $sql .= "-- Definition\n\n"; + // DROP TABLE must be fully qualified in case a table with the same name exists + // in pg_catalog. + if (!$clean) { + $sql .= "-- "; + } + + $sql .= "DROP TABLE "; + $sql .= "\"{$t->fields['nspname']}\".\"{$t->fields['relname']}\";\n"; + $sql .= "CREATE TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" (\n"; + + // Output all table columns + $col_comments_sql = ''; // Accumulate comments on columns + $num = $atts->recordCount() + $cons->recordCount(); + $i = 1; + while (!$atts->EOF) { + $this->fieldClean($atts->fields['attname']); + $sql .= " \"{$atts->fields['attname']}\""; + // Dump SERIAL and BIGSERIAL columns correctly + if ($this->phpBool($atts->fields['attisserial']) && + ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) { + if ($atts->fields['type'] == 'integer') { + $sql .= " SERIAL"; + } else { + $sql .= " BIGSERIAL"; + } + + } else { + $sql .= " " . $this->formatType($atts->fields['type'], $atts->fields['atttypmod']); + + // Add NOT NULL if necessary + if ($this->phpBool($atts->fields['attnotnull'])) { + $sql .= " NOT NULL"; + } + + // Add default if necessary + if ($atts->fields['adsrc'] !== null) { + $sql .= " DEFAULT {$atts->fields['adsrc']}"; + } + + } + + // Output comma or not + if ($i < $num) { + $sql .= ",\n"; + } else { + $sql .= "\n"; + } + + // Does this column have a comment? + if ($atts->fields['comment'] !== null) { + $this->clean($atts->fields['comment']); + $col_comments_sql .= "COMMENT ON COLUMN \"{$t->fields['relname']}\".\"{$atts->fields['attname']}\" IS '{$atts->fields['comment']}';\n"; + } + + $atts->moveNext(); + $i++; + } + // Output all table constraints + while (!$cons->EOF) { + $this->fieldClean($cons->fields['conname']); + $sql .= " CONSTRAINT \"{$cons->fields['conname']}\" "; + // Nasty hack to support pre-7.4 PostgreSQL + if ($cons->fields['consrc'] !== null) { + $sql .= $cons->fields['consrc']; + } else { + switch ($cons->fields['contype']) { + case 'p': + $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey'])); + $sql .= "PRIMARY KEY (" . join(',', $keys) . ")"; + break; + case 'u': + $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey'])); + $sql .= "UNIQUE (" . join(',', $keys) . ")"; + break; + default: + // Unrecognised constraint + $this->rollbackTransaction(); + return null; + } + } + + // Output comma or not + if ($i < $num) { + $sql .= ",\n"; + } else { + $sql .= "\n"; + } + + $cons->moveNext(); + $i++; + } + + $sql .= ")"; + + // @@@@ DUMP CLUSTERING INFORMATION + + // Inherits + /* + * XXX: This is currently commented out as handling inheritance isn't this simple. + * You also need to make sure you don't dump inherited columns and defaults, as well + * as inherited NOT NULL and CHECK constraints. So for the time being, we just do + * not claim to support inheritance. + $parents = $this->getTableParents($table); + if ($parents->recordCount() > 0) { + $sql .= " INHERITS ("; + while (!$parents->EOF) { + $this->fieldClean($parents->fields['relname']); + // Qualify the parent table if it's in another schema + if ($parents->fields['schemaname'] != $this->_schema) { + $this->fieldClean($parents->fields['schemaname']); + $sql .= "\"{$parents->fields['schemaname']}\"."; + } + $sql .= "\"{$parents->fields['relname']}\""; + + $parents->moveNext(); + if (!$parents->EOF) $sql .= ', '; + } + $sql .= ")"; + } + */ + + // Handle WITHOUT OIDS + if ($this->hasObjectID($table)) { + $sql .= " WITH OIDS"; + } else { + $sql .= " WITHOUT OIDS"; + } + + $sql .= ";\n"; + + // Column storage and statistics + $atts->moveFirst(); + $first = true; + while (!$atts->EOF) { + $this->fieldClean($atts->fields['attname']); + // Statistics first + if ($atts->fields['attstattarget'] >= 0) { + if ($first) { + $sql .= "\n"; + $first = false; + } + $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n"; + } + // Then storage + if ($atts->fields['attstorage'] != $atts->fields['typstorage']) { + switch ($atts->fields['attstorage']) { + case 'p': + $storage = 'PLAIN'; + break; + case 'e': + $storage = 'EXTERNAL'; + break; + case 'm': + $storage = 'MAIN'; + break; + case 'x': + $storage = 'EXTENDED'; + break; + default: + // Unknown storage type + $this->rollbackTransaction(); + return null; + } + $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n"; + } + + $atts->moveNext(); + } + + // Comment + if ($t->fields['relcomment'] !== null) { + $this->clean($t->fields['relcomment']); + $sql .= "\n-- Comment\n\n"; + $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n"; + } + + // Add comments on columns, if any + if ($col_comments_sql != '') { + $sql .= $col_comments_sql; + } + + // Privileges + $privs = $this->getPrivileges($table, 'table'); + if (!is_array($privs)) { + $this->rollbackTransaction(); + return null; + } + + if (sizeof($privs) > 0) { + $sql .= "\n-- Privileges\n\n"; + /* + * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to + * wire-in knowledge about the default public privileges for different + * kinds of objects. + */ + $sql .= "REVOKE ALL ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" FROM PUBLIC;\n"; + foreach ($privs as $v) { + // Get non-GRANT OPTION privs + $nongrant = array_diff($v[2], $v[4]); + + // Skip empty or owner ACEs + if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $t->fields['relowner'])) { + continue; + } + + // Change user if necessary + if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { + $grantor = $v[3]; + $this->clean($grantor); + $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n"; + } + + // Output privileges with no GRANT OPTION + $sql .= "GRANT " . join(', ', $nongrant) . " ON TABLE \"{$t->fields['relname']}\" TO "; + switch ($v[0]) { + case 'public': + $sql .= "PUBLIC;\n"; + break; + case 'user': + $this->fieldClean($v[1]); + $sql .= "\"{$v[1]}\";\n"; + break; + case 'group': + $this->fieldClean($v[1]); + $sql .= "GROUP \"{$v[1]}\";\n"; + break; + default: + // Unknown privilege type - fail + $this->rollbackTransaction(); + return null; + } + + // Reset user if necessary + if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { + $sql .= "RESET SESSION AUTHORIZATION;\n"; + } + + // Output privileges with GRANT OPTION + + // Skip empty or owner ACEs + if (!$this->hasGrantOption() || sizeof($v[4]) == 0) { + continue; + } + + // Change user if necessary + if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { + $grantor = $v[3]; + $this->clean($grantor); + $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n"; + } + + $sql .= "GRANT " . join(', ', $v[4]) . " ON \"{$t->fields['relname']}\" TO "; + switch ($v[0]) { + case 'public': + $sql .= "PUBLIC"; + break; + case 'user': + $this->fieldClean($v[1]); + $sql .= "\"{$v[1]}\""; + break; + case 'group': + $this->fieldClean($v[1]); + $sql .= "GROUP \"{$v[1]}\""; + break; + default: + // Unknown privilege type - fail + return null; + } + $sql .= " WITH GRANT OPTION;\n"; + + // Reset user if necessary + if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { + $sql .= "RESET SESSION AUTHORIZATION;\n"; + } + + } + } + + // Add a newline to separate data that follows (if any) + $sql .= "\n"; + + return $sql; + } + + /** + * Returns extra table definition information that is most usefully + * dumped after the table contents for speed and efficiency reasons + * @param $table The table to define + * @return A string containing the formatted SQL code + * @return null On error + */ + public function getTableDefSuffix($table) + { + $sql = ''; + + // Indexes + $indexes = $this->getIndexes($table); + if (!is_object($indexes)) { + $this->rollbackTransaction(); + return null; + } + + if ($indexes->recordCount() > 0) { + $sql .= "\n-- Indexes\n\n"; + while (!$indexes->EOF) { + $sql .= $indexes->fields['inddef'] . ";\n"; + + $indexes->moveNext(); + } + } + + // Triggers + $triggers = $this->getTriggers($table); + if (!is_object($triggers)) { + $this->rollbackTransaction(); + return null; + } + + if ($triggers->recordCount() > 0) { + $sql .= "\n-- Triggers\n\n"; + while (!$triggers->EOF) { + + $sql .= $triggers->fields['tgdef']; + $sql .= ";\n"; + + $triggers->moveNext(); + } + } + + // Rules + $rules = $this->getRules($table); + if (!is_object($rules)) { + $this->rollbackTransaction(); + return null; + } + + if ($rules->recordCount() > 0) { + $sql .= "\n-- Rules\n\n"; + while (!$rules->EOF) { + $sql .= $rules->fields['definition'] . "\n"; + + $rules->moveNext(); + } + } + + return $sql; + } + + /** + * 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 + */ + public function createTable($name, $fields, $field, $type, $array, $length, $notnull, + $default, $withoutoids, $colcomment, $tblcomment, $tablespace, + $uniquekey, $primarykey) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($name); + + $status = $this->beginTransaction(); + if ($status != 0) { + return -1; + } + + $found = false; + $first = true; + $comment_sql = ''; //Accumulate comments for the columns + $sql = "CREATE TABLE \"{$f_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 = []; + 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 ($withoutoids) { + $sql .= ' WITHOUT OIDS'; + } else { + $sql .= ' WITH 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) + */ + public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '') + { + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $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 \"{$f_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 + * /!\ this function is called from _alterTable which take care of escaping fields + * @param $tblrs The table RecordSet returned by getTable() + * @param $name The new table's name + * @return 0 success + */ + public function alterTableName($tblrs, $name = null) + { + /* vars cleaned in _alterTable */ + // Rename (only if name has changed) + if (!empty($name) && ($name != $tblrs->fields['relname'])) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + + $sql = "ALTER TABLE \"{$f_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 + * /!\ this function is called from _alterTable which take care of escaping fields + * @param $tblrs The table RecordSet returned by getTable() + * @param $name The new table's owner + * @return 0 success + */ + public function alterTableOwner($tblrs, $owner = null) + { + /* vars cleaned in _alterTable */ + if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + // 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 \"{$f_schema}\".\"{$tblrs->fields['relname']}\" OWNER TO \"{$owner}\""; + + return $this->execute($sql); + } + return 0; + } + + /** + * Alter a table's tablespace + * /!\ this function is called from _alterTable which take care of escaping fields + * @param $tblrs The table RecordSet returned by getTable() + * @param $name The new table's tablespace + * @return 0 success + */ + public function alterTableTablespace($tblrs, $tablespace = null) + { + /* vars cleaned in _alterTable */ + if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + + // If tablespace has been changed, then do the alteration. We + // don't want to do this unnecessarily. + $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET TABLESPACE \"{$tablespace}\""; + + return $this->execute($sql); + } + return 0; + } + + /** + * Alter a table's schema + * /!\ this function is called from _alterTable which take care of escaping fields + * @param $tblrs The table RecordSet returned by getTable() + * @param $name The new table's schema + * @return 0 success + */ + public function alterTableSchema($tblrs, $schema = null) + { + /* vars cleaned in _alterTable */ + if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + // If tablespace has been changed, then do the alteration. We + // don't want to do this unnecessarily. + $sql = "ALTER TABLE \"{$f_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 + $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 + */ + public function alterTable($table, $name, $owner, $schema, $comment, $tablespace) + { + + $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 + */ + public function getChangeUserSQL($user) + { + $this->clean($user); + return "SET SESSION AUTHORIZATION '{$user}';"; + } + + /** + * 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 + */ + public function getAttributeNames($table, $atts) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + $this->arrayClean($atts); + + if (!is_array($atts)) { + return -1; + } + + if (sizeof($atts) == 0) { + return []; + } + + $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='{$c_schema}')) AND attnum IN ('" . join("','", $atts) . "')"; - $rs = $this->selectSet($sql); - if ($rs->recordCount() != sizeof($atts)) { - return -2; - } else { - $temp = []; - while (!$rs->EOF) { - $temp[$rs->fields['attnum']] = $rs->fields['attname']; - $rs->moveNext(); - } - return $temp; - } - } - - /** - * Empties a table in the database - * @param $table The table to be emptied - * @return 0 success - */ - function emptyTable($table) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - - $sql = "DELETE FROM \"{$f_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - - $sql = "DROP TABLE \"{$f_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 - * @param $type The type of the column - * @param $array True if array type, false otherwise - * @param $notnull True if NOT NULL, false otherwise - * @param $default The default for the column. '' for none. - * @param $length The optional size of the column (ie. 30 for varchar(30)) - * @return 0 success - */ - function addColumn($table, $column, $type, $array, $length, $notnull, $default, $comment) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldClean($column); - $this->clean($type); - $this->clean($length); - - if ($length == '') { - $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$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 = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" timestamp({$length}){$qual}"; - break; - case 'time with time zone': - case 'time without time zone': - $qual = substr($type, 4); - $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" time({$length}){$qual}"; - break; - default: - $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}({$length})"; - } - } - - // Add array qualifier, if requested - if ($array) { - $sql .= '[]'; - } - - // If we have advanced column adding, add the extra qualifiers - if ($this->hasCreateFieldWithConstraints()) { - // NOT NULL clause - if ($notnull) { - $sql .= ' NOT NULL'; - } - - // DEFAULT clause - if ($default != '') { - $sql .= ' DEFAULT ' . $default; - } - - } - - $status = $this->beginTransaction(); - if ($status != 0) { - return -1; - } - - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $status = $this->setComment('COLUMN', $column, $table, $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - return $this->endTransaction(); - } - - /** - * 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 -4 rename column error - * @return -5 comment error - * @return -6 transaction error - */ - function alterColumn($table, $column, $name, $notnull, $oldnotnull, $default, $olddefault, - $type, $length, $array, $oldtype, $comment) { - // Begin transaction - $status = $this->beginTransaction(); - $sql = ''; - if ($status != 0) { - $this->rollbackTransaction(); - return [-6, $sql]; - } - - // Rename the column, if it has been changed - if ($column != $name) { - $status = $this->renameColumn($table, $column, $name); - if ($status != 0) { - $this->rollbackTransaction(); - return [-4, $sql]; - } - } - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($name); - $this->fieldClean($table); - $this->fieldClean($column); - - $toAlter = []; - // Create the command for changing nullability - if ($notnull != $oldnotnull) { - $toAlter[] = "ALTER COLUMN \"{$name}\" " . (($notnull) ? 'SET' : 'DROP') . " NOT NULL"; - } - - // Add default, if it has changed - if ($default != $olddefault) { - if ($default == '') { - $toAlter[] = "ALTER COLUMN \"{$name}\" DROP DEFAULT"; - } else { - $toAlter[] = "ALTER COLUMN \"{$name}\" 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) { - $toAlter[] = "ALTER COLUMN \"{$name}\" TYPE {$ftype}"; - } - - // Attempt to process the batch alteration, if anything has been changed - if (!empty($toAlter)) { - // Initialise an empty SQL string - $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" " - . implode(',', $toAlter); - - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return [-1, $sql]; - } - } - - // Update the comment on the column - $status = $this->setComment('COLUMN', $name, $table, $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return [-5, $sql]; - } - - return [$this->endTransaction(), $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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldClean($column); - $this->fieldClean($newName); - - $sql = "ALTER TABLE \"{$f_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldClean($column); - - $sql = "ALTER TABLE \"{$f_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldClean($column); - - $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . (($state) ? 'DROP' : 'SET') . " NOT NULL"; - - 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldClean($column); - - $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DROP COLUMN \"{$column}\""; - if ($cascade) { - $sql .= " CASCADE"; - } - - 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldClean($column); - - $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT"; - - return $this->execute($sql); - } - - /** - * 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; - } - - // Set extra_float_digits to 2 - $sql = "SET extra_float_digits TO 2"; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - return 0; - } - - /** - * 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}\""); - } - - /** - * Returns all available autovacuum per table information. - * @param $table if given, return autovacuum info for the given table or return all informations for all table - * - * @return A recordset - */ - function getTableAutovacuum($table = '') { - - $sql = ''; - - if ($table !== '') { - $this->clean($table); - $c_schema = $this->_schema; - $this->clean($c_schema); - - $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions + $rs = $this->selectSet($sql); + if ($rs->recordCount() != sizeof($atts)) { + return -2; + } else { + $temp = []; + while (!$rs->EOF) { + $temp[$rs->fields['attnum']] = $rs->fields['attname']; + $rs->moveNext(); + } + return $temp; + } + } + + /** + * Empties a table in the database + * @param $table The table to be emptied + * @return 0 success + */ + public function emptyTable($table) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + + $sql = "DELETE FROM \"{$f_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 + */ + public function dropTable($table, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + + $sql = "DROP TABLE \"{$f_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 + * @param $type The type of the column + * @param $array True if array type, false otherwise + * @param $notnull True if NOT NULL, false otherwise + * @param $default The default for the column. '' for none. + * @param $length The optional size of the column (ie. 30 for varchar(30)) + * @return 0 success + */ + public function addColumn($table, $column, $type, $array, $length, $notnull, $default, $comment) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldClean($column); + $this->clean($type); + $this->clean($length); + + if ($length == '') { + $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$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 = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" timestamp({$length}){$qual}"; + break; + case 'time with time zone': + case 'time without time zone': + $qual = substr($type, 4); + $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" time({$length}){$qual}"; + break; + default: + $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}({$length})"; + } + } + + // Add array qualifier, if requested + if ($array) { + $sql .= '[]'; + } + + // If we have advanced column adding, add the extra qualifiers + if ($this->hasCreateFieldWithConstraints()) { + // NOT NULL clause + if ($notnull) { + $sql .= ' NOT NULL'; + } + + // DEFAULT clause + if ($default != '') { + $sql .= ' DEFAULT ' . $default; + } + + } + + $status = $this->beginTransaction(); + if ($status != 0) { + return -1; + } + + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $status = $this->setComment('COLUMN', $column, $table, $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + return $this->endTransaction(); + } + + /** + * 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 -4 rename column error + * @return -5 comment error + * @return -6 transaction error + */ + public function alterColumn($table, $column, $name, $notnull, $oldnotnull, $default, $olddefault, + $type, $length, $array, $oldtype, $comment) { + // Begin transaction + $status = $this->beginTransaction(); + $sql = ''; + if ($status != 0) { + $this->rollbackTransaction(); + return [-6, $sql]; + } + + // Rename the column, if it has been changed + if ($column != $name) { + $status = $this->renameColumn($table, $column, $name); + if ($status != 0) { + $this->rollbackTransaction(); + return [-4, $sql]; + } + } + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($name); + $this->fieldClean($table); + $this->fieldClean($column); + + $toAlter = []; + // Create the command for changing nullability + if ($notnull != $oldnotnull) { + $toAlter[] = "ALTER COLUMN \"{$name}\" " . (($notnull) ? 'SET' : 'DROP') . " NOT NULL"; + } + + // Add default, if it has changed + if ($default != $olddefault) { + if ($default == '') { + $toAlter[] = "ALTER COLUMN \"{$name}\" DROP DEFAULT"; + } else { + $toAlter[] = "ALTER COLUMN \"{$name}\" 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) { + $toAlter[] = "ALTER COLUMN \"{$name}\" TYPE {$ftype}"; + } + + // Attempt to process the batch alteration, if anything has been changed + if (!empty($toAlter)) { + // Initialise an empty SQL string + $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" " + . implode(',', $toAlter); + + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return [-1, $sql]; + } + } + + // Update the comment on the column + $status = $this->setComment('COLUMN', $name, $table, $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return [-5, $sql]; + } + + return [$this->endTransaction(), $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 + */ + public function renameColumn($table, $column, $newName) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldClean($column); + $this->fieldClean($newName); + + $sql = "ALTER TABLE \"{$f_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 + */ + public function setColumnDefault($table, $column, $default) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldClean($column); + + $sql = "ALTER TABLE \"{$f_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 + */ + public function setColumnNull($table, $column, $state) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldClean($column); + + $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . (($state) ? 'DROP' : 'SET') . " NOT NULL"; + + 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 + */ + public function dropColumn($table, $column, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldClean($column); + + $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DROP COLUMN \"{$column}\""; + if ($cascade) { + $sql .= " CASCADE"; + } + + 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 + */ + public function dropColumnDefault($table, $column) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldClean($column); + + $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT"; + + return $this->execute($sql); + } + + /** + * Sets up the data object for a dump. eg. Starts the appropriate + * transaction, sets variables, etc. + * @return 0 success + */ + public 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; + } + + // Set extra_float_digits to 2 + $sql = "SET extra_float_digits TO 2"; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + return 0; + } + + /** + * Ends the data object for a dump. + * @return 0 success + */ + public 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 + */ + public 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}\""); + } + + /** + * Returns all available autovacuum per table information. + * @param $table if given, return autovacuum info for the given table or return all informations for all table + * + * @return A recordset + */ + public function getTableAutovacuum($table = '') + { + + $sql = ''; + + if ($table !== '') { + $this->clean($table); + $c_schema = $this->_schema; + $this->clean($c_schema); + + $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind = 'r'::\"char\" @@ -2550,8 +2627,8 @@ class Postgres extends ADODB_base { AND c.reloptions IS NOT NULL AND c.relname = '{$table}' AND n.nspname = '{$c_schema}' ORDER BY nspname, relname"; - } else { - $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions + } else { + $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind = 'r'::\"char\" @@ -2559,54 +2636,55 @@ class Postgres extends ADODB_base { AND c.reloptions IS NOT NULL ORDER BY nspname, relname"; - } - - /* tmp var to parse the results */ - $_autovacs = $this->selectSet($sql); - - /* result aray to return as RS */ - $autovacs = []; - while (!$_autovacs->EOF) { - $_ = [ - 'nspname' => $_autovacs->fields['nspname'], - 'relname' => $_autovacs->fields['relname'], - ]; - - foreach (explode(',', $_autovacs->fields['reloptions']) as $var) { - list($o, $v) = explode('=', $var); - $_[$o] = $v; - } - - $autovacs[] = $_; - - $_autovacs->moveNext(); - } - - return new \PHPPgAdmin\ArrayRecordSet($autovacs); - } - - // Row 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; - $c_schema = $this->_schema; - $this->clean($c_schema); - $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 = " + } + + /* tmp var to parse the results */ + $_autovacs = $this->selectSet($sql); + + /* result aray to return as RS */ + $autovacs = []; + while (!$_autovacs->EOF) { + $_ = [ + 'nspname' => $_autovacs->fields['nspname'], + 'relname' => $_autovacs->fields['relname'], + ]; + + foreach (explode(',', $_autovacs->fields['reloptions']) as $var) { + list($o, $v) = explode('=', $var); + $_[$o] = $v; + } + + $autovacs[] = $_; + + $_autovacs->moveNext(); + } + + return new \PHPPgAdmin\ArrayRecordSet($autovacs); + } + + // Row 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 + */ + public function getRowIdentifier($table) + { + $oldtable = $table; + $c_schema = $this->_schema; + $this->clean($c_schema); + $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=( @@ -2617,206 +2695,210 @@ class Postgres extends ADODB_base { ) ) 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 = []; - if ($this->hasObjectID($table)) { - $temp = ['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; - } - } - } - - /** - * Adds a new row to a table - * @param $table The table in which to insert - * @param $fields Array of given field in values - * @param $values Array of 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, $fields, $values, $nulls, $format, $types) { - - if (!is_array($fields) || !is_array($values) || !is_array($nulls) - || !is_array($format) || !is_array($types) - || (count($fields) != count($values)) - ) { - return -1; - } else { - // Build clause - if (count($values) > 0) { - // Escape all field names - $fields = array_map(['\PHPPgAdmin\Database\Postgres', 'fieldClean'], $fields); - $f_schema = $this->_schema; - $this->fieldClean($table); - $this->fieldClean($f_schema); - - $sql = ''; - foreach ($values as $i => $value) { - - // Handle NULL values - if (isset($nulls[$i])) { - $sql .= ',NULL'; - } else { - $sql .= ',' . $this->formatValue($types[$i], $format[$i], $value); - } - - } - - $sql = "INSERT INTO \"{$f_schema}\".\"{$table}\" (\"" . implode('","', $fields) . "\") + $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 = []; + if ($this->hasObjectID($table)) { + $temp = ['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; + } + } + } + + /** + * Adds a new row to a table + * @param $table The table in which to insert + * @param $fields Array of given field in values + * @param $values Array of 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 + */ + public function insertRow($table, $fields, $values, $nulls, $format, $types) + { + + if (!is_array($fields) || !is_array($values) || !is_array($nulls) + || !is_array($format) || !is_array($types) + || (count($fields) != count($values)) + ) { + return -1; + } else { + // Build clause + if (count($values) > 0) { + // Escape all field names + $fields = array_map(['\PHPPgAdmin\Database\Postgres', 'fieldClean'], $fields); + $f_schema = $this->_schema; + $this->fieldClean($table); + $this->fieldClean($f_schema); + + $sql = ''; + foreach ($values as $i => $value) { + + // Handle NULL values + if (isset($nulls[$i])) { + $sql .= ',NULL'; + } else { + $sql .= ',' . $this->formatValue($types[$i], $format[$i], $value); + } + + } + + $sql = "INSERT INTO \"{$f_schema}\".\"{$table}\" (\"" . implode('","', $fields) . "\") VALUES (" . substr($sql, 1) . ")"; - return $this->execute($sql); - } - } - - return -1; - } - - /** - * Updates a row in a table - * @param $table The table in which to update - * @param $vars 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 - * @param $keyarr An array mapping column => value to update - * @return 0 success - * @return -1 invalid parameters - */ - function editRow($table, $vars, $nulls, $format, $types, $keyarr) { - if (!is_array($vars) || !is_array($nulls) || !is_array($format) || !is_array($types)) { - return -1; - } else { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - - // Build clause - if (sizeof($vars) > 0) { - - 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 (isset($sql)) { - $sql .= ", \"{$key}\"={$tmp}"; - } else { - $sql = "UPDATE \"{$f_schema}\".\"{$table}\" SET \"{$key}\"={$tmp}"; - } - - } - $first = true; - foreach ($keyarr as $k => $v) { - $this->fieldClean($k); - $this->clean($v); - if ($first) { - $sql .= " WHERE \"{$k}\"='{$v}'"; - $first = false; - } 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(); - return -1; - } - - $status = $this->execute($sql); - if ($status != 0) { - // update failed - $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(); - } - } - - /** - * 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 deleteRow($table, $key, $schema = false) { - if (!is_array($key)) { - return -1; - } else { - // 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; - } - - if ($schema === false) { - $schema = $this->_schema; - } - - $status = $this->delete($table, $key, $schema); - if ($status != 0 || $this->conn->Affected_Rows() != 1) { - $this->rollbackTransaction(); - return -2; - } - - // End transaction - return $this->endTransaction(); - } - } - - // Sequence functions - - /** - * Returns properties of a single sequence - * @param $sequence Sequence name - * @return A recordset - */ - function getSequence($sequence) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $c_sequence = $sequence; - $this->fieldClean($sequence); - $this->clean($c_sequence); - - $sql = " + return $this->execute($sql); + } + } + + return -1; + } + + /** + * Updates a row in a table + * @param $table The table in which to update + * @param $vars 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 + * @param $keyarr An array mapping column => value to update + * @return 0 success + * @return -1 invalid parameters + */ + public function editRow($table, $vars, $nulls, $format, $types, $keyarr) + { + if (!is_array($vars) || !is_array($nulls) || !is_array($format) || !is_array($types)) { + return -1; + } else { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + + // Build clause + if (sizeof($vars) > 0) { + + 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 (isset($sql)) { + $sql .= ", \"{$key}\"={$tmp}"; + } else { + $sql = "UPDATE \"{$f_schema}\".\"{$table}\" SET \"{$key}\"={$tmp}"; + } + + } + $first = true; + foreach ($keyarr as $k => $v) { + $this->fieldClean($k); + $this->clean($v); + if ($first) { + $sql .= " WHERE \"{$k}\"='{$v}'"; + $first = false; + } 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(); + return -1; + } + + $status = $this->execute($sql); + if ($status != 0) { + // update failed + $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(); + } + } + + /** + * 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 + */ + public function deleteRow($table, $key, $schema = false) + { + if (!is_array($key)) { + return -1; + } else { + // 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; + } + + if ($schema === false) { + $schema = $this->_schema; + } + + $status = $this->delete($table, $key, $schema); + if ($status != 0 || $this->conn->Affected_Rows() != 1) { + $this->rollbackTransaction(); + return -2; + } + + // End transaction + return $this->endTransaction(); + } + } + + // Sequence functions + + /** + * Returns properties of a single sequence + * @param $sequence Sequence name + * @return A recordset + */ + public function getSequence($sequence) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $c_sequence = $sequence; + $this->fieldClean($sequence); + $this->clean($c_sequence); + + $sql = " SELECT c.relname AS seqname, s.*, pg_catalog.obj_description(s.tableoid, 'pg_class') AS seqcomment, u.usename AS seqowner, n.nspname @@ -2825,434 +2907,443 @@ class Postgres extends ADODB_base { AND c.relname = '{$c_sequence}' AND c.relkind = 'S' AND n.nspname='{$c_schema}' AND n.oid = c.relnamespace"; - return $this->selectSet($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 + return $this->selectSet($sql); + } + + /** + * Returns all sequences in the current database + * @return A recordset + */ + public 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 { - $c_schema = $this->_schema; - $this->clean($c_schema); - $sql = "SELECT c.relname AS seqname, u.usename AS seqowner, pg_catalog.obj_description(c.oid, 'pg_class') AS seqcomment, + } else { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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='{$c_schema}' ORDER BY seqname"; - } - - return $this->selectSet($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 */ - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->clean($f_schema); - $this->fieldClean($sequence); - $this->clean($sequence); - - $sql = "SELECT pg_catalog.NEXTVAL('\"{$f_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 */ - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->clean($f_schema); - $this->fieldClean($sequence); - $this->clean($sequence); - $this->clean($nextvalue); - - $sql = "SELECT pg_catalog.SETVAL('\"{$f_schema}\".\"{$sequence}\"', '{$nextvalue}')"; - - return $this->execute($sql); - } - - /** - * Restart a given sequence to its start value - * @param $sequence Sequence name - * @return 0 success - * @return -1 sequence not found - */ - function restartSequence($sequence) { - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($sequence); - - $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$sequence}\" RESTART;"; - - return $this->execute($sql); - } - - /** - * 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']; - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - /* This double-cleaning is deliberate */ - $this->fieldClean($sequence); - $this->clean($sequence); - - $sql = "SELECT pg_catalog.SETVAL('\"{$f_schema}\".\"{$sequence}\"', {$minvalue})"; - - return $this->execute($sql); - } - - /** - * Creates a new sequence - * @param $sequence Sequence name - * @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 - */ - function createSequence($sequence, $increment, $minvalue, $maxvalue, - $startvalue, $cachevalue, $cycledvalue) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($sequence); - $this->clean($increment); - $this->clean($minvalue); - $this->clean($maxvalue); - $this->clean($startvalue); - $this->clean($cachevalue); - - $sql = "CREATE SEQUENCE \"{$f_schema}\".\"{$sequence}\""; - if ($increment != '') { - $sql .= " INCREMENT {$increment}"; - } - - if ($minvalue != '') { - $sql .= " MINVALUE {$minvalue}"; - } - - if ($maxvalue != '') { - $sql .= " MAXVALUE {$maxvalue}"; - } - - if ($startvalue != '') { - $sql .= " START {$startvalue}"; - } - - if ($cachevalue != '') { - $sql .= " CACHE {$cachevalue}"; - } - - if ($cycledvalue) { - $sql .= " CYCLE"; - } - - return $this->execute($sql); - } - - /** - * Rename a sequence - * @param $seqrs The sequence RecordSet returned by getSequence() - * @param $name The new name for the sequence - * @return 0 success - */ - function alterSequenceName($seqrs, $name) { - /* vars are cleaned in _alterSequence */ - if (!empty($name) && ($seqrs->fields['seqname'] != $name)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $sql = "ALTER SEQUENCE \"{$f_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. - /* vars are cleaned in _alterSequence */ - if (!empty($owner) && ($seqrs->fields['seqowner'] != $owner)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$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) { - /* vars are cleaned in _alterSequence */ - if (!empty($schema) && ($seqrs->fields['nspname'] != $schema)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" SET SCHEMA {$schema}"; - return $this->execute($sql); - } - return 0; - } - - /** - * 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 $restartvalue The sequence current value - * @param $cachevalue The sequence cache value - * @param $cycledvalue Sequence can cycle ? - * @param $startvalue The sequence start value when issueing a restart - * @return 0 success - */ - function alterSequenceProps($seqrs, $increment, $minvalue, $maxvalue, - $restartvalue, $cachevalue, $cycledvalue, $startvalue) { - - $sql = ''; - /* vars are cleaned in _alterSequence */ - 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($restartvalue) && ($restartvalue != $seqrs->fields['last_value'])) { - $sql .= " RESTART {$restartvalue}"; - } - - if (!empty($cachevalue) && ($cachevalue != $seqrs->fields['cache_value'])) { - $sql .= " CACHE {$cachevalue}"; - } - - if (!empty($startvalue) && ($startvalue != $seqrs->fields['start_value'])) { - $sql .= " START {$startvalue}"; - } - - // toggle cycle yes/no - if (!is_null($cycledvalue)) { - $sql .= (!$cycledvalue ? ' NO ' : '') . " CYCLE"; - } - - if ($sql != '') { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" {$sql}"; - return $this->execute($sql); - } - return 0; - } - - /** - * 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 $restartvalue The starting value - * @param $cachevalue The cache value - * @param $cycledvalue True if cycled, false otherwise - * @param $startvalue The sequence start value when issueing a restart - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 get sequence props error - * @return -7 schema error - */ - protected - function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $restartvalue, $cachevalue, $cycledvalue, $startvalue) { - - $this->fieldArrayClean($seqrs->fields); - - // Comment - $status = $this->setComment('SEQUENCE', $seqrs->fields['seqname'], '', $comment); - if ($status != 0) { - return -4; - } - - // 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($restartvalue); - $this->clean($cachevalue); - $this->clean($cycledvalue); - $this->clean($startvalue); - $status = $this->alterSequenceProps($seqrs, $increment, $minvalue, - $maxvalue, $restartvalue, $cachevalue, $cycledvalue, $startvalue); - 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; - } - - /** - * Alters a sequence - * @param $sequence The name of the sequence - * @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 $restartvalue The starting value - * @param $cachevalue The cache value - * @param $cycledvalue True if cycled, false otherwise - * @param $startvalue The sequence start value when issueing a restart - * @return 0 success - * @return -1 transaction error - * @return -2 get existing sequence error - * @return $this->_alterSequence error code - */ - function alterSequence($sequence, $name, $comment, $owner = null, $schema = null, $increment = null, - $minvalue = null, $maxvalue = null, $restartvalue = null, $cachevalue = null, $cycledvalue = null, $startvalue = null) { - - $this->fieldClean($sequence); - - $data = $this->getSequence($sequence); - - if ($data->recordCount() != 1) { - return -2; - } - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $status = $this->_alterSequence($data, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $restartvalue, $cachevalue, $cycledvalue, $startvalue); - - if ($status != 0) { - $this->rollbackTransaction(); - return $status; - } - - return $this->endTransaction(); - } - - /** - * Drops a given sequence - * @param $sequence Sequence name - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropSequence($sequence, $cascade) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($sequence); - - $sql = "DROP SEQUENCE \"{$f_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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($view); - - $sql = " + } + + return $this->selectSet($sql); + } + + /** + * Execute nextval on a given sequence + * @param $sequence Sequence name + * @return 0 success + * @return -1 sequence not found + */ + public function nextvalSequence($sequence) + { + /* This double-cleaning is deliberate */ + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->clean($f_schema); + $this->fieldClean($sequence); + $this->clean($sequence); + + $sql = "SELECT pg_catalog.NEXTVAL('\"{$f_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 + */ + public function setvalSequence($sequence, $nextvalue) + { + /* This double-cleaning is deliberate */ + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->clean($f_schema); + $this->fieldClean($sequence); + $this->clean($sequence); + $this->clean($nextvalue); + + $sql = "SELECT pg_catalog.SETVAL('\"{$f_schema}\".\"{$sequence}\"', '{$nextvalue}')"; + + return $this->execute($sql); + } + + /** + * Restart a given sequence to its start value + * @param $sequence Sequence name + * @return 0 success + * @return -1 sequence not found + */ + public function restartSequence($sequence) + { + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($sequence); + + $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$sequence}\" RESTART;"; + + return $this->execute($sql); + } + + /** + * Resets a given sequence to min value of sequence + * @param $sequence Sequence name + * @return 0 success + * @return -1 sequence not found + */ + public 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']; + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + /* This double-cleaning is deliberate */ + $this->fieldClean($sequence); + $this->clean($sequence); + + $sql = "SELECT pg_catalog.SETVAL('\"{$f_schema}\".\"{$sequence}\"', {$minvalue})"; + + return $this->execute($sql); + } + + /** + * Creates a new sequence + * @param $sequence Sequence name + * @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 + */ + public function createSequence($sequence, $increment, $minvalue, $maxvalue, + $startvalue, $cachevalue, $cycledvalue) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($sequence); + $this->clean($increment); + $this->clean($minvalue); + $this->clean($maxvalue); + $this->clean($startvalue); + $this->clean($cachevalue); + + $sql = "CREATE SEQUENCE \"{$f_schema}\".\"{$sequence}\""; + if ($increment != '') { + $sql .= " INCREMENT {$increment}"; + } + + if ($minvalue != '') { + $sql .= " MINVALUE {$minvalue}"; + } + + if ($maxvalue != '') { + $sql .= " MAXVALUE {$maxvalue}"; + } + + if ($startvalue != '') { + $sql .= " START {$startvalue}"; + } + + if ($cachevalue != '') { + $sql .= " CACHE {$cachevalue}"; + } + + if ($cycledvalue) { + $sql .= " CYCLE"; + } + + return $this->execute($sql); + } + + /** + * Rename a sequence + * @param $seqrs The sequence RecordSet returned by getSequence() + * @param $name The new name for the sequence + * @return 0 success + */ + public function alterSequenceName($seqrs, $name) + { + /* vars are cleaned in _alterSequence */ + if (!empty($name) && ($seqrs->fields['seqname'] != $name)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $sql = "ALTER SEQUENCE \"{$f_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 + */ + public 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. + /* vars are cleaned in _alterSequence */ + if (!empty($owner) && ($seqrs->fields['seqowner'] != $owner)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$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 + */ + public function alterSequenceSchema($seqrs, $schema) + { + /* vars are cleaned in _alterSequence */ + if (!empty($schema) && ($seqrs->fields['nspname'] != $schema)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" SET SCHEMA {$schema}"; + return $this->execute($sql); + } + return 0; + } + + /** + * 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 $restartvalue The sequence current value + * @param $cachevalue The sequence cache value + * @param $cycledvalue Sequence can cycle ? + * @param $startvalue The sequence start value when issueing a restart + * @return 0 success + */ + public function alterSequenceProps($seqrs, $increment, $minvalue, $maxvalue, + $restartvalue, $cachevalue, $cycledvalue, $startvalue) { + + $sql = ''; + /* vars are cleaned in _alterSequence */ + 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($restartvalue) && ($restartvalue != $seqrs->fields['last_value'])) { + $sql .= " RESTART {$restartvalue}"; + } + + if (!empty($cachevalue) && ($cachevalue != $seqrs->fields['cache_value'])) { + $sql .= " CACHE {$cachevalue}"; + } + + if (!empty($startvalue) && ($startvalue != $seqrs->fields['start_value'])) { + $sql .= " START {$startvalue}"; + } + + // toggle cycle yes/no + if (!is_null($cycledvalue)) { + $sql .= (!$cycledvalue ? ' NO ' : '') . " CYCLE"; + } + + if ($sql != '') { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" {$sql}"; + return $this->execute($sql); + } + return 0; + } + + /** + * 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 $restartvalue The starting value + * @param $cachevalue The cache value + * @param $cycledvalue True if cycled, false otherwise + * @param $startvalue The sequence start value when issueing a restart + * @return 0 success + * @return -3 rename error + * @return -4 comment error + * @return -5 owner error + * @return -6 get sequence props error + * @return -7 schema error + */ + protected function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, + $minvalue, $maxvalue, $restartvalue, $cachevalue, $cycledvalue, $startvalue) { + + $this->fieldArrayClean($seqrs->fields); + + // Comment + $status = $this->setComment('SEQUENCE', $seqrs->fields['seqname'], '', $comment); + if ($status != 0) { + return -4; + } + + // 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($restartvalue); + $this->clean($cachevalue); + $this->clean($cycledvalue); + $this->clean($startvalue); + $status = $this->alterSequenceProps($seqrs, $increment, $minvalue, + $maxvalue, $restartvalue, $cachevalue, $cycledvalue, $startvalue); + 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; + } + + /** + * Alters a sequence + * @param $sequence The name of the sequence + * @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 $restartvalue The starting value + * @param $cachevalue The cache value + * @param $cycledvalue True if cycled, false otherwise + * @param $startvalue The sequence start value when issueing a restart + * @return 0 success + * @return -1 transaction error + * @return -2 get existing sequence error + * @return $this->_alterSequence error code + */ + public function alterSequence($sequence, $name, $comment, $owner = null, $schema = null, $increment = null, + $minvalue = null, $maxvalue = null, $restartvalue = null, $cachevalue = null, $cycledvalue = null, $startvalue = null) { + + $this->fieldClean($sequence); + + $data = $this->getSequence($sequence); + + if ($data->recordCount() != 1) { + return -2; + } + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $status = $this->_alterSequence($data, $name, $comment, $owner, $schema, $increment, + $minvalue, $maxvalue, $restartvalue, $cachevalue, $cycledvalue, $startvalue); + + if ($status != 0) { + $this->rollbackTransaction(); + return $status; + } + + return $this->endTransaction(); + } + + /** + * Drops a given sequence + * @param $sequence Sequence name + * @param $cascade True to cascade drop, false to restrict + * @return 0 success + */ + public function dropSequence($sequence, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($sequence); + + $sql = "DROP SEQUENCE \"{$f_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 + */ + public function getView($view) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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 @@ -3260,17 +3351,18 @@ class Postgres extends ADODB_base { LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) WHERE (c.relname = '{$view}') AND n.nspname='{$c_schema}'"; - return $this->selectSet($sql); - } - - /** - * Returns a list of all views in the database - * @return All views - */ - function getViews() { - $c_schema = $this->_schema; - $this->clean($c_schema); - $sql = " + return $this->selectSet($sql); + } + + /** + * Returns a list of all views in the database + * @return All views + */ + public function getViews() + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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 @@ -3278,17 +3370,18 @@ class Postgres extends ADODB_base { WHERE (n.nspname='{$c_schema}') AND (c.relkind = 'v'::\"char\") ORDER BY relname"; - return $this->selectSet($sql); - } - - /** - * Returns a list of all views in the database - * @return All views - */ - function getMaterializedViews() { - $c_schema = $this->_schema; - $this->clean($c_schema); - $sql = " + return $this->selectSet($sql); + } + + /** + * Returns a list of all views in the database + * @return All views + */ + public function getMaterializedViews() + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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 @@ -3296,268 +3389,278 @@ class Postgres extends ADODB_base { WHERE (n.nspname='{$c_schema}') AND (c.relkind = 'm'::\"char\") ORDER BY relname"; - 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); - } - - /** - * 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 -1; - } - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($viewname); - - // Note: $definition not cleaned - - $sql = "CREATE "; - if ($replace) { - $sql .= "OR REPLACE "; - } - - $sql .= "VIEW \"{$f_schema}\".\"{$viewname}\" AS {$definition}"; - - $status = $this->execute($sql); - if ($status) { - $this->rollbackTransaction(); - return -1; - } - - if ($comment != '') { - $status = $this->setComment('VIEW', $viewname, '', $comment); - if ($status) { - $this->rollbackTransaction(); - return -1; - } - } - - return $this->endTransaction(); - } - - /** - * Rename a view - * @param $vwrs The view recordSet returned by getView() - * @param $name The new view's name - * @return 0 success - */ - function alterViewName($vwrs, $name) { - // Rename (only if name has changed) - /* $vwrs and $name are cleaned in _alterView */ - if (!empty($name) && ($name != $vwrs->fields['relname'])) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $sql = "ALTER VIEW \"{$f_schema}\".\"{$vwrs->fields['relname']}\" RENAME TO \"{$name}\""; - $status = $this->execute($sql); - if ($status == 0) { - $vwrs->fields['relname'] = $name; - } else { - return $status; - } - - } - return 0; - } - - /** - * Alter a view's owner - * @param $vwrs The view recordSet returned by getView() - * @param $name The new view's owner - * @return 0 success - */ - function alterViewOwner($vwrs, $owner = null) { - /* $vwrs and $owner are cleaned in _alterView */ - if ((!empty($owner)) && ($vwrs->fields['relowner'] != $owner)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - // 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 \"{$f_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) { - /* $vwrs and $schema are cleaned in _alterView */ - if (!empty($schema) && ($vwrs->fields['nspname'] != $schema)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - // If tablespace has been changed, then do the alteration. We - // don't want to do this unnecessarily. - $sql = "ALTER TABLE \"{$f_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 - 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; - } - - // Schema - $this->fieldClean($schema); - $status = $this->alterViewSchema($vwrs, $schema); - if ($status != 0) { - return -6; - } - - return 0; - } - - /** - * 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 transaction error - * @return -2 get existing view error - * @return $this->_alterView error code - */ - function alterView($view, $name, $owner, $schema, $comment) { - - $data = $this->getView($view); - if ($data->recordCount() != 1) { - return -2; - } - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $status = $this->_alterView($data, $name, $owner, $schema, $comment); - - if ($status != 0) { - $this->rollbackTransaction(); - return $status; - } - - return $this->endTransaction(); - } - - /** - * 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 dropView($viewname, $cascade) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($viewname); - - $sql = "DROP VIEW \"{$f_schema}\".\"{$viewname}\""; - if ($cascade) { - $sql .= " CASCADE"; - } - - return $this->execute($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 = " + 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 + */ + public function setView($viewname, $definition, $comment) + { + return $this->createView($viewname, $definition, true, $comment); + } + + /** + * 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 + */ + public function createView($viewname, $definition, $replace, $comment) + { + $status = $this->beginTransaction(); + if ($status != 0) { + return -1; + } + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($viewname); + + // Note: $definition not cleaned + + $sql = "CREATE "; + if ($replace) { + $sql .= "OR REPLACE "; + } + + $sql .= "VIEW \"{$f_schema}\".\"{$viewname}\" AS {$definition}"; + + $status = $this->execute($sql); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + + if ($comment != '') { + $status = $this->setComment('VIEW', $viewname, '', $comment); + if ($status) { + $this->rollbackTransaction(); + return -1; + } + } + + return $this->endTransaction(); + } + + /** + * Rename a view + * @param $vwrs The view recordSet returned by getView() + * @param $name The new view's name + * @return 0 success + */ + public function alterViewName($vwrs, $name) + { + // Rename (only if name has changed) + /* $vwrs and $name are cleaned in _alterView */ + if (!empty($name) && ($name != $vwrs->fields['relname'])) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $sql = "ALTER VIEW \"{$f_schema}\".\"{$vwrs->fields['relname']}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); + if ($status == 0) { + $vwrs->fields['relname'] = $name; + } else { + return $status; + } + + } + return 0; + } + + /** + * Alter a view's owner + * @param $vwrs The view recordSet returned by getView() + * @param $name The new view's owner + * @return 0 success + */ + public function alterViewOwner($vwrs, $owner = null) + { + /* $vwrs and $owner are cleaned in _alterView */ + if ((!empty($owner)) && ($vwrs->fields['relowner'] != $owner)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + // 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 \"{$f_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 + */ + public function alterViewSchema($vwrs, $schema) + { + /* $vwrs and $schema are cleaned in _alterView */ + if (!empty($schema) && ($vwrs->fields['nspname'] != $schema)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + // If tablespace has been changed, then do the alteration. We + // don't want to do this unnecessarily. + $sql = "ALTER TABLE \"{$f_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 + 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; + } + + // Schema + $this->fieldClean($schema); + $status = $this->alterViewSchema($vwrs, $schema); + if ($status != 0) { + return -6; + } + + return 0; + } + + /** + * 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 transaction error + * @return -2 get existing view error + * @return $this->_alterView error code + */ + public function alterView($view, $name, $owner, $schema, $comment) + { + + $data = $this->getView($view); + if ($data->recordCount() != 1) { + return -2; + } + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $status = $this->_alterView($data, $name, $owner, $schema, $comment); + + if ($status != 0) { + $this->rollbackTransaction(); + return $status; + } + + return $this->endTransaction(); + } + + /** + * Drops a view. + * @param $viewname The name of the view to drop + * @param $cascade True to cascade drop, false to restrict + * @return 0 success + */ + public function dropView($viewname, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($viewname); + + $sql = "DROP VIEW \"{$f_schema}\".\"{$viewname}\""; + if ($cascade) { + $sql .= " CASCADE"; + } + + return $this->execute($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 + */ + public 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 "; - } + if ($unique) { + $sql .= " AND i.indisunique "; + } - $sql .= " ORDER BY c2.relname"; + $sql .= " ORDER BY c2.relname"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - /** - * test if a table has been clustered on an index - * @param $table The table to test - * - * @return true if the table has been already clustered - */ - function alreadyClustered($table) { + /** + * test if a table has been clustered on an index + * @param $table The table to test + * + * @return true if the table has been already clustered + */ + public function alreadyClustered($table) + { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); - $sql = "SELECT i.indisclustered + $sql = "SELECT i.indisclustered FROM pg_catalog.pg_class c, pg_catalog.pg_index i WHERE c.relname = '{$table}' AND c.oid = i.indrelid AND i.indisclustered @@ -3565,161 +3668,166 @@ class Postgres extends ADODB_base { WHERE nspname='{$c_schema}') "; - $v = $this->selectSet($sql); - - if ($v->recordCount() == 0) { - return false; - } - - return true; - } - - /** - * Creates an index - * @param $name The index name - * @param $table The table on which to add the index - * @param $columns An array of columns that form the index - * or a string expression for a functional index - * @param $type The index type - * @param $unique True if unique, false otherwise - * @param $where Index predicate ('' for none) - * @param $tablespace The tablespaces ('' means none/default) - * @return 0 success - */ - function createIndex($name, $table, $columns, $type, $unique, $where, $tablespace, $concurrently) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($name); - $this->fieldClean($table); - - $sql = "CREATE"; - if ($unique) { - $sql .= " UNIQUE"; - } - - $sql .= " INDEX"; - if ($concurrently) { - $sql .= " CONCURRENTLY"; - } - - $sql .= " \"{$name}\" ON \"{$f_schema}\".\"{$table}\" USING {$type} "; - - if (is_array($columns)) { - $this->arrayClean($columns); - $sql .= "(\"" . implode('","', $columns) . "\")"; - } else { - $sql .= "(" . $columns . ")"; - } - - // Tablespace - if ($this->hasTablespaces() && $tablespace != '') { - $this->fieldClean($tablespace); - $sql .= " TABLESPACE \"{$tablespace}\""; - } - - // Predicate - if (trim($where) != '') { - $sql .= " WHERE ({$where})"; - } - - return $this->execute($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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($index); - - $sql = "DROP INDEX \"{$f_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($name); - switch ($type) { - case 'DATABASE': - $sql = "REINDEX {$type} \"{$name}\""; - if ($force) { - $sql .= ' FORCE'; - } - - break; - case 'TABLE': - case 'INDEX': - $sql = "REINDEX {$type} \"{$f_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($table = '', $index = '') { - - $sql = 'CLUSTER'; - - // 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 - - if (!empty($table)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $sql .= " \"{$f_schema}\".\"{$table}\""; - - if (!empty($index)) { - $this->fieldClean($index); - $sql .= " USING \"{$index}\""; - } - } - - return $this->execute($sql); - } - - // 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - // This SQL is greatly complicated by the need to retrieve - // index clustering information for primary and unique constraints - $sql = "SELECT + $v = $this->selectSet($sql); + + if ($v->recordCount() == 0) { + return false; + } + + return true; + } + + /** + * Creates an index + * @param $name The index name + * @param $table The table on which to add the index + * @param $columns An array of columns that form the index + * or a string expression for a functional index + * @param $type The index type + * @param $unique True if unique, false otherwise + * @param $where Index predicate ('' for none) + * @param $tablespace The tablespaces ('' means none/default) + * @return 0 success + */ + public function createIndex($name, $table, $columns, $type, $unique, $where, $tablespace, $concurrently) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($name); + $this->fieldClean($table); + + $sql = "CREATE"; + if ($unique) { + $sql .= " UNIQUE"; + } + + $sql .= " INDEX"; + if ($concurrently) { + $sql .= " CONCURRENTLY"; + } + + $sql .= " \"{$name}\" ON \"{$f_schema}\".\"{$table}\" USING {$type} "; + + if (is_array($columns)) { + $this->arrayClean($columns); + $sql .= "(\"" . implode('","', $columns) . "\")"; + } else { + $sql .= "(" . $columns . ")"; + } + + // Tablespace + if ($this->hasTablespaces() && $tablespace != '') { + $this->fieldClean($tablespace); + $sql .= " TABLESPACE \"{$tablespace}\""; + } + + // Predicate + if (trim($where) != '') { + $sql .= " WHERE ({$where})"; + } + + return $this->execute($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 + */ + public function dropIndex($index, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($index); + + $sql = "DROP INDEX \"{$f_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 + */ + public function reindex($type, $name, $force = false) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($name); + switch ($type) { + case 'DATABASE': + $sql = "REINDEX {$type} \"{$name}\""; + if ($force) { + $sql .= ' FORCE'; + } + + break; + case 'TABLE': + case 'INDEX': + $sql = "REINDEX {$type} \"{$f_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 + */ + public function clusterIndex($table = '', $index = '') + { + + $sql = 'CLUSTER'; + + // 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 + + if (!empty($table)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $sql .= " \"{$f_schema}\".\"{$table}\""; + + if (!empty($index)) { + $this->fieldClean($index); + $sql .= " USING \"{$index}\""; + } + } + + return $this->execute($sql); + } + + // Constraint functions + + /** + * Returns a list of all constraints on a table + * @param $table The table to find rules for + * @return A recordset + */ + public function getConstraints($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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, @@ -3748,24 +3856,25 @@ class Postgres extends ADODB_base { 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) { - - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - // get the max number of col used in a constraint for the table - $sql = "SELECT DISTINCT + 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 + */ + public function getConstraintsWithFields($table) + { + + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->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 \$patern\$^\\[.*:(.*)\\]$\$patern\$)) as nb FROM pg_catalog.pg_constraint AS c JOIN pg_catalog.pg_class AS r ON (c.conrelid=r.oid) @@ -3773,15 +3882,15 @@ class Postgres extends ADODB_base { WHERE r.relname = '{$table}' AND ns.nspname='{$c_schema}'"; - $rs = $this->selectSet($sql); + $rs = $this->selectSet($sql); - if ($rs->EOF) { - $max_col = 0; - } else { - $max_col = $rs->fields['nb']; - } + if ($rs->EOF) { + $max_col = 0; + } else { + $max_col = $rs->fields['nb']; + } - $sql = ' + $sql = ' SELECT c.oid AS conid, 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, @@ -3792,294 +3901,300 @@ class Postgres extends ADODB_base { 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 .= ')) + 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)"; - } + for ($i = 2; $i <= $rs->fields['nb']; $i++) { + $sql .= " OR (c.confkey[$i]=f2.attnum AND c.conkey[$i]=f1.attnum)"; + } - $sql .= sprintf(")) + $sql .= sprintf(")) WHERE r1.relname = '%s' AND ns1.nspname='%s' ORDER BY 1", $table, $c_schema); - return $this->selectSet($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; - } - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldArrayClean($fields); - $this->fieldClean($name); - $this->fieldClean($tablespace); - - $sql = "ALTER TABLE \"{$f_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); - } - - /** - * 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; - } - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldArrayClean($fields); - $this->fieldClean($name); - $this->fieldClean($tablespace); - - $sql = "ALTER TABLE \"{$f_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 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 = '') { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldClean($name); - // @@ How the heck do you clean a definition??? - - $sql = "ALTER TABLE \"{$f_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $c_schema = $this->_schema; - $this->clean($c_schema); - $c_table = $table; - $this->fieldClean($table); - $this->clean($c_table); - $this->clean($name); - - // Begin transaction - $status = $this->beginTransaction(); - if ($status != 0) { - return -2; - } - - // Properly lock the table - $sql = "LOCK TABLE \"{$f_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='{$c_table}' + return $this->selectSet($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 + */ + public function addPrimaryKey($table, $fields, $name = '', $tablespace = '') + { + if (!is_array($fields) || sizeof($fields) == 0) { + return -1; + } + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldArrayClean($fields); + $this->fieldClean($name); + $this->fieldClean($tablespace); + + $sql = "ALTER TABLE \"{$f_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); + } + + /** + * 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 + */ + public function addUniqueKey($table, $fields, $name = '', $tablespace = '') + { + if (!is_array($fields) || sizeof($fields) == 0) { + return -1; + } + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldArrayClean($fields); + $this->fieldClean($name); + $this->fieldClean($tablespace); + + $sql = "ALTER TABLE \"{$f_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 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 + */ + public function addCheckConstraint($table, $definition, $name = '') + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldClean($name); + // @@ How the heck do you clean a definition??? + + $sql = "ALTER TABLE \"{$f_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 + */ + public function dropCheckConstraint($table, $name) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $c_schema = $this->_schema; + $this->clean($c_schema); + $c_table = $table; + $this->fieldClean($table); + $this->clean($c_table); + $this->clean($name); + + // Begin transaction + $status = $this->beginTransaction(); + if ($status != 0) { + return -2; + } + + // Properly lock the table + $sql = "LOCK TABLE \"{$f_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='{$c_table}' AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_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 + $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='{$c_table}' AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))) WHERE relname='{$c_table}'"; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - - // Otherwise, close the transaction - return $this->endTransaction(); - } - - /** - * 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 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; - } - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldClean($targschema); - $this->fieldClean($targtable); - $this->fieldArrayClean($sfields); - $this->fieldArrayClean($tfields); - $this->fieldClean($name); - - $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD "; - if ($name != '') { - $sql .= "CONSTRAINT \"{$name}\" "; - } - - $sql .= "FOREIGN KEY (\"" . join('","', $sfields) . "\") "; - // Target table needs to be fully qualified - $sql .= "REFERENCES \"{$targschema}\".\"{$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); - } - - /** - * 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 - */ - function dropConstraint($constraint, $relation, $type, $cascade) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($constraint); - $this->fieldClean($relation); - - $sql = "ALTER TABLE \"{$f_schema}\".\"{$relation}\" DROP CONSTRAINT \"{$constraint}\""; - if ($cascade) { - $sql .= " CASCADE"; - } - - return $this->execute($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; - } - - $this->clean($tables[0]['tablename']); - $this->clean($tables[0]['schemaname']); - $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++) { - $this->clean($tables[$i]['tablename']); - $this->clean($tables[$i]['schemaname']); - $tables_list .= ", '{$tables[$i]['tablename']}'"; - $schema_list .= ", '{$tables[$i]['schemaname']}'"; - $schema_tables_list .= ", '{$tables[$i]['schemaname']}.{$tables[$i]['tablename']}'"; - } - - $maxDimension = 1; - - $sql = " + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; + } + + // Otherwise, close the transaction + return $this->endTransaction(); + } + + /** + * 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 + */ + public 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; + } + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldClean($targschema); + $this->fieldClean($targtable); + $this->fieldArrayClean($sfields); + $this->fieldArrayClean($tfields); + $this->fieldClean($name); + + $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD "; + if ($name != '') { + $sql .= "CONSTRAINT \"{$name}\" "; + } + + $sql .= "FOREIGN KEY (\"" . join('","', $sfields) . "\") "; + // Target table needs to be fully qualified + $sql .= "REFERENCES \"{$targschema}\".\"{$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); + } + + /** + * 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 + */ + public function dropConstraint($constraint, $relation, $type, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($constraint); + $this->fieldClean($relation); + + $sql = "ALTER TABLE \"{$f_schema}\".\"{$relation}\" DROP CONSTRAINT \"{$constraint}\""; + if ($cascade) { + $sql .= " CASCADE"; + } + + return $this->execute($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 + */ + public function getLinkingKeys($tables) + { + if (!is_array($tables)) { + return -1; + } + + $this->clean($tables[0]['tablename']); + $this->clean($tables[0]['schemaname']); + $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++) { + $this->clean($tables[$i]['tablename']); + $this->clean($tables[$i]['schemaname']); + $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 @@ -4092,23 +4207,23 @@ class Postgres extends ADODB_base { 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 = " + //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, @@ -4136,26 +4251,27 @@ class Postgres extends ADODB_base { AND pgns1.nspname || '.' || pgc1.relname IN ($schema_tables_list) AND pgns2.nspname || '.' || pgc2.relname IN ($schema_tables_list) "; - return $this->selectSet($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); - - $status = $this->beginTransaction(); - if ($status != 0) { - return -1; - } - - $c_schema = $this->_schema; - $this->clean($c_schema); - - $sql = " + return $this->selectSet($sql); + } + + /** + * Finds the foreign keys that refer to the specified table + * @param $table The table to find referrers for + * @return A recordset + */ + public function getReferrers($table) + { + $this->clean($table); + + $status = $this->beginTransaction(); + if ($status != 0) { + return -1; + } + + $c_schema = $this->_schema; + $this->clean($c_schema); + + $sql = " SELECT pn.nspname, pl.relname, @@ -4175,22 +4291,23 @@ class Postgres extends ADODB_base { ORDER BY 1,2,3 "; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Domain functions + // Domain functions - /** - * Gets all information for a single domain - * @param $domain The name of the domain to fetch - * @return A recordset - */ - function getDomain($domain) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($domain); + /** + * Gets all information for a single domain + * @param $domain The name of the domain to fetch + * @return A recordset + */ + public function getDomain($domain) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($domain); - $sql = " + $sql = " SELECT t.typname AS domname, pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype, @@ -4206,18 +4323,19 @@ class Postgres extends ADODB_base { AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}')"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - /** - * Return all domains in current schema. Excludes domain constraints. - * @return All tables, sorted alphabetically - */ - function getDomains() { - $c_schema = $this->_schema; - $this->clean($c_schema); + /** + * Return all domains in current schema. Excludes domain constraints. + * @return All tables, sorted alphabetically + */ + public function getDomains() + { + $c_schema = $this->_schema; + $this->clean($c_schema); - $sql = " + $sql = " SELECT t.typname AS domname, pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype, @@ -4233,20 +4351,21 @@ class Postgres extends ADODB_base { WHERE nspname='{$c_schema}') ORDER BY t.typname"; - return $this->selectSet($sql); - } - - /** - * Get domain constraints - * @param $domain The name of the domain whose constraints to fetch - * @return A recordset - */ - function getDomainConstraints($domain) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($domain); - - $sql = " + return $this->selectSet($sql); + } + + /** + * Get domain constraints + * @param $domain The name of the domain whose constraints to fetch + * @return A recordset + */ + public function getDomainConstraints($domain) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($domain); + + $sql = " SELECT conname, contype, @@ -4263,204 +4382,210 @@ class Postgres extends ADODB_base { ) ORDER BY conname"; - 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($domain); - - $sql = "CREATE DOMAIN \"{$f_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); - } - - /** - * 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 alterDomain($domain, $domdefault, $domnotnull, $domowner) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($domain); - $this->fieldClean($domowner); - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Default - if ($domdefault == '') { - $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP DEFAULT"; - } else { - $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" SET DEFAULT {$domdefault}"; - } - - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } - - // NOT NULL - if ($domnotnull) { - $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" SET NOT NULL"; - } else { - $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP NOT NULL"; - } - - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - - // Owner - $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" OWNER TO \"{$domowner}\""; - - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - - return $this->endTransaction(); - } - - /** - * 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($domain); - - $sql = "DROP DOMAIN \"{$f_schema}\".\"{$domain}\""; - if ($cascade) { - $sql .= " CASCADE"; - } - - return $this->execute($sql); - } - - /** - * 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 = '') { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($domain); - $this->fieldClean($name); - - $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" ADD "; - if ($name != '') { - $sql .= "CONSTRAINT \"{$name}\" "; - } - - $sql .= "CHECK ({$definition})"; - - return $this->execute($sql); - } - - /** - * 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 dropDomainConstraint($domain, $constraint, $cascade) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($domain); - $this->fieldClean($constraint); - - $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP CONSTRAINT \"{$constraint}\""; - if ($cascade) { - $sql .= " CASCADE"; - } - - 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 = " + 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 + */ + public function createDomain($domain, $type, $length, $array, $notnull, $default, $check) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($domain); + + $sql = "CREATE DOMAIN \"{$f_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); + } + + /** + * 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 + */ + public function alterDomain($domain, $domdefault, $domnotnull, $domowner) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($domain); + $this->fieldClean($domowner); + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + // Default + if ($domdefault == '') { + $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP DEFAULT"; + } else { + $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" SET DEFAULT {$domdefault}"; + } + + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; + } + + // NOT NULL + if ($domnotnull) { + $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" SET NOT NULL"; + } else { + $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP NOT NULL"; + } + + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } + + // Owner + $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" OWNER TO \"{$domowner}\""; + + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; + } + + return $this->endTransaction(); + } + + /** + * Drops a domain. + * @param $domain The name of the domain to drop + * @param $cascade True to cascade drop, false to restrict + * @return 0 success + */ + public function dropDomain($domain, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($domain); + + $sql = "DROP DOMAIN \"{$f_schema}\".\"{$domain}\""; + if ($cascade) { + $sql .= " CASCADE"; + } + + return $this->execute($sql); + } + + /** + * 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 + */ + public function addDomainCheckConstraint($domain, $definition, $name = '') + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($domain); + $this->fieldClean($name); + + $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" ADD "; + if ($name != '') { + $sql .= "CONSTRAINT \"{$name}\" "; + } + + $sql .= "CHECK ({$definition})"; + + return $this->execute($sql); + } + + /** + * 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 + */ + public function dropDomainConstraint($domain, $constraint, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($domain); + $this->fieldClean($constraint); + + $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP CONSTRAINT \"{$constraint}\""; + if ($cascade) { + $sql .= " CASCADE"; + } + + 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 + */ + public function getFunction($function_oid) + { + $this->clean($function_oid); + + $sql = " SELECT pc.oid AS prooid, proname, pg_catalog.pg_get_userbyid(proowner) AS proowner, @@ -4483,32 +4608,33 @@ class Postgres extends ADODB_base { AND pc.pronamespace = pn.oid "; - 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 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 { - $c_schema = $this->_schema; - $this->clean($c_schema); - $where = "n.nspname = '{$c_schema}'"; - $distinct = ''; - } - - $sql = " + 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 those in search path + * @param $type If not null, will find all functions with return value = type + * + * @return All functions + */ + public 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 { + $c_schema = $this->_schema; + $this->clean($c_schema); + $where = "n.nspname = '{$c_schema}'"; + $distinct = ''; + } + + $sql = " SELECT {$distinct} p.oid AS prooid, @@ -4530,300 +4656,306 @@ class Postgres extends ADODB_base { ORDER BY p.proname, proresult "; - 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 = []; - - // 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; - } - - /** - * 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, $comment, true); - if ($status != 0) { - $this->rollbackTransaction(); - return $status; - } - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - - // Rename the function, if necessary - $this->fieldClean($newname); - /* $funcname is escaped in createFunction */ - if ($funcname != $newname) { - $sql = "ALTER FUNCTION \"{$f_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 \"{$f_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); - /* $funcschema is escaped in createFunction */ - if ($funcschema != $newschema) { - $sql = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) SET SCHEMA \"{$newschema}\""; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -7; - } - } - } - - 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 $comment Comment for the function - * @param $replace (optional) True if OR REPLACE, false for normal - * @return 0 success - * @return -3 create function failed - * @return -4 set comment failed - */ - function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, $replace = false) { - - // Begin a transaction - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $this->fieldClean($funcname); - $this->clean($args); - $this->fieldClean($language); - $this->arrayClean($flags); - $this->clean($cost); - $this->clean($rows); - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - - $sql = "CREATE"; - if ($replace) { - $sql .= " OR REPLACE"; - } - - $sql .= " FUNCTION \"{$f_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}"; - } - - } - - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - - /* set the comment */ - $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - - return $this->endTransaction(); - } - - /** - * 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 dropFunction($function_oid, $cascade) { - // Function comes in with $object as function OID - $fn = $this->getFunction($function_oid); - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($fn->fields['proname']); - - $sql = "DROP FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})"; - if ($cascade) { - $sql .= " CASCADE"; - } - - return $this->execute($sql); - } - - // 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 + 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 + */ + public function getFunctionProperties($f) + { + $temp = []; + + // 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; + } + + /** + * 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 + */ + public 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, $comment, true); + if ($status != 0) { + $this->rollbackTransaction(); + return $status; + } + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + + // Rename the function, if necessary + $this->fieldClean($newname); + /* $funcname is escaped in createFunction */ + if ($funcname != $newname) { + $sql = "ALTER FUNCTION \"{$f_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 \"{$f_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); + /* $funcschema is escaped in createFunction */ + if ($funcschema != $newschema) { + $sql = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) SET SCHEMA \"{$newschema}\""; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -7; + } + } + } + + 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 $comment Comment for the function + * @param $replace (optional) True if OR REPLACE, false for normal + * @return 0 success + * @return -3 create function failed + * @return -4 set comment failed + */ + public function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, $replace = false) + { + + // Begin a transaction + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $this->fieldClean($funcname); + $this->clean($args); + $this->fieldClean($language); + $this->arrayClean($flags); + $this->clean($cost); + $this->clean($rows); + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + + $sql = "CREATE"; + if ($replace) { + $sql .= " OR REPLACE"; + } + + $sql .= " FUNCTION \"{$f_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}"; + } + + } + + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } + + /* set the comment */ + $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; + } + + return $this->endTransaction(); + } + + /** + * 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 + */ + public function dropFunction($function_oid, $cascade) + { + // Function comes in with $object as function OID + $fn = $this->getFunction($function_oid); + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($fn->fields['proname']); + + $sql = "DROP FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})"; + if ($cascade) { + $sql .= " CASCADE"; + } + + return $this->execute($sql); + } + + // Type functions + + /** + * Returns all details for a particular type + * @param $typname The name of the view to retrieve + * @return Type info + */ + public 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 types, 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 { - $c_schema = $this->_schema; - $this->clean($c_schema); - $where = "n.nspname = '{$c_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 + return $this->selectSet($sql); + } + + /** + * Returns a list of all types in the database + * @param $all If true, will find all available types, 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 + */ + public function getTypes($all = false, $tabletypes = false, $domains = false) + { + if ($all) { + $where = '1 = 1'; + } else { + $c_schema = $this->_schema; + $this->clean($c_schema); + $where = "n.nspname = '{$c_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, @@ -4838,276 +4970,281 @@ class Postgres extends ADODB_base { ORDER BY 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($typname); - $this->fieldClean($typin); - $this->fieldClean($typout); - - $sql = " + return $this->selectSet($sql); + } + + /** + * Creates a new type + * @param ... + * @return 0 success + */ + public function createType($typname, $typin, $typout, $typlen, $typdef, + $typelem, $typdelim, $typbyval, $typalign, $typstorage) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($typname); + $this->fieldClean($typin); + $this->fieldClean($typout); + + $sql = " CREATE TYPE \"{$f_schema}\".\"{$typname}\" ( INPUT = \"{$typin}\", OUTPUT = \"{$typout}\", INTERNALLENGTH = {$typlen}"; - if ($typdef != '') { - $sql .= ", DEFAULT = {$typdef}"; - } - - if ($typelem != '') { - $sql .= ", ELEMENT = {$typelem}"; - } - - if ($typdelim != '') { - $sql .= ", DELIMITER = {$typdelim}"; - } - - if ($typbyval) { - $sql .= ", PASSEDBYVALUE, "; - } - - if ($typalign != '') { - $sql .= ", ALIGNMENT = {$typalign}"; - } - - if ($typstorage != '') { - $sql .= ", STORAGE = {$typstorage}"; - } - - $sql .= ")"; - - return $this->execute($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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($typname); - - $sql = "DROP TYPE \"{$f_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($name); - - 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 \"{$f_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->clean($name); - - $sql = "SELECT enumlabel AS enumval + if ($typdef != '') { + $sql .= ", DEFAULT = {$typdef}"; + } + + if ($typelem != '') { + $sql .= ", ELEMENT = {$typelem}"; + } + + if ($typdelim != '') { + $sql .= ", DELIMITER = {$typdelim}"; + } + + if ($typbyval) { + $sql .= ", PASSEDBYVALUE, "; + } + + if ($typalign != '') { + $sql .= ", ALIGNMENT = {$typalign}"; + } + + if ($typstorage != '') { + $sql .= ", STORAGE = {$typstorage}"; + } + + $sql .= ")"; + + return $this->execute($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 + */ + public function dropType($typname, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($typname); + + $sql = "DROP TYPE \"{$f_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 + */ + public function createEnumType($name, $values, $typcomment) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($name); + + 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 \"{$f_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 + */ + public function getEnumValues($name) + { + $this->clean($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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($name); - - $status = $this->beginTransaction(); - if ($status != 0) { - return -1; - } - - $found = false; - $first = true; - $comment_sql = ''; // Accumulate comments for the columns - $sql = "CREATE TYPE \"{$f_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 \"{$f_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() { - $conf = $this->conf; - - if ($conf['show_system']) { - $where = ''; - } else { - $where = ' + 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 + */ + public function createCompositeType($name, $fields, $field, $type, $array, $length, $colcomment, $typcomment) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($name); + + $status = $this->beginTransaction(); + if ($status != 0) { + return -1; + } + + $found = false; + $first = true; + $comment_sql = ''; // Accumulate comments for the columns + $sql = "CREATE TYPE \"{$f_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 \"{$f_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 + */ + public function getCasts() + { + $conf = $this->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 = " + $sql = " SELECT c.castsource::pg_catalog.regtype AS castsource, c.casttarget::pg_catalog.regtype AS casttarget, @@ -5130,17 +5267,18 @@ class Postgres extends ADODB_base { ORDER BY 1, 2 "; - return $this->selectSet($sql); - } - - /** - * Returns a list of all conversions in the database - * @return All conversions - */ - function getConversions() { - $c_schema = $this->_schema; - $this->clean($c_schema); - $sql = " + return $this->selectSet($sql); + } + + /** + * Returns a list of all conversions in the database + * @return All conversions + */ + public function getConversions() + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $sql = " SELECT c.conname, pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding, @@ -5153,22 +5291,23 @@ class Postgres extends ADODB_base { ORDER BY 1; "; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Rule functions + // 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); + /** + * Returns a list of all rules on a table OR view + * @param $table The table to find rules for + * @return A recordset + */ + public function getRules($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); - $sql = " + $sql = " SELECT * FROM pg_catalog.pg_rules WHERE @@ -5176,128 +5315,133 @@ class Postgres extends ADODB_base { 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($name); - $this->fieldClean($table); - if (!in_array($event, $this->rule_events)) { - return -1; - } - - $sql = "CREATE"; - if ($replace) { - $sql .= " OR REPLACE"; - } - - $sql .= " RULE \"{$name}\" AS ON {$event} TO \"{$f_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($rule); - $this->fieldClean($relation); - - $sql = "DROP RULE \"{$rule}\" ON \"{$f_schema}\".\"{$relation}\""; - if ($cascade) { - $sql .= " CASCADE"; - } - - return $this->execute($sql); - } - - // 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - $this->clean($trigger); - - $sql = " + 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 + */ + public 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 + */ + public function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($name); + $this->fieldClean($table); + if (!in_array($event, $this->rule_events)) { + return -1; + } + + $sql = "CREATE"; + if ($replace) { + $sql .= " OR REPLACE"; + } + + $sql .= " RULE \"{$name}\" AS ON {$event} TO \"{$f_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 + */ + public function dropRule($rule, $relation, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($rule); + $this->fieldClean($relation); + + $sql = "DROP RULE \"{$rule}\" ON \"{$f_schema}\".\"{$relation}\""; + if ($cascade) { + $sql .= " CASCADE"; + } + + return $this->execute($sql); + } + + // 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 + */ + public function getTrigger($table, $trigger) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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='{$c_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 = '') { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $sql = "SELECT + 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 + */ + public function getTriggers($table = '') + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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, @@ -5312,253 +5456,261 @@ class Postgres extends ADODB_base { 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. - * Note: Only needed for pre-7.4 servers, this function is deprecated - * @param $trigger An array containing fields from the trigger table - * @return The trigger definition string - */ - function getTriggerDef($trigger) { - - $this->fieldArrayClean($trigger); - // Constants to figure out tgtype - if (!defined('TRIGGER_TYPE_ROW')) { - define('TRIGGER_TYPE_ROW', (1 << 0)); - } - - if (!defined('TRIGGER_TYPE_BEFORE')) { - define('TRIGGER_TYPE_BEFORE', (1 << 1)); - } - - if (!defined('TRIGGER_TYPE_INSERT')) { - define('TRIGGER_TYPE_INSERT', (1 << 2)); - } - - if (!defined('TRIGGER_TYPE_DELETE')) { - define('TRIGGER_TYPE_DELETE', (1 << 3)); - } - - if (!defined('TRIGGER_TYPE_UPDATE')) { - define('TRIGGER_TYPE_UPDATE', (1 << 4)); - } - - $trigger['tgisconstraint'] = $this->phpBool($trigger['tgisconstraint']); - $trigger['tgdeferrable'] = $this->phpBool($trigger['tgdeferrable']); - $trigger['tginitdeferred'] = $this->phpBool($trigger['tginitdeferred']); - - // Constraint trigger or normal trigger - if ($trigger['tgisconstraint']) { - $tgdef = 'CREATE CONSTRAINT TRIGGER '; - } else { - $tgdef = 'CREATE TRIGGER '; - } - - $tgdef .= "\"{$trigger['tgname']}\" "; - - // Trigger type - $findx = 0; - if (($trigger['tgtype'] & TRIGGER_TYPE_BEFORE) == TRIGGER_TYPE_BEFORE) { - $tgdef .= 'BEFORE'; - } else { - $tgdef .= 'AFTER'; - } - - if (($trigger['tgtype'] & TRIGGER_TYPE_INSERT) == TRIGGER_TYPE_INSERT) { - $tgdef .= ' INSERT'; - $findx++; - } - if (($trigger['tgtype'] & TRIGGER_TYPE_DELETE) == TRIGGER_TYPE_DELETE) { - if ($findx > 0) { - $tgdef .= ' OR DELETE'; - } else { - $tgdef .= ' DELETE'; - $findx++; - } - } - if (($trigger['tgtype'] & TRIGGER_TYPE_UPDATE) == TRIGGER_TYPE_UPDATE) { - if ($findx > 0) { - $tgdef .= ' OR UPDATE'; - } else { - $tgdef .= ' UPDATE'; - } - - } - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - // Table name - $tgdef .= " ON \"{$f_schema}\".\"{$trigger['relname']}\" "; - - // Deferrability - if ($trigger['tgisconstraint']) { - if ($trigger['tgconstrrelid'] != 0) { - // Assume constrelname is not null - $tgdef .= " FROM \"{$trigger['tgconstrrelname']}\" "; - } - if (!$trigger['tgdeferrable']) { - $tgdef .= 'NOT '; - } - - $tgdef .= 'DEFERRABLE INITIALLY '; - if ($trigger['tginitdeferred']) { - $tgdef .= 'DEFERRED '; - } else { - $tgdef .= 'IMMEDIATE '; - } - - } - - // Row or statement - if ($trigger['tgtype'] & TRIGGER_TYPE_ROW == TRIGGER_TYPE_ROW) { - $tgdef .= 'FOR EACH ROW '; - } else { - $tgdef .= 'FOR EACH STATEMENT '; - } - - // Execute procedure - $tgdef .= "EXECUTE PROCEDURE \"{$trigger['tgfname']}\"("; - - // Parameters - // Escape null characters - $v = addCSlashes($trigger['tgargs'], "\0"); - // Split on escaped null characters - $params = explode('\\000', $v); - for ($findx = 0; $findx < $trigger['tgnargs']; $findx++) { - $param = "'" . str_replace('\'', '\\\'', $params[$findx]) . "'"; - $tgdef .= $param; - if ($findx < ($trigger['tgnargs'] - 1)) { - $tgdef .= ', '; - } - - } - - // Finish it off - $tgdef .= ')'; - - return $tgdef; - } - - /** - * Returns a list of all functions that can be used in triggers - */ - function getTriggerFunctions() { - return $this->getFunctions(true, 'trigger'); - } - - /** - * 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($tgname); - $this->fieldClean($table); - $this->fieldClean($tgproc); - - /* No Statement Level Triggers in PostgreSQL (by now) */ - $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime} + return $this->selectSet($sql); + } + + /** + * A helper function for getTriggers that translates + * an array of attribute numbers to an array of field names. + * Note: Only needed for pre-7.4 servers, this function is deprecated + * @param $trigger An array containing fields from the trigger table + * @return The trigger definition string + */ + public function getTriggerDef($trigger) + { + + $this->fieldArrayClean($trigger); + // Constants to figure out tgtype + if (!defined('TRIGGER_TYPE_ROW')) { + define('TRIGGER_TYPE_ROW', (1 << 0)); + } + + if (!defined('TRIGGER_TYPE_BEFORE')) { + define('TRIGGER_TYPE_BEFORE', (1 << 1)); + } + + if (!defined('TRIGGER_TYPE_INSERT')) { + define('TRIGGER_TYPE_INSERT', (1 << 2)); + } + + if (!defined('TRIGGER_TYPE_DELETE')) { + define('TRIGGER_TYPE_DELETE', (1 << 3)); + } + + if (!defined('TRIGGER_TYPE_UPDATE')) { + define('TRIGGER_TYPE_UPDATE', (1 << 4)); + } + + $trigger['tgisconstraint'] = $this->phpBool($trigger['tgisconstraint']); + $trigger['tgdeferrable'] = $this->phpBool($trigger['tgdeferrable']); + $trigger['tginitdeferred'] = $this->phpBool($trigger['tginitdeferred']); + + // Constraint trigger or normal trigger + if ($trigger['tgisconstraint']) { + $tgdef = 'CREATE CONSTRAINT TRIGGER '; + } else { + $tgdef = 'CREATE TRIGGER '; + } + + $tgdef .= "\"{$trigger['tgname']}\" "; + + // Trigger type + $findx = 0; + if (($trigger['tgtype'] & TRIGGER_TYPE_BEFORE) == TRIGGER_TYPE_BEFORE) { + $tgdef .= 'BEFORE'; + } else { + $tgdef .= 'AFTER'; + } + + if (($trigger['tgtype'] & TRIGGER_TYPE_INSERT) == TRIGGER_TYPE_INSERT) { + $tgdef .= ' INSERT'; + $findx++; + } + if (($trigger['tgtype'] & TRIGGER_TYPE_DELETE) == TRIGGER_TYPE_DELETE) { + if ($findx > 0) { + $tgdef .= ' OR DELETE'; + } else { + $tgdef .= ' DELETE'; + $findx++; + } + } + if (($trigger['tgtype'] & TRIGGER_TYPE_UPDATE) == TRIGGER_TYPE_UPDATE) { + if ($findx > 0) { + $tgdef .= ' OR UPDATE'; + } else { + $tgdef .= ' UPDATE'; + } + + } + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + // Table name + $tgdef .= " ON \"{$f_schema}\".\"{$trigger['relname']}\" "; + + // Deferrability + if ($trigger['tgisconstraint']) { + if ($trigger['tgconstrrelid'] != 0) { + // Assume constrelname is not null + $tgdef .= " FROM \"{$trigger['tgconstrrelname']}\" "; + } + if (!$trigger['tgdeferrable']) { + $tgdef .= 'NOT '; + } + + $tgdef .= 'DEFERRABLE INITIALLY '; + if ($trigger['tginitdeferred']) { + $tgdef .= 'DEFERRED '; + } else { + $tgdef .= 'IMMEDIATE '; + } + + } + + // Row or statement + if ($trigger['tgtype'] & TRIGGER_TYPE_ROW == TRIGGER_TYPE_ROW) { + $tgdef .= 'FOR EACH ROW '; + } else { + $tgdef .= 'FOR EACH STATEMENT '; + } + + // Execute procedure + $tgdef .= "EXECUTE PROCEDURE \"{$trigger['tgfname']}\"("; + + // Parameters + // Escape null characters + $v = addCSlashes($trigger['tgargs'], "\0"); + // Split on escaped null characters + $params = explode('\\000', $v); + for ($findx = 0; $findx < $trigger['tgnargs']; $findx++) { + $param = "'" . str_replace('\'', '\\\'', $params[$findx]) . "'"; + $tgdef .= $param; + if ($findx < ($trigger['tgnargs'] - 1)) { + $tgdef .= ', '; + } + + } + + // Finish it off + $tgdef .= ')'; + + return $tgdef; + } + + /** + * Returns a list of all functions that can be used in triggers + */ + public function getTriggerFunctions() + { + return $this->getFunctions(true, 'trigger'); + } + + /** + * 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 + */ + public function createTrigger($tgname, $table, $tgproc, $tgtime, $tgevent, $tgfrequency, $tgargs) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($tgname); + $this->fieldClean($table); + $this->fieldClean($tgproc); + + /* No Statement Level Triggers in PostgreSQL (by now) */ + $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime} {$tgevent} ON \"{$f_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $this->fieldClean($trigger); - $this->fieldClean($name); - - $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$f_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 - * @param $cascade True to cascade drop, false to restrict - * @return 0 success - */ - function dropTrigger($tgname, $table, $cascade) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($tgname); - $this->fieldClean($table); - - $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$f_schema}\".\"{$table}\""; - if ($cascade) { - $sql .= " CASCADE"; - } - - return $this->execute($sql); - } - - /** - * 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($tgname); - $this->fieldClean($table); - - $sql = "ALTER TABLE \"{$f_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($tgname); - $this->fieldClean($table); - - $sql = "ALTER TABLE \"{$f_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() { - $c_schema = $this->_schema; - $this->clean($c_schema); - // We stick with the subselects here, as you cannot ORDER BY a regtype - $sql = " + 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 + */ + public function alterTrigger($table, $trigger, $name) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $this->fieldClean($trigger); + $this->fieldClean($name); + + $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$f_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 + * @param $cascade True to cascade drop, false to restrict + * @return 0 success + */ + public function dropTrigger($tgname, $table, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($tgname); + $this->fieldClean($table); + + $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$f_schema}\".\"{$table}\""; + if ($cascade) { + $sql .= " CASCADE"; + } + + return $this->execute($sql); + } + + /** + * 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 + */ + public function enableTrigger($tgname, $table) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($tgname); + $this->fieldClean($table); + + $sql = "ALTER TABLE \"{$f_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 + */ + public function disableTrigger($tgname, $table) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($tgname); + $this->fieldClean($table); + + $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\""; + + return $this->execute($sql); + } + + // Operator functions + + /** + * Returns a list of all operators in the database + * @return All operators + */ + public function getOperators() + { + $c_schema = $this->_schema; + $this->clean($c_schema); + // 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, @@ -5573,18 +5725,19 @@ class Postgres extends ADODB_base { po.oprname, oprleftname, oprrightname "; - return $this->selectSet($sql); - } + 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); + /** + * Returns all details for a particular operator + * @param $operator_oid The oid of the operator + * @return Function info + */ + public function getOperator($operator_oid) + { + $this->clean($operator_oid); - $sql = " + $sql = " SELECT po.oid, po.oprname, oprleft::pg_catalog.regtype AS oprleftname, @@ -5603,55 +5756,57 @@ class Postgres extends ADODB_base { 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); - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($opr->fields['oprname']); - - $sql = "DROP OPERATOR \"{$f_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() { - $c_schema = $this->_schema; - $this->clean($c_schema); - $sql = " + 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 + */ + public function dropOperator($operator_oid, $cascade) + { + // Function comes in with $object as operator OID + $opr = $this->getOperator($operator_oid); + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($opr->fields['oprname']); + + $sql = "DROP OPERATOR \"{$f_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 + */ + + public function getOpClasses() + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $sql = " SELECT pa.amname, po.opcname, po.opcintype::pg_catalog.regtype AS opcintype, @@ -5666,81 +5821,83 @@ class Postgres extends ADODB_base { 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 = '') { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($cfgname); - - $sql = "CREATE TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$cfgname}\" ("; - if ($parser != '') { - $this->fieldClean($parser['schema']); - $this->fieldClean($parser['parser']); - $parser = "\"{$parser['schema']}\".\"{$parser['parser']}\""; - $sql .= " PARSER = {$parser}"; - } - if ($template != '') { - $this->fieldClean($template['schema']); - $this->fieldClean($template['name']); - $sql .= " COPY = \"{$template['schema']}\".\"{$template['name']}\""; - } - $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 != '') { - $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - return $this->endTransaction(); - } - - return 0; - } - - /** - * Returns available FTS configurations - * @param $all if false, returns schema qualified FTS confs - * - * @return A recordset - */ - function getFtsConfigurations($all = true) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $sql = " + 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 + */ + public function createFtsConfiguration($cfgname, $parser = '', $template = '', $comment = '') + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($cfgname); + + $sql = "CREATE TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$cfgname}\" ("; + if ($parser != '') { + $this->fieldClean($parser['schema']); + $this->fieldClean($parser['parser']); + $parser = "\"{$parser['schema']}\".\"{$parser['parser']}\""; + $sql .= " PARSER = {$parser}"; + } + if ($template != '') { + $this->fieldClean($template['schema']); + $this->fieldClean($template['name']); + $sql .= " COPY = \"{$template['schema']}\".\"{$template['name']}\""; + } + $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 != '') { + $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + return $this->endTransaction(); + } + + return 0; + } + + /** + * Returns available FTS configurations + * @param $all if false, returns schema qualified FTS confs + * + * @return A recordset + */ + public function getFtsConfigurations($all = true) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $sql = " SELECT n.nspname as schema, c.cfgname as name, @@ -5751,26 +5908,27 @@ class Postgres extends ADODB_base { WHERE pg_catalog.pg_ts_config_is_visible(c.oid)"; - if (!$all) { - $sql .= " AND n.nspname='{$c_schema}'\n"; - } - - $sql .= "ORDER BY 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($ftscfg); - $sql = " + if (!$all) { + $sql .= " AND n.nspname='{$c_schema}'\n"; + } + + $sql .= "ORDER BY 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 + */ + public function getFtsConfigurationByName($ftscfg) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($ftscfg); + $sql = " SELECT n.nspname as schema, c.cfgname as name, @@ -5784,31 +5942,32 @@ class Postgres extends ADODB_base { AND c.cfgname = '{$ftscfg}' AND n.nspname='{$c_schema}'"; - return $this->selectSet($sql); - } + 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 - * - * @return RecordSet - */ - function getFtsConfigurationMap($ftscfg) { + /** + * Returns the map of FTS configuration given + * (list of mappings (tokens) and their processing dictionaries) + * @param string $ftscfg Name of the FTS configuration + * + * @return RecordSet + */ + public function getFtsConfigurationMap($ftscfg) + { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->fieldClean($ftscfg); + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->fieldClean($ftscfg); - $oidSet = $this->selectSet("SELECT c.oid + $oidSet = $this->selectSet("SELECT c.oid FROM pg_catalog.pg_ts_config AS c LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.cfgnamespace) WHERE c.cfgname = '{$ftscfg}' AND n.nspname='{$c_schema}'"); - $oid = $oidSet->fields['oid']; + $oid = $oidSet->fields['oid']; - $sql = " + $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, @@ -5823,19 +5982,20 @@ class Postgres extends ADODB_base { AND d.dictnamespace = n.oid ORDER BY name "; - return $this->selectSet($sql); - } - - /** - * Returns FTS parsers available - * @param $all if false, return only Parsers from the current schema - * - * @return RecordSet - */ - function getFtsParsers($all = true) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $sql = " + return $this->selectSet($sql); + } + + /** + * Returns FTS parsers available + * @param $all if false, return only Parsers from the current schema + * + * @return RecordSet + */ + public function getFtsParsers($all = true) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $sql = " SELECT n.nspname as schema, p.prsname as name, @@ -5844,25 +6004,26 @@ class Postgres extends ADODB_base { LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = p.prsnamespace) WHERE pg_catalog.pg_ts_parser_is_visible(p.oid)"; - if (!$all) { - $sql .= " AND n.nspname='{$c_schema}'\n"; - } - - $sql .= "ORDER BY name"; - - return $this->selectSet($sql); - } - - /** - * Returns FTS dictionaries available - * @param $all if false, return only Dics from the current schema - * - * @returns RecordSet - */ - function getFtsDictionaries($all = true) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $sql = " + if (!$all) { + $sql .= " AND n.nspname='{$c_schema}'\n"; + } + + $sql .= "ORDER BY name"; + + return $this->selectSet($sql); + } + + /** + * Returns FTS dictionaries available + * @param $all if false, return only Dics from the current schema + * + * @returns RecordSet + */ + public function getFtsDictionaries($all = true) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $sql = " SELECT n.nspname as schema, d.dictname as name, pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment @@ -5870,21 +6031,22 @@ class Postgres extends ADODB_base { LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace WHERE pg_catalog.pg_ts_dict_is_visible(d.oid)"; - if (!$all) { - $sql .= " AND n.nspname='{$c_schema}'\n"; - } + if (!$all) { + $sql .= " AND n.nspname='{$c_schema}'\n"; + } - $sql .= "ORDER BY name;"; + $sql .= "ORDER BY name;"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - /** - * Returns all FTS dictionary templates available - */ - function getFtsDictionaryTemplates() { + /** + * Returns all FTS dictionary templates available + */ + public function getFtsDictionaryTemplates() + { - $sql = " + $sql = " SELECT n.nspname as schema, t.tmplname as name, @@ -5902,227 +6064,232 @@ class Postgres extends ADODB_base { WHERE pg_catalog.pg_ts_template_is_visible(t.oid) ORDER BY name;"; - return $this->selectSet($sql); - } - - /** - * Drops FTS coniguration - * @param $ftscfg The configuration's name - * @param $cascade Cascade to dependenced objects - * - * @return 0 on success - */ - function dropFtsConfiguration($ftscfg, $cascade) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($ftscfg); - - $sql = "DROP TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$ftscfg}\""; - if ($cascade) { - $sql .= ' CASCADE'; - } - - return $this->execute($sql); - } - - /** - * Drops FTS dictionary - * @param $ftsdict The dico's name - * @param $cascade Cascade to dependenced objects - * - * @todo Support of dictionary templates dropping - * @return 0 on success - */ - function dropFtsDictionary($ftsdict, $cascade) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($ftsdict); - - $sql = "DROP TEXT SEARCH DICTIONARY"; - $sql .= " \"{$f_schema}\".\"{$ftsdict}\""; - if ($cascade) { - $sql .= ' CASCADE'; - } - - return $this->execute($sql); - } - - /** - * Alters FTS configuration - * @param $cfgname The conf's name - * @param $comment A comment on for the conf - * @param $name The new conf name - * - * @return 0 on success - */ - function updateFtsConfiguration($cfgname, $comment, $name) { - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $this->fieldClean($cfgname); - - $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Only if the name has changed - if ($name != $cfgname) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($name); - - $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$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 = '') { - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($dictname); - $this->fieldClean($template); - $this->fieldClean($lexize); - $this->fieldClean($init); - $this->fieldClean($option); - - $sql = "CREATE TEXT SEARCH"; - if ($isTemplate) { - $sql .= " TEMPLATE \"{$f_schema}\".\"{$dictname}\" ("; - if ($lexize != '') { - $sql .= " LEXIZE = {$lexize}"; - } - - if ($init != '') { - $sql .= ", INIT = {$init}"; - } - - $sql .= ")"; - $whatToComment = 'TEXT SEARCH TEMPLATE'; - } else { - $sql .= " DICTIONARY \"{$f_schema}\".\"{$dictname}\" ("; - if ($template != '') { - $this->fieldClean($template['schema']); - $this->fieldClean($template['name']); - $template = "\"{$template['schema']}\".\"{$template['name']}\""; - - $sql .= " TEMPLATE = {$template}"; - } - if ($option != '') { - $sql .= ", {$option}"; - } - - $sql .= ")"; - $whatToComment = 'TEXT SEARCH DICTIONARY'; - } - - /* if comment, begin a transaction to - * run both commands */ - 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 - * @param $dictname The dico's name - * @param $comment The comment - * @param $name The new dico's name - * - * @return 0 on success - */ - function updateFtsDictionary($dictname, $comment, $name) { - - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $this->fieldClean($dictname); - $status = $this->setComment('TEXT SEARCH DICTIONARY', $dictname, '', $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - // Only if the name has changed - if ($name != $dictname) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($name); - - $sql = "ALTER TEXT SEARCH DICTIONARY \"{$f_schema}\".\"{$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 RecordSet of FTS dictionary information - */ - function getFtsDictionaryByName($ftsdict) { - - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($ftsdict); - - $sql = "SELECT + return $this->selectSet($sql); + } + + /** + * Drops FTS coniguration + * @param $ftscfg The configuration's name + * @param $cascade Cascade to dependenced objects + * + * @return 0 on success + */ + public function dropFtsConfiguration($ftscfg, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($ftscfg); + + $sql = "DROP TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$ftscfg}\""; + if ($cascade) { + $sql .= ' CASCADE'; + } + + return $this->execute($sql); + } + + /** + * Drops FTS dictionary + * @param $ftsdict The dico's name + * @param $cascade Cascade to dependenced objects + * + * @todo Support of dictionary templates dropping + * @return 0 on success + */ + public function dropFtsDictionary($ftsdict, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($ftsdict); + + $sql = "DROP TEXT SEARCH DICTIONARY"; + $sql .= " \"{$f_schema}\".\"{$ftsdict}\""; + if ($cascade) { + $sql .= ' CASCADE'; + } + + return $this->execute($sql); + } + + /** + * Alters FTS configuration + * @param $cfgname The conf's name + * @param $comment A comment on for the conf + * @param $name The new conf name + * + * @return 0 on success + */ + public function updateFtsConfiguration($cfgname, $comment, $name) + { + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $this->fieldClean($cfgname); + + $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + // Only if the name has changed + if ($name != $cfgname) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($name); + + $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$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 + */ + public function createFtsDictionary($dictname, $isTemplate = false, $template = '', $lexize = '', + $init = '', $option = '', $comment = '') { + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($dictname); + $this->fieldClean($template); + $this->fieldClean($lexize); + $this->fieldClean($init); + $this->fieldClean($option); + + $sql = "CREATE TEXT SEARCH"; + if ($isTemplate) { + $sql .= " TEMPLATE \"{$f_schema}\".\"{$dictname}\" ("; + if ($lexize != '') { + $sql .= " LEXIZE = {$lexize}"; + } + + if ($init != '') { + $sql .= ", INIT = {$init}"; + } + + $sql .= ")"; + $whatToComment = 'TEXT SEARCH TEMPLATE'; + } else { + $sql .= " DICTIONARY \"{$f_schema}\".\"{$dictname}\" ("; + if ($template != '') { + $this->fieldClean($template['schema']); + $this->fieldClean($template['name']); + $template = "\"{$template['schema']}\".\"{$template['name']}\""; + + $sql .= " TEMPLATE = {$template}"; + } + if ($option != '') { + $sql .= ", {$option}"; + } + + $sql .= ")"; + $whatToComment = 'TEXT SEARCH DICTIONARY'; + } + + /* if comment, begin a transaction to + * run both commands */ + 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 + * @param $dictname The dico's name + * @param $comment The comment + * @param $name The new dico's name + * + * @return 0 on success + */ + public function updateFtsDictionary($dictname, $comment, $name) + { + + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $this->fieldClean($dictname); + $status = $this->setComment('TEXT SEARCH DICTIONARY', $dictname, '', $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + // Only if the name has changed + if ($name != $dictname) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($name); + + $sql = "ALTER TEXT SEARCH DICTIONARY \"{$f_schema}\".\"{$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 RecordSet of FTS dictionary information + */ + public function getFtsDictionaryByName($ftsdict) + { + + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($ftsdict); + + $sql = "SELECT n.nspname as schema, d.dictname as name, ( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM @@ -6138,124 +6305,128 @@ class Postgres extends ADODB_base { AND n.nspname='{$c_schema}' ORDER BY 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) { - - if (count($mapping) > 0) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($ftscfg); - $this->fieldClean($dictname); - $this->arrayClean($mapping); - - switch ($action) { - case 'alter': - $whatToDo = "ALTER"; - break; - case 'drop': - $whatToDo = "DROP"; - break; - default: - $whatToDo = "ADD"; - break; - } - - $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$ftscfg}\" {$whatToDo} MAPPING FOR "; - $sql .= implode(",", $mapping); - if ($action != 'drop' && !empty($dictname)) { - $sql .= " WITH {$dictname}"; - } - - return $this->execute($sql); - } 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($ftscfg); - $this->clean($mapping); - - $oidSet = $this->selectSet("SELECT c.oid, cfgparser + 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 + */ + public function changeFtsMapping($ftscfg, $mapping, $action, $dictname = null) + { + + if (count($mapping) > 0) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($ftscfg); + $this->fieldClean($dictname); + $this->arrayClean($mapping); + + switch ($action) { + case 'alter': + $whatToDo = "ALTER"; + break; + case 'drop': + $whatToDo = "DROP"; + break; + default: + $whatToDo = "ADD"; + break; + } + + $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$ftscfg}\" {$whatToDo} MAPPING FOR "; + $sql .= implode(",", $mapping); + if ($action != 'drop' && !empty($dictname)) { + $sql .= " WITH {$dictname}"; + } + + return $this->execute($sql); + } 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 + */ + public function getFtsMappingByName($ftscfg, $mapping) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($ftscfg); + $this->clean($mapping); + + $oidSet = $this->selectSet("SELECT c.oid, cfgparser FROM pg_catalog.pg_ts_config AS c LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = c.cfgnamespace WHERE c.cfgname = '{$ftscfg}' AND n.nspname='{$c_schema}'"); - $oid = $oidSet->fields['oid']; - $cfgparser = $oidSet->fields['cfgparser']; + $oid = $oidSet->fields['oid']; + $cfgparser = $oidSet->fields['cfgparser']; - $tokenIdSet = $this->selectSet("SELECT tokid + $tokenIdSet = $this->selectSet("SELECT tokid FROM pg_catalog.ts_token_type({$cfgparser}) WHERE alias = '{$mapping}'"); - $tokid = $tokenIdSet->fields['tokid']; + $tokid = $tokenIdSet->fields['tokid']; - $sql = "SELECT + $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 $this->selectSet($sql); + } - /** - * Return list of FTS mappings possible for given parser - * (specified by given configuration since configuration can only have 1 parser) - * @param $ftscfg The config's name that use the parser - * - * @return 0 on success - */ - function getFtsMappings($ftscfg) { + /** + * Return list of FTS mappings possible for given parser + * (specified by given configuration since configuration can only have 1 parser) + * @param $ftscfg The config's name that use the parser + * + * @return 0 on success + */ + public function getFtsMappings($ftscfg) + { - $cfg = $this->getFtsConfigurationByName($ftscfg); + $cfg = $this->getFtsConfigurationByName($ftscfg); - $sql = "SELECT alias AS name, description + $sql = "SELECT alias AS name, description FROM pg_catalog.ts_token_type({$cfg->fields['parser_id']}) ORDER BY name"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Language functions + // Language functions - /** - * Gets all languages - * @param $all True to get all languages, regardless of show_system - * @return A recordset - */ - function getLanguages($all = false) { - $conf = $this->conf; + /** + * Gets all languages + * @param $all True to get all languages, regardless of show_system + * @return A recordset + */ + public function getLanguages($all = false) + { + $conf = $this->conf; - if ($conf['show_system'] || $all) { - $where = ''; - } else { - $where = 'WHERE lanispl'; - } + if ($conf['show_system'] || $all) { + $where = ''; + } else { + $where = 'WHERE lanispl'; + } - $sql = " + $sql = " SELECT lanname, lanpltrusted, lanplcallfoid::pg_catalog.regproc AS lanplcallf @@ -6265,116 +6436,120 @@ class Postgres extends ADODB_base { 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($name); - $this->fieldClean($basetype); - $this->fieldClean($sfunc); - $this->fieldClean($stype); - $this->fieldClean($ffunc); - $this->fieldClean($initcond); - $this->fieldClean($sortop); - - $this->beginTransaction(); - - $sql = "CREATE AGGREGATE \"{$f_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) { - /* this function is called from alterAggregate where params are cleaned */ - $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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($aggrname); - $this->fieldClean($aggrtype); - - $sql = "DROP AGGREGATE \"{$f_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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->fieldclean($name); - $this->fieldclean($basetype); - - $sql = " + 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 + */ + public function createAggregate($name, $basetype, $sfunc, $stype, $ffunc, $initcond, $sortop, $comment) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($name); + $this->fieldClean($basetype); + $this->fieldClean($sfunc); + $this->fieldClean($stype); + $this->fieldClean($ffunc); + $this->fieldClean($initcond); + $this->fieldClean($sortop); + + $this->beginTransaction(); + + $sql = "CREATE AGGREGATE \"{$f_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 + */ + public function renameAggregate($aggrschema, $aggrname, $aggrtype, $newaggrname) + { + /* this function is called from alterAggregate where params are cleaned */ + $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 + */ + public function dropAggregate($aggrname, $aggrtype, $cascade) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($aggrname); + $this->fieldClean($aggrtype); + + $sql = "DROP AGGREGATE \"{$f_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 + */ + public function getAggregate($name, $basetype) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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, @@ -6389,701 +6564,724 @@ class Postgres extends ADODB_base { ELSE pg_catalog.format_type(p.proargtypes[0], NULL) END ='" . $basetype . "'"; - return $this->selectSet($sql); - } - - /** - * Gets all aggregates - * @return A recordset - */ - function getAggregates() { - $c_schema = $this->_schema; - $this->clean($c_schema); - $sql = "SELECT p.proname, CASE p.proargtypes[0] WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL ELSE + return $this->selectSet($sql); + } + + /** + * Gets all aggregates + * @return A recordset + */ + public function getAggregates() + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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='{$c_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($aggrname); - $this->fieldClean($newaggrowner); - $sql = "ALTER AGGREGATE \"{$f_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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($aggrname); - $this->fieldClean($newaggrschema); - $sql = "ALTER AGGREGATE \"{$f_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->fieldClean($newaggrname); - $this->fieldClean($newaggrowner); - $this->fieldClean($newaggrschema); - - $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 = ' + 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 + */ + public function changeAggregateOwner($aggrname, $aggrtype, $newaggrowner) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($aggrname); + $this->fieldClean($newaggrowner); + $sql = "ALTER AGGREGATE \"{$f_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 + */ + public function changeAggregateSchema($aggrname, $aggrtype, $newaggrschema) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($aggrname); + $this->fieldClean($newaggrschema); + $sql = "ALTER AGGREGATE \"{$f_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 + */ + public 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->fieldClean($newaggrname); + $this->fieldClean($newaggrowner); + $this->fieldClean($newaggrschema); + + $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 + */ + public 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}'"; - } + if ($rolename) { + $sql .= " WHERE rolname!='{$rolename}'"; + } - $sql .= ' ORDER BY rolname'; + $sql .= ' ORDER BY rolname'; - return $this->selectSet($sql); - } + 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); + /** + * Returns information about a single role + * @param $rolename The name of the role to retrieve + * @return The role's data + */ + public function getRole($rolename) + { + $this->clean($rolename); - $sql = " + $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, useconfig + 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 + */ + public 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 + */ + public 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 + */ + public function getUsers() + { + $sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, useconfig FROM pg_user ORDER BY usename"; - return $this->selectSet($sql); - } + 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); + /** + * Returns information about a single user + * @param $username The username of the user to retrieve + * @return The user's data + */ + public function getUser($username) + { + $this->clean($username); - $sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, useconfig + $sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, useconfig 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; - } - - if ($rolename != $newrolename) { - $status = $this->renameRole($rolename, $newrolename); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - $rolename = $newrolename; - } - - $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 $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; - } - - if ($username != $newname) { - $status = $this->renameUser($username, $newname); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - $username = $newname; - } - - $status = $this->setUser($username, $password, $createdb, $createuser, $expiry); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } - - 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 (empty($usename)) { - $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 = " + 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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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; + } + + if ($rolename != $newrolename) { + $status = $this->renameRole($rolename, $newrolename); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } + $rolename = $newrolename; + } + + $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 $this->endTransaction(); + } + + /** + * Removes a role + * @param $rolename The name of the role to drop + * @return 0 success + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public function setRenameUser($username, $password, $createdb, $createuser, $expiry, $newname) + { + $status = $this->beginTransaction(); + if ($status != 0) { + return -1; + } + + if ($username != $newname) { + $status = $this->renameUser($username, $newname); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } + $username = $newname; + } + + $status = $this->setUser($username, $password, $createdb, $createuser, $expiry); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; + } + + return $this->endTransaction(); + } + + /** + * Removes a user + * @param $username The username of the user to drop + * @return 0 success + */ + public 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 + */ + public function isSuperUser($username = '') + { + $this->clean($username); + + if (empty($usename)) { + $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 + */ + public 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 + */ + public 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 + */ + public 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 ( @@ -7091,247 +7289,255 @@ class Postgres extends ADODB_base { WHERE rolname='{$rolename}') ORDER BY rolname"; - return $this->selectSet($sql); - } + 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); + /** + * 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 + */ + public function getMembers($rolename, $admin = 'f') + { + $this->clean($rolename); - $sql = " + $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 = " + 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 + */ + public 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 + */ + public 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 - * @param $acl The ACL to parse (of type aclitem[]) - * @return Privileges array - */ - function _parseACL($acl) { - // Take off the first and last characters (the braces) - $acl = substr($acl, 1, strlen($acl) - 2); - - // Pick out individual ACE's by carefully parsing. This is necessary in order - // to cope with usernames and stuff that contain commas - $aces = []; - $i = $j = 0; - $in_quotes = false; - while ($i < strlen($acl)) { - // If current char is a double quote and it's not escaped, then - // enter quoted bit - $char = substr($acl, $i, 1); - if ($char == '"' && ($i == 0 || substr($acl, $i - 1, 1) != '\\')) { - $in_quotes = !$in_quotes; - } elseif ($char == ',' && !$in_quotes) { - // Add text so far to the array - $aces[] = substr($acl, $j, $i - $j); - $j = $i + 1; - } - $i++; - } - // Add final text to the array - $aces[] = substr($acl, $j); - - // Create the array to be returned - $temp = []; - - // For each ACE, generate an entry in $temp - foreach ($aces as $v) { - - // If the ACE begins with a double quote, strip them off both ends - // and unescape backslashes and double quotes - $unquote = false; - if (strpos($v, '"') === 0) { - $v = substr($v, 1, strlen($v) - 2); - $v = str_replace('\\"', '"', $v); - $v = str_replace('\\\\', '\\', $v); - } - - // Figure out type of ACE (public, user or group) - if (strpos($v, '=') === 0) { - $atype = 'public'; - } else if ($this->hasRoles()) { - $atype = 'role'; - } else if (strpos($v, 'group ') === 0) { - $atype = 'group'; - // Tear off 'group' prefix - $v = substr($v, 6); - } else { - $atype = 'user'; - } - - // Break on unquoted equals sign... - $i = 0; - $in_quotes = false; - $entity = null; - $chars = null; - while ($i < strlen($v)) { - // If current char is a double quote and it's not escaped, then - // enter quoted bit - $char = substr($v, $i, 1); - $next_char = substr($v, $i + 1, 1); - if ($char == '"' && ($i == 0 || $next_char != '"')) { - $in_quotes = !$in_quotes; - } - // Skip over escaped double quotes - elseif ($char == '"' && $next_char == '"') { - $i++; - } elseif ($char == '=' && !$in_quotes) { - // Split on current equals sign - $entity = substr($v, 0, $i); - $chars = substr($v, $i + 1); - break; - } - $i++; - } - - // Check for quoting on entity name, and unescape if necessary - if (strpos($entity, '"') === 0) { - $entity = substr($entity, 1, strlen($entity) - 2); - $entity = str_replace('""', '"', $entity); - } - - // New row to be added to $temp - // (type, grantee, privileges, grantor, grant option? - $row = [$atype, $entity, [], '', []]; - - // Loop over chars and add privs to $row - for ($i = 0; $i < strlen($chars); $i++) { - // Append to row's privs list the string representing - // the privilege - $char = substr($chars, $i, 1); - if ($char == '*') { - $row[4][] = $this->privmap[substr($chars, $i - 1, 1)]; - } elseif ($char == '/') { - $grantor = substr($chars, $i + 1); - // Check for quoting - if (strpos($grantor, '"') === 0) { - $grantor = substr($grantor, 1, strlen($grantor) - 2); - $grantor = str_replace('""', '"', $grantor); - } - $row[3] = $grantor; - break; - } else { - if (!isset($this->privmap[$char])) { - return -3; - } else { - $row[2][] = $this->privmap[$char]; - } - - } - } - - // Append row to temp - $temp[] = $row; - } - - return $temp; - } - - /** - * 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) - * @param $table Optional, column's table if type = column - * @return Privileges array - * @return -1 invalid type - * @return -2 object not found - * @return -3 unknown privilege type - */ - function getPrivileges($object, $type, $table = null) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($object); - - switch ($type) { - case 'column': - $this->clean($table); - $sql = " + return $this->selectSet($sql); + } + + /** + * Returns all groups in the database cluser + * @return All groups + */ + public 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 + */ + public 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 + */ + public function dropGroup($groname) + { + $this->fieldClean($groname); + + $sql = "DROP GROUP \"{$groname}\""; + + return $this->execute($sql); + } + + /** + * Internal function used for parsing ACLs + * @param $acl The ACL to parse (of type aclitem[]) + * @return Privileges array + */ + public function _parseACL($acl) + { + // Take off the first and last characters (the braces) + $acl = substr($acl, 1, strlen($acl) - 2); + + // Pick out individual ACE's by carefully parsing. This is necessary in order + // to cope with usernames and stuff that contain commas + $aces = []; + $i = $j = 0; + $in_quotes = false; + while ($i < strlen($acl)) { + // If current char is a double quote and it's not escaped, then + // enter quoted bit + $char = substr($acl, $i, 1); + if ($char == '"' && ($i == 0 || substr($acl, $i - 1, 1) != '\\')) { + $in_quotes = !$in_quotes; + } elseif ($char == ',' && !$in_quotes) { + // Add text so far to the array + $aces[] = substr($acl, $j, $i - $j); + $j = $i + 1; + } + $i++; + } + // Add final text to the array + $aces[] = substr($acl, $j); + + // Create the array to be returned + $temp = []; + + // For each ACE, generate an entry in $temp + foreach ($aces as $v) { + + // If the ACE begins with a double quote, strip them off both ends + // and unescape backslashes and double quotes + $unquote = false; + if (strpos($v, '"') === 0) { + $v = substr($v, 1, strlen($v) - 2); + $v = str_replace('\\"', '"', $v); + $v = str_replace('\\\\', '\\', $v); + } + + // Figure out type of ACE (public, user or group) + if (strpos($v, '=') === 0) { + $atype = 'public'; + } else if ($this->hasRoles()) { + $atype = 'role'; + } else if (strpos($v, 'group ') === 0) { + $atype = 'group'; + // Tear off 'group' prefix + $v = substr($v, 6); + } else { + $atype = 'user'; + } + + // Break on unquoted equals sign... + $i = 0; + $in_quotes = false; + $entity = null; + $chars = null; + while ($i < strlen($v)) { + // If current char is a double quote and it's not escaped, then + // enter quoted bit + $char = substr($v, $i, 1); + $next_char = substr($v, $i + 1, 1); + if ($char == '"' && ($i == 0 || $next_char != '"')) { + $in_quotes = !$in_quotes; + } + // Skip over escaped double quotes + elseif ($char == '"' && $next_char == '"') { + $i++; + } elseif ($char == '=' && !$in_quotes) { + // Split on current equals sign + $entity = substr($v, 0, $i); + $chars = substr($v, $i + 1); + break; + } + $i++; + } + + // Check for quoting on entity name, and unescape if necessary + if (strpos($entity, '"') === 0) { + $entity = substr($entity, 1, strlen($entity) - 2); + $entity = str_replace('""', '"', $entity); + } + + // New row to be added to $temp + // (type, grantee, privileges, grantor, grant option? + $row = [$atype, $entity, [], '', []]; + + // Loop over chars and add privs to $row + for ($i = 0; $i < strlen($chars); $i++) { + // Append to row's privs list the string representing + // the privilege + $char = substr($chars, $i, 1); + if ($char == '*') { + $row[4][] = $this->privmap[substr($chars, $i - 1, 1)]; + } elseif ($char == '/') { + $grantor = substr($chars, $i + 1); + // Check for quoting + if (strpos($grantor, '"') === 0) { + $grantor = substr($grantor, 1, strlen($grantor) - 2); + $grantor = str_replace('""', '"', $grantor); + } + $row[3] = $grantor; + break; + } else { + if (!isset($this->privmap[$char])) { + return -3; + } else { + $row[2][] = $this->privmap[$char]; + } + + } + } + + // Append row to temp + $temp[] = $row; + } + + return $temp; + } + + /** + * 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) + * @param $table Optional, column's table if type = column + * @return Privileges array + * @return -1 invalid type + * @return -2 object not found + * @return -3 unknown privilege type + */ + public function getPrivileges($object, $type, $table = null) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($object); + + switch ($type) { + case 'column': + $this->clean($table); + $sql = " SELECT E'{' || pg_catalog.array_to_string(attacl, E',') || E'}' as acl FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_class c ON (a.attrelid = c.oid) @@ -7339,391 +7545,400 @@ class Postgres extends ADODB_base { WHERE n.nspname='{$c_schema}' AND c.relname='{$table}' AND a.attname='{$object}'"; - break; - case 'table': - case 'view': - case 'sequence': - $sql = " + break; + 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='{$c_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 []; - } else { - return $this->_parseACL($acl); - } - - } - - /** - * Grants a privilege to a user, group or public - * @param $mode 'GRANT' or 'REVOKE'; - * @param $type The type of object - * @param $object The name of the object - * @param $public True to grant to public, false otherwise - * @param $usernames The array of usernames to grant privs to. - * @param $groupnames The array of group names to grant privs to. - * @param $privileges The array of privileges to grant (eg. ('SELECT', 'ALL PRIVILEGES', etc.) ) - * @param $grantoption True if has grant option, false otherwise - * @param $cascade True for cascade revoke, false otherwise - * @param $table the column's table if type=column - * @return 0 success - * @return -1 invalid type - * @return -2 invalid entity - * @return -3 invalid privileges - * @return -4 not granting to anything - * @return -4 invalid mode - */ - function setPrivileges($mode, $type, $object, $public, $usernames, $groupnames, - $privileges, $grantoption, $cascade, $table - ) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldArrayClean($usernames); - $this->fieldArrayClean($groupnames); - - // Input checking - if (!is_array($privileges) || sizeof($privileges) == 0) { - return -3; - } - - if (!is_array($usernames) || !is_array($groupnames) || - (!$public && sizeof($usernames) == 0 && sizeof($groupnames) == 0)) { - return -4; - } - - if ($mode != 'GRANT' && $mode != 'REVOKE') { - return -5; - } - - $sql = $mode; - - // Grant option - if ($this->hasGrantOption() && $mode == 'REVOKE' && $grantoption) { - $sql .= ' GRANT OPTION FOR'; - } - - if (in_array('ALL PRIVILEGES', $privileges)) { - $sql .= ' ALL PRIVILEGES'; - } else { - if ($type == 'column') { - $this->fieldClean($object); - $sql .= ' ' . join(" (\"{$object}\"), ", $privileges); - } else { - $sql .= ' ' . join(', ', $privileges); - } - } - - switch ($type) { - case 'column': - $sql .= " (\"{$object}\")"; - $object = $table; - case 'table': - case 'view': - case 'sequence': - $this->fieldClean($object); - $sql .= " ON \"{$f_schema}\".\"{$object}\""; - break; - case 'database': - $this->fieldClean($object); - $sql .= " ON DATABASE \"{$object}\""; - break; - case 'function': - // Function comes in with $object as function OID - $fn = $this->getFunction($object); - $this->fieldClean($fn->fields['proname']); - $sql .= " ON FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})"; - break; - case 'language': - $this->fieldClean($object); - $sql .= " ON LANGUAGE \"{$object}\""; - break; - case 'schema': - $this->fieldClean($object); - $sql .= " ON SCHEMA \"{$object}\""; - break; - case 'tablespace': - $this->fieldClean($object); - $sql .= " ON TABLESPACE \"{$object}\""; - break; - default: - return -1; - } - - // Dump PUBLIC - $first = true; - $sql .= ($mode == 'GRANT') ? ' TO ' : ' FROM '; - if ($public) { - $sql .= 'PUBLIC'; - $first = false; - } - // Dump users - foreach ($usernames as $v) { - if ($first) { - $sql .= "\"{$v}\""; - $first = false; - } else { - $sql .= ", \"{$v}\""; - } - } - // Dump groups - foreach ($groupnames as $v) { - if ($first) { - $sql .= "GROUP \"{$v}\""; - $first = false; - } else { - $sql .= ", GROUP \"{$v}\""; - } - } - - // Grant option - if ($this->hasGrantOption() && $mode == 'GRANT' && $grantoption) { - $sql .= ' WITH GRANT OPTION'; - } - - // Cascade revoke - if ($this->hasGrantOption() && $mode == 'REVOKE' && $cascade) { - $sql .= ' CASCADE'; - } - - return $this->execute($sql); - } - - /** - * Helper function that computes encypted PostgreSQL passwords - * @param $username The username - * @param $password The password - */ - function _encryptPassword($username, $password) { - return 'md5' . md5($password . $username); - } - - // 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) { - $conf = $this->conf; - - $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation, + 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 []; + } else { + return $this->_parseACL($acl); + } + + } + + /** + * Grants a privilege to a user, group or public + * @param $mode 'GRANT' or 'REVOKE'; + * @param $type The type of object + * @param $object The name of the object + * @param $public True to grant to public, false otherwise + * @param $usernames The array of usernames to grant privs to. + * @param $groupnames The array of group names to grant privs to. + * @param $privileges The array of privileges to grant (eg. ('SELECT', 'ALL PRIVILEGES', etc.) ) + * @param $grantoption True if has grant option, false otherwise + * @param $cascade True for cascade revoke, false otherwise + * @param $table the column's table if type=column + * @return 0 success + * @return -1 invalid type + * @return -2 invalid entity + * @return -3 invalid privileges + * @return -4 not granting to anything + * @return -4 invalid mode + */ + public function setPrivileges($mode, $type, $object, $public, $usernames, $groupnames, + $privileges, $grantoption, $cascade, $table + ) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldArrayClean($usernames); + $this->fieldArrayClean($groupnames); + + // Input checking + if (!is_array($privileges) || sizeof($privileges) == 0) { + return -3; + } + + if (!is_array($usernames) || !is_array($groupnames) || + (!$public && sizeof($usernames) == 0 && sizeof($groupnames) == 0)) { + return -4; + } + + if ($mode != 'GRANT' && $mode != 'REVOKE') { + return -5; + } + + $sql = $mode; + + // Grant option + if ($this->hasGrantOption() && $mode == 'REVOKE' && $grantoption) { + $sql .= ' GRANT OPTION FOR'; + } + + if (in_array('ALL PRIVILEGES', $privileges)) { + $sql .= ' ALL PRIVILEGES'; + } else { + if ($type == 'column') { + $this->fieldClean($object); + $sql .= ' ' . join(" (\"{$object}\"), ", $privileges); + } else { + $sql .= ' ' . join(', ', $privileges); + } + } + + switch ($type) { + case 'column': + $sql .= " (\"{$object}\")"; + $object = $table; + case 'table': + case 'view': + case 'sequence': + $this->fieldClean($object); + $sql .= " ON \"{$f_schema}\".\"{$object}\""; + break; + case 'database': + $this->fieldClean($object); + $sql .= " ON DATABASE \"{$object}\""; + break; + case 'function': + // Function comes in with $object as function OID + $fn = $this->getFunction($object); + $this->fieldClean($fn->fields['proname']); + $sql .= " ON FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})"; + break; + case 'language': + $this->fieldClean($object); + $sql .= " ON LANGUAGE \"{$object}\""; + break; + case 'schema': + $this->fieldClean($object); + $sql .= " ON SCHEMA \"{$object}\""; + break; + case 'tablespace': + $this->fieldClean($object); + $sql .= " ON TABLESPACE \"{$object}\""; + break; + default: + return -1; + } + + // Dump PUBLIC + $first = true; + $sql .= ($mode == 'GRANT') ? ' TO ' : ' FROM '; + if ($public) { + $sql .= 'PUBLIC'; + $first = false; + } + // Dump users + foreach ($usernames as $v) { + if ($first) { + $sql .= "\"{$v}\""; + $first = false; + } else { + $sql .= ", \"{$v}\""; + } + } + // Dump groups + foreach ($groupnames as $v) { + if ($first) { + $sql .= "GROUP \"{$v}\""; + $first = false; + } else { + $sql .= ", GROUP \"{$v}\""; + } + } + + // Grant option + if ($this->hasGrantOption() && $mode == 'GRANT' && $grantoption) { + $sql .= ' WITH GRANT OPTION'; + } + + // Cascade revoke + if ($this->hasGrantOption() && $mode == 'REVOKE' && $cascade) { + $sql .= ' CASCADE'; + } + + return $this->execute($sql); + } + + /** + * Helper function that computes encypted PostgreSQL passwords + * @param $username The username + * @param $password The password + */ + public function _encryptPassword($username, $password) + { + return 'md5' . md5($password . $username); + } + + // Tablespace functions + + /** + * Retrieves information for all tablespaces + * @param $all Include all tablespaces (necessary when moving objects back to the default space) + * @return A recordset + */ + public function getTablespaces($all = false) + { + $conf = $this->conf; + + $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation, (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment FROM pg_catalog.pg_tablespace"; - if (!$conf['show_system'] && !$all) { - $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$'; - } + if (!$conf['show_system'] && !$all) { + $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$'; + } - $sql .= " ORDER BY spcname"; + $sql .= " ORDER BY spcname"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - /** - * Retrieves a tablespace's information - * @return A recordset - */ - function getTablespace($spcname) { - $this->clean($spcname); + /** + * Retrieves a tablespace's information + * @return A recordset + */ + public function getTablespace($spcname) + { + $this->clean($spcname); - $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation, + $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation, (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'"; - 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); - - $sql = "CREATE TABLESPACE \"{$spcname}\""; - - if ($spcowner != '') { - $this->fieldClean($spcowner); - $sql .= " OWNER \"{$spcowner}\""; - } - - $sql .= " LOCATION '{$spcloc}'"; - - $status = $this->execute($sql); - if ($status != 0) { - return -1; - } - - if ($comment != '' && $this->hasSharedComments()) { - $status = $this->setComment('TABLESPACE', $spcname, '', $comment); - if ($status != 0) { - return -2; - } - - } - - return 0; - } - - /** - * 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 owner error - * @return -3 rename error - * @return -4 comment error - */ - 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; - } - - // Owner - $sql = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\""; - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } - - // 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; - } - - $spcname = $name; - } - - // Set comment if it has changed - if (trim($comment) != '' && $this->hasSharedComments()) { - $status = $this->setComment('TABLESPACE', $spcname, '', $comment); - if ($status != 0) { - return -4; - } - - } - - return $this->endTransaction(); - } - - /** - * 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); - } - - // Administration functions - - /** - * Analyze a database - * @param $table (optional) The table to analyze - */ - function analyzeDB($table = '') { - if ($table != '') { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - - $sql = "ANALYZE \"{$f_schema}\".\"{$table}\""; - } else { - $sql = "ANALYZE"; - } - - return $this->execute($sql); - } - - /** - * Vacuums a database - * @param $table The table to vacuum - * @param $analyze If true, also does analyze - * @param $full If true, selects "full" vacuum - * @param $freeze If true, selects aggressive "freezing" of tuples - */ - 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 != '') { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - $sql .= " \"{$f_schema}\".\"{$table}\""; - } - - return $this->execute($sql); - } - - /** - * Returns all autovacuum global configuration - * @return associative array array( param => value, ...) - */ - function getAutovacuum() { - - $_defaults = $this->selectSet("SELECT name, setting + 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 + */ + public function createTablespace($spcname, $spcowner, $spcloc, $comment = '') + { + $this->fieldClean($spcname); + $this->clean($spcloc); + + $sql = "CREATE TABLESPACE \"{$spcname}\""; + + if ($spcowner != '') { + $this->fieldClean($spcowner); + $sql .= " OWNER \"{$spcowner}\""; + } + + $sql .= " LOCATION '{$spcloc}'"; + + $status = $this->execute($sql); + if ($status != 0) { + return -1; + } + + if ($comment != '' && $this->hasSharedComments()) { + $status = $this->setComment('TABLESPACE', $spcname, '', $comment); + if ($status != 0) { + return -2; + } + + } + + return 0; + } + + /** + * 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 owner error + * @return -3 rename error + * @return -4 comment error + */ + public 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; + } + + // Owner + $sql = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\""; + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; + } + + // 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; + } + + $spcname = $name; + } + + // Set comment if it has changed + if (trim($comment) != '' && $this->hasSharedComments()) { + $status = $this->setComment('TABLESPACE', $spcname, '', $comment); + if ($status != 0) { + return -4; + } + + } + + return $this->endTransaction(); + } + + /** + * Drops a tablespace + * @param $spcname The name of the domain to drop + * @return 0 success + */ + public function dropTablespace($spcname) + { + $this->fieldClean($spcname); + + $sql = "DROP TABLESPACE \"{$spcname}\""; + + return $this->execute($sql); + } + + // Administration functions + + /** + * Analyze a database + * @param $table (optional) The table to analyze + */ + public function analyzeDB($table = '') + { + if ($table != '') { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + + $sql = "ANALYZE \"{$f_schema}\".\"{$table}\""; + } else { + $sql = "ANALYZE"; + } + + return $this->execute($sql); + } + + /** + * Vacuums a database + * @param $table The table to vacuum + * @param $analyze If true, also does analyze + * @param $full If true, selects "full" vacuum + * @param $freeze If true, selects aggressive "freezing" of tuples + */ + public 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 != '') { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + $sql .= " \"{$f_schema}\".\"{$table}\""; + } + + return $this->execute($sql); + } + + /** + * Returns all autovacuum global configuration + * @return associative array array( param => value, ...) + */ + public function getAutovacuum() + { + + $_defaults = $this->selectSet("SELECT name, setting FROM pg_catalog.pg_settings WHERE name = 'autovacuum' @@ -7736,114 +7951,117 @@ class Postgres extends ADODB_base { OR name = 'vacuum_freeze_min_age' OR name = 'autovacuum_freeze_max_age' " - ); - - $ret = []; - while (!$_defaults->EOF) { - $ret[$_defaults->fields['name']] = $_defaults->fields['setting']; - $_defaults->moveNext(); - } - - return $ret; - } - - /** - * Returns all available autovacuum per table information. - * @return A recordset - */ - function saveAutovacuum($table, $vacenabled, $vacthreshold, $vacscalefactor, $anathresold, - $anascalefactor, $vaccostdelay, $vaccostlimit) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - - $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET ("; - - if (!empty($vacenabled)) { - $this->clean($vacenabled); - $params[] = "autovacuum_enabled='{$vacenabled}'"; - } - if (!empty($vacthreshold)) { - $this->clean($vacthreshold); - $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'"; - } - if (!empty($vacscalefactor)) { - $this->clean($vacscalefactor); - $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'"; - } - if (!empty($anathresold)) { - $this->clean($anathresold); - $params[] = "autovacuum_analyze_threshold='{$anathresold}'"; - } - if (!empty($anascalefactor)) { - $this->clean($anascalefactor); - $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'"; - } - if (!empty($vaccostdelay)) { - $this->clean($vaccostdelay); - $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'"; - } - if (!empty($vaccostlimit)) { - $this->clean($vaccostlimit); - $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'"; - } - - $sql = $sql . implode(',', $params) . ');'; - - return $this->execute($sql); - } - - function dropAutovacuum($table) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - - return $this->execute(" + ); + + $ret = []; + while (!$_defaults->EOF) { + $ret[$_defaults->fields['name']] = $_defaults->fields['setting']; + $_defaults->moveNext(); + } + + return $ret; + } + + /** + * Returns all available autovacuum per table information. + * @return A recordset + */ + public function saveAutovacuum($table, $vacenabled, $vacthreshold, $vacscalefactor, $anathresold, + $anascalefactor, $vaccostdelay, $vaccostlimit) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + + $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET ("; + + if (!empty($vacenabled)) { + $this->clean($vacenabled); + $params[] = "autovacuum_enabled='{$vacenabled}'"; + } + if (!empty($vacthreshold)) { + $this->clean($vacthreshold); + $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'"; + } + if (!empty($vacscalefactor)) { + $this->clean($vacscalefactor); + $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'"; + } + if (!empty($anathresold)) { + $this->clean($anathresold); + $params[] = "autovacuum_analyze_threshold='{$anathresold}'"; + } + if (!empty($anascalefactor)) { + $this->clean($anascalefactor); + $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'"; + } + if (!empty($vaccostdelay)) { + $this->clean($vaccostdelay); + $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'"; + } + if (!empty($vaccostlimit)) { + $this->clean($vaccostlimit); + $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'"; + } + + $sql = $sql . implode(',', $params) . ');'; + + return $this->execute($sql); + } + + public function dropAutovacuum($table) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + + return $this->execute(" ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold, autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor, autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit );" - ); - } - - /** - * 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 datname, usename, pid, waiting, state_change as query_start, + ); + } + + /** + * Returns all available process information. + * @param $database (optional) Find only connections to specified database + * @return A recordset + */ + public function getProcesses($database = null) + { + if ($database === null) { + $sql = "SELECT datname, usename, pid, waiting, state_change as query_start, case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query FROM pg_catalog.pg_stat_activity ORDER BY datname, usename, pid"; - } else { - $this->clean($database); - $sql = "SELECT datname, usename, pid, waiting, state_change as query_start, + } else { + $this->clean($database); + $sql = "SELECT datname, usename, pid, waiting, state_change as query_start, case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query FROM pg_catalog.pg_stat_activity WHERE datname='{$database}' ORDER BY usename, pid"; - } + } - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - /** - * Returns table locks information in the current database - * @return A recordset - */ + /** + * Returns table locks information in the current database + * @return A recordset + */ - function getLocks() { - $conf = $this->conf; + public function getLocks() + { + $conf = $this->conf; - if (!$conf['show_system']) { - $where = 'AND pn.nspname NOT LIKE $$pg\_%$$'; - } else { - $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; - } + if (!$conf['show_system']) { + $where = 'AND pn.nspname NOT LIKE $$pg\_%$$'; + } else { + $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; + } - $sql = " + $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' @@ -7857,802 +8075,905 @@ class Postgres extends ADODB_base { {$where} ORDER BY pid,nspname,tablename"; - return $this->selectSet($sql); - } - - /** - * 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 sendSignal($pid, $signal) { - // Clean - $pid = (int) $pid; - - if ($signal == 'CANCEL') { - $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val"; - } elseif ($signal == 'KILL') { - $sql = "SELECT pg_catalog.pg_terminate_backend({$pid}) AS val"; - } else { - return -1; - } - - // Execute the query - $val = $this->selectField($sql, 'val'); - - if ($val === 'f') { - return -1; - } elseif ($val === 't') { - return 0; - } else { - return -1; - } - - } - - // Misc functions - - /** - * Sets the comment for an object in the database - * @pre All parameters must already be cleaned - * @param $obj_type One of 'TABLE' | 'COLUMN' | 'VIEW' | 'SCHEMA' | 'SEQUENCE' | 'TYPE' | 'FUNCTION' | 'AGGREGATE' - * @param $obj_name The name of the object for which to attach a comment. - * @param $table Name of table that $obj_name belongs to. Ignored unless $obj_type is 'TABLE' or 'COLUMN'. - * @param $comment The comment to add. - * @return 0 success - */ - function setComment($obj_type, $obj_name, $table, $comment, $basetype = NULL) { - $sql = "COMMENT ON {$obj_type} "; - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->clean($comment); // Passing in an already cleaned comment will lead to double escaped data - // So, while counter-intuitive, it is important to not clean comments before - // calling setComment. We will clean it here instead. - /* - $this->fieldClean($table); - $this->fieldClean($obj_name); -*/ - - switch ($obj_type) { - case 'TABLE': - $sql .= "\"{$f_schema}\".\"{$table}\" IS "; - break; - case 'COLUMN': - $sql .= "\"{$f_schema}\".\"{$table}\".\"{$obj_name}\" IS "; - break; - case 'SEQUENCE': - case 'VIEW': - case 'TEXT SEARCH CONFIGURATION': - case 'TEXT SEARCH DICTIONARY': - case 'TEXT SEARCH TEMPLATE': - case 'TEXT SEARCH PARSER': - case 'TYPE': - $sql .= "\"{$f_schema}\"."; - case 'DATABASE': - case 'ROLE': - case 'SCHEMA': - case 'TABLESPACE': - $sql .= "\"{$obj_name}\" IS "; - break; - case 'FUNCTION': - $sql .= "\"{$f_schema}\".{$obj_name} IS "; - break; - case 'AGGREGATE': - $sql .= "\"{$f_schema}\".\"{$obj_name}\" (\"{$basetype}\") IS "; - break; - default: - // Unknown object type - return -1; - } - - if ($comment != '') { - $sql .= "'{$comment}';"; - } else { - $sql .= 'NULL;'; - } - - return $this->execute($sql); - - } - - /** - * A private helper method for executeScript that advances the - * character by 1. In psql this is careful to take into account - * multibyte languages, but we don't at the moment, so this function - * is someone redundant, since it will always advance by 1 - * @param &$i The current character position in the line - * @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; - $thislen = 1; - } - - /** - * 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 (preg_match('/^[$][$]/', $dquote) || preg_match('/^[$][_[: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 - * the PostgreSQL source code. - * XXX: It does not handle multibyte languages properly. - * @param $name Entry in $_FILES to use - * @param $callback (optional) Callback function to call with each query, - its result and line number. - * @return True for general success, false on any failure. - */ - function executeScript($name, $callback = null) { - - // This whole function isn't very encapsulated, but hey... - $conn = $this->conn->_connectionID; - if (!is_uploaded_file($_FILES[$name]['tmp_name'])) { - return false; - } - - $fd = fopen($_FILES[$name]['tmp_name'], 'r'); - if (!$fd) { - return false; - } - - // Build up each SQL statement, they can be multiline - $query_buf = null; - $query_start = 0; - $in_quote = 0; - $in_xcomment = 0; - $bslash_count = 0; - $dol_quote = null; - $paren_level = 0; - $len = 0; - $i = 0; - $prevlen = 0; - $thislen = 0; - $lineno = 0; - - // Loop over each line in the file - while (!feof($fd)) { - $line = fgets($fd); - $lineno++; - - // Nothing left on line? Then ignore... - if (trim($line) == '') { - continue; - } - - $len = strlen($line); - $query_start = 0; - - /* - * Parse line, looking for command separators. - * - * The current character is at line[i], the prior character at line[i - * - prevlen], the next character at line[i + thislen]. - */ - $prevlen = 0; - $thislen = ($len > 0) ? 1 : 0; - - for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) { - - /* was the previous character a backslash? */ - if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') { - $bslash_count++; - } else { - $bslash_count = 0; - } - - /* - * It is important to place the in_* test routines before the - * in_* detection routines. i.e. we have to test if we are in - * a quote before testing for comments. - */ - - /* in quote? */ - if ($in_quote !== 0) { - /* - * end of quote if matching non-backslashed character. - * backslashes don't count for double quotes, though. - */ - if (substr($line, $i, 1) == $in_quote && - ($bslash_count % 2 == 0 || $in_quote == '"')) { - $in_quote = 0; - } - - } - - /* in or end of $foo$ type quote? */ - else if ($dol_quote) { - if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) { - $this->advance_1($i, $prevlen, $thislen); - while (substr($line, $i, 1) != '$') { - $this->advance_1($i, $prevlen, $thislen); - } - - $dol_quote = null; - } - } - - /* start of extended comment? */ - else if (substr($line, $i, 2) == '/*') { - $in_xcomment++; - if ($in_xcomment == 1) { - $this->advance_1($i, $prevlen, $thislen); - } - - } - - /* in or end of extended comment? */ - else if ($in_xcomment) { - if (substr($line, $i, 2) == '*/' && !--$in_xcomment) { - $this->advance_1($i, $prevlen, $thislen); - } - - } - - /* start of quote? */ - else if (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') { - $in_quote = substr($line, $i, 1); - } - - /* - * 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); - $this->advance_1($i, $prevlen, $thislen); - while (substr($line, $i, 1) != '$') { - $this->advance_1($i, $prevlen, $thislen); - } - - } - - /* single-line comment? truncate line */ - else if (substr($line, $i, 2) == '--') { - $line = substr($line, 0, $i); /* remove comment */ - break; - } - - /* count nested parentheses */ - else if (substr($line, $i, 1) == '(') { - $paren_level++; - } else if (substr($line, $i, 1) == ')' && $paren_level > 0) { - $paren_level--; - } - - /* semicolon? then send query */ - else if (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) { - $subline = substr(substr($line, 0, $i), $query_start); - /* is there anything else on the line? */ - if (strspn($subline, " \t\n\r") != strlen($subline)) { - /* - * 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 */ - $query_buf .= $subline; - $query_buf .= ';'; - - // Execute the query. PHP cannot execute - // empty queries, unlike libpq - $res = @pg_query($conn, $query_buf); - - // Call the callback function for display - if ($callback !== null) { - $callback($query_buf, $res, $lineno); - } - - // Check for COPY request - if (pg_result_status($res) == 4) { - // 4 == PGSQL_COPY_FROM - while (!feof($fd)) { - $copy = fgets($fd, 32768); - $lineno++; - pg_put_line($conn, $copy); - if ($copy == "\\.\n" || $copy == "\\.\r\n") { - pg_end_copy($conn); - break; - } - } - } - } - - $query_buf = null; - $query_start = $i + $thislen; - } - - /* - * keyword or identifier? - * 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 (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) { - $sub = substr($line, $i, $thislen); - while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) { - /* keep going while we still have identifier chars */ - $this->advance_1($i, $prevlen, $thislen); - $sub = substr($line, $i, $thislen); - } - // 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. */ - $subline = substr($line, $query_start); - if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) { - if (strlen($query_buf) > 0) { - $query_buf .= "\n"; - } - - $query_buf .= $subline; - } - - $line = null; - - } // end while - - /* - * 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 - $res = @pg_query($conn, $query_buf); - - // Call the callback function for display - if ($callback !== null) { - $callback($query_buf, $res, $lineno); - } - - // Check for COPY request - if (pg_result_status($res) == 4) { - // 4 == PGSQL_COPY_FROM - while (!feof($fd)) { - $copy = fgets($fd, 32768); - $lineno++; - pg_put_line($conn, $copy); - 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 = []) { - $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 "; - } - - $this->fieldClean($table); - - if (isset($_REQUEST['schema'])) { - $f_schema = $_REQUEST['schema']; - $this->fieldClean($f_schema); - $sql .= "\"{$f_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 (preg_match('/^[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 (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) { - $orderby = [$sortkey => $sortdir]; - } else { - $orderby = []; - } - - $query = $this->getSelectSQL($table, [], [], [], $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' && preg_match('/^[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 - $rs = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} 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) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - - $sql = "SELECT * FROM \"{$f_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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $sql = "SELECT * FROM pg_stat_all_tables + return $this->selectSet($sql); + } + + /** + * 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 + */ + public function sendSignal($pid, $signal) + { + // Clean + $pid = (int) $pid; + + if ($signal == 'CANCEL') { + $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val"; + } elseif ($signal == 'KILL') { + $sql = "SELECT pg_catalog.pg_terminate_backend({$pid}) AS val"; + } else { + return -1; + } + + // Execute the query + $val = $this->selectField($sql, 'val'); + + if ($val === 'f') { + return -1; + } elseif ($val === 't') { + return 0; + } else { + return -1; + } + + } + + // Misc functions + + /** + * Sets the comment for an object in the database + * @pre All parameters must already be cleaned + * @param $obj_type One of 'TABLE' | 'COLUMN' | 'VIEW' | 'SCHEMA' | 'SEQUENCE' | 'TYPE' | 'FUNCTION' | 'AGGREGATE' + * @param $obj_name The name of the object for which to attach a comment. + * @param $table Name of table that $obj_name belongs to. Ignored unless $obj_type is 'TABLE' or 'COLUMN'. + * @param $comment The comment to add. + * @return 0 success + */ + public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null) + { + $sql = "COMMENT ON {$obj_type} "; + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->clean($comment); // Passing in an already cleaned comment will lead to double escaped data + // So, while counter-intuitive, it is important to not clean comments before + // calling setComment. We will clean it here instead. + /* + $this->fieldClean($table); + $this->fieldClean($obj_name); + */ + + switch ($obj_type) { + case 'TABLE': + $sql .= "\"{$f_schema}\".\"{$table}\" IS "; + break; + case 'COLUMN': + $sql .= "\"{$f_schema}\".\"{$table}\".\"{$obj_name}\" IS "; + break; + case 'SEQUENCE': + case 'VIEW': + case 'TEXT SEARCH CONFIGURATION': + case 'TEXT SEARCH DICTIONARY': + case 'TEXT SEARCH TEMPLATE': + case 'TEXT SEARCH PARSER': + case 'TYPE': + $sql .= "\"{$f_schema}\"."; + case 'DATABASE': + case 'ROLE': + case 'SCHEMA': + case 'TABLESPACE': + $sql .= "\"{$obj_name}\" IS "; + break; + case 'FUNCTION': + $sql .= "\"{$f_schema}\".{$obj_name} IS "; + break; + case 'AGGREGATE': + $sql .= "\"{$f_schema}\".\"{$obj_name}\" (\"{$basetype}\") IS "; + break; + default: + // Unknown object type + return -1; + } + + if ($comment != '') { + $sql .= "'{$comment}';"; + } else { + $sql .= 'NULL;'; + } + + return $this->execute($sql); + + } + + /** + * A private helper method for executeScript that advances the + * character by 1. In psql this is careful to take into account + * multibyte languages, but we don't at the moment, so this function + * is someone redundant, since it will always advance by 1 + * @param &$i The current character position in the line + * @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; + $thislen = 1; + } + + /** + * 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 (preg_match('/^[$][$]/', $dquote) || preg_match('/^[$][_[: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 + * the PostgreSQL source code. + * XXX: It does not handle multibyte languages properly. + * @param $name Entry in $_FILES to use + * @param $callback (optional) Callback function to call with each query, + its result and line number. + * @return True for general success, false on any failure. + */ + public function executeScript($name, $callback = null) + { + + // This whole function isn't very encapsulated, but hey... + $conn = $this->conn->_connectionID; + if (!is_uploaded_file($_FILES[$name]['tmp_name'])) { + return false; + } + + $fd = fopen($_FILES[$name]['tmp_name'], 'r'); + if (!$fd) { + return false; + } + + // Build up each SQL statement, they can be multiline + $query_buf = null; + $query_start = 0; + $in_quote = 0; + $in_xcomment = 0; + $bslash_count = 0; + $dol_quote = null; + $paren_level = 0; + $len = 0; + $i = 0; + $prevlen = 0; + $thislen = 0; + $lineno = 0; + + // Loop over each line in the file + while (!feof($fd)) { + $line = fgets($fd); + $lineno++; + + // Nothing left on line? Then ignore... + if (trim($line) == '') { + continue; + } + + $len = strlen($line); + $query_start = 0; + + /* + * Parse line, looking for command separators. + * + * The current character is at line[i], the prior character at line[i + * - prevlen], the next character at line[i + thislen]. + */ + $prevlen = 0; + $thislen = ($len > 0) ? 1 : 0; + + for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) { + + /* was the previous character a backslash? */ + if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') { + $bslash_count++; + } else { + $bslash_count = 0; + } + + /* + * It is important to place the in_* test routines before the + * in_* detection routines. i.e. we have to test if we are in + * a quote before testing for comments. + */ + + /* in quote? */ + if ($in_quote !== 0) { + /* + * end of quote if matching non-backslashed character. + * backslashes don't count for double quotes, though. + */ + if (substr($line, $i, 1) == $in_quote && + ($bslash_count % 2 == 0 || $in_quote == '"')) { + $in_quote = 0; + } + + } + + /* in or end of $foo$ type quote? */ + else if ($dol_quote) { + if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) { + $this->advance_1($i, $prevlen, $thislen); + while (substr($line, $i, 1) != '$') { + $this->advance_1($i, $prevlen, $thislen); + } + + $dol_quote = null; + } + } + + /* start of extended comment? */ + else if (substr($line, $i, 2) == '/*') { + $in_xcomment++; + if ($in_xcomment == 1) { + $this->advance_1($i, $prevlen, $thislen); + } + + } + + /* in or end of extended comment? */ + else if ($in_xcomment) { + if (substr($line, $i, 2) == '*/' && !--$in_xcomment) { + $this->advance_1($i, $prevlen, $thislen); + } + + } + + /* start of quote? */ + else if (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') { + $in_quote = substr($line, $i, 1); + } + + /* + * 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); + $this->advance_1($i, $prevlen, $thislen); + while (substr($line, $i, 1) != '$') { + $this->advance_1($i, $prevlen, $thislen); + } + + } + + /* single-line comment? truncate line */ + else if (substr($line, $i, 2) == '--') { + $line = substr($line, 0, $i); /* remove comment */ + break; + } + + /* count nested parentheses */ + else if (substr($line, $i, 1) == '(') { + $paren_level++; + } else if (substr($line, $i, 1) == ')' && $paren_level > 0) { + $paren_level--; + } + + /* semicolon? then send query */ + else if (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) { + $subline = substr(substr($line, 0, $i), $query_start); + /* is there anything else on the line? */ + if (strspn($subline, " \t\n\r") != strlen($subline)) { + /* + * 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 */ + $query_buf .= $subline; + $query_buf .= ';'; + + // Execute the query. PHP cannot execute + // empty queries, unlike libpq + $res = @pg_query($conn, $query_buf); + + // Call the callback function for display + if ($callback !== null) { + $callback($query_buf, $res, $lineno); + } + + // Check for COPY request + if (pg_result_status($res) == 4) { + // 4 == PGSQL_COPY_FROM + while (!feof($fd)) { + $copy = fgets($fd, 32768); + $lineno++; + pg_put_line($conn, $copy); + if ($copy == "\\.\n" || $copy == "\\.\r\n") { + pg_end_copy($conn); + break; + } + } + } + } + + $query_buf = null; + $query_start = $i + $thislen; + } + + /* + * keyword or identifier? + * 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 (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) { + $sub = substr($line, $i, $thislen); + while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) { + /* keep going while we still have identifier chars */ + $this->advance_1($i, $prevlen, $thislen); + $sub = substr($line, $i, $thislen); + } + // 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. */ + $subline = substr($line, $query_start); + if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) { + if (strlen($query_buf) > 0) { + $query_buf .= "\n"; + } + + $query_buf .= $subline; + } + + $line = null; + + } // end while + + /* + * 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 + $res = @pg_query($conn, $query_buf); + + // Call the callback function for display + if ($callback !== null) { + $callback($query_buf, $res, $lineno); + } + + // Check for COPY request + if (pg_result_status($res) == 4) { + // 4 == PGSQL_COPY_FROM + while (!feof($fd)) { + $copy = fgets($fd, 32768); + $lineno++; + pg_put_line($conn, $copy); + 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 + */ + public function getSelectSQL($table, $show, $values, $ops, $orderby = []) + { + $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 "; + } + + $this->fieldClean($table); + + if (isset($_REQUEST['schema'])) { + $f_schema = $_REQUEST['schema']; + $this->fieldClean($f_schema); + $sql .= "\"{$f_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 (preg_match('/^[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 + */ + public 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 (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) { + $orderby = [$sortkey => $sortdir]; + } else { + $orderby = []; + } + + $query = $this->getSelectSQL($table, [], [], [], $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' && preg_match('/^[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 + $rs = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} 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 + */ + public 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 + */ + public function browseRow($table, $key) + { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + + $sql = "SELECT * FROM \"{$f_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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public function getStatsTableTuples($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + + $sql = "SELECT * FROM pg_stat_all_tables WHERE schemaname='{$c_schema}' AND relname='{$table}'"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } + + /** + * Fetches I/0 statistics for a table + * @param $table The table to fetch stats for + * @return A recordset + */ + public function getStatsTableIO($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + + $sql = "SELECT * FROM pg_statio_all_tables + WHERE schemaname='{$c_schema}' AND relname='{$table}'"; - /** - * Fetches I/0 statistics for a table - * @param $table The table to fetch stats for - * @return A recordset - */ - function getStatsTableIO($table) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($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 + */ + public function getStatsIndexTuples($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + + $sql = "SELECT * FROM pg_stat_all_indexes + WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname"; - $sql = "SELECT * FROM pg_statio_all_tables - WHERE schemaname='{$c_schema}' AND relname='{$table}'"; + 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 + */ + public function getStatsIndexIO($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + + $sql = "SELECT * FROM pg_statio_all_indexes + WHERE schemaname='{$c_schema}' AND relname='{$table}' + ORDER BY indexrelname"; - return $this->selectSet($sql); - } + 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); + // Capabilities - $sql = "SELECT * FROM pg_stat_all_indexes - WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname"; + public function hasAggregateSortOp() + {return true;} - return $this->selectSet($sql); - } + public function hasAlterAggregate() + {return true;} - /** - * 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); + public function hasAlterColumnType() + {return true;} - $sql = "SELECT * FROM pg_statio_all_indexes - WHERE schemaname='{$c_schema}' AND relname='{$table}' - ORDER BY indexrelname"; + public function hasAlterDatabaseOwner() + {return true;} + + public function hasAlterDatabaseRename() + {return true;} + + public function hasAlterSchema() + {return true;} + + public function hasAlterSchemaOwner() + {return true;} + + public function hasAlterSequenceSchema() + {return true;} + + public function hasAlterSequenceStart() + {return true;} + + public function hasAlterTableSchema() + {return true;} + + public function hasAutovacuum() + {return true;} + + public function hasCreateTableLike() + {return true;} + + public function hasCreateTableLikeWithConstraints() + {return true;} + + public function hasCreateTableLikeWithIndexes() + {return true;} + + public function hasCreateFieldWithConstraints() + {return true;} + + public function hasDisableTriggers() + {return true;} + + public function hasAlterDomains() + {return true;} + + public function hasDomainConstraints() + {return true;} + + public function hasEnumTypes() + {return true;} + + public function hasFTS() + {return true;} + + public function hasFunctionAlterOwner() + {return true;} + + public function hasFunctionAlterSchema() + {return true;} + + public function hasFunctionCosting() + {return true;} + + public function hasFunctionGUC() + {return true;} + + public function hasGrantOption() + {return true;} + + public function hasNamedParams() + {return true;} + + public function hasPrepare() + {return true;} + + public function hasPreparedXacts() + {return true;} + + public function hasReadOnlyQueries() + {return true;} + + public function hasRecluster() + {return true;} + + public function hasRoles() + {return true;} + + public function hasServerAdminFuncs() + {return true;} + + public function hasSharedComments() + {return true;} + + public function hasQueryCancel() + {return true;} + + public function hasTablespaces() + {return true;} + + public function hasUserRename() + {return true;} + + public function hasUserSignals() + {return true;} + + public function hasVirtualTransactionId() + {return true;} + + public function hasAlterDatabase() + {return $this->hasAlterDatabaseRename();} + + public function hasDatabaseCollation() + {return true;} + + public function hasMagicTypes() + {return true;} + + public function hasQueryKill() + {return true;} + + public function hasConcurrentIndexBuild() + {return true;} + + public function hasForceReindex() + {return false;} - return $this->selectSet($sql); - } - - // Capabilities - - function hasAggregateSortOp() {return true;} - function hasAlterAggregate() {return true;} - function hasAlterColumnType() {return true;} - function hasAlterDatabaseOwner() {return true;} - function hasAlterDatabaseRename() {return true;} - function hasAlterSchema() {return true;} - function hasAlterSchemaOwner() {return true;} - function hasAlterSequenceSchema() {return true;} - function hasAlterSequenceStart() {return true;} - function hasAlterTableSchema() {return true;} - function hasAutovacuum() {return true;} - function hasCreateTableLike() {return true;} - function hasCreateTableLikeWithConstraints() {return true;} - function hasCreateTableLikeWithIndexes() {return true;} - function hasCreateFieldWithConstraints() {return true;} - function hasDisableTriggers() {return true;} - function hasAlterDomains() {return true;} - function hasDomainConstraints() {return true;} - function hasEnumTypes() {return true;} - function hasFTS() {return true;} - function hasFunctionAlterOwner() {return true;} - function hasFunctionAlterSchema() {return true;} - function hasFunctionCosting() {return true;} - function hasFunctionGUC() {return true;} - function hasGrantOption() {return true;} - function hasNamedParams() {return true;} - function hasPrepare() {return true;} - function hasPreparedXacts() {return true;} - function hasReadOnlyQueries() {return true;} - function hasRecluster() {return true;} - function hasRoles() {return true;} - function hasServerAdminFuncs() {return true;} - function hasSharedComments() {return true;} - function hasQueryCancel() {return true;} - function hasTablespaces() {return true;} - function hasUserRename() {return true;} - function hasUserSignals() {return true;} - function hasVirtualTransactionId() {return true;} - function hasAlterDatabase() {return $this->hasAlterDatabaseRename();} - function hasDatabaseCollation() {return true;} - function hasMagicTypes() {return true;} - function hasQueryKill() {return true;} - function hasConcurrentIndexBuild() {return true;} - function hasForceReindex() {return false;} - function hasByteaHexDefault() {return true;} + public function hasByteaHexDefault() + {return true;} } diff --git a/src/database/Postgres74.php b/src/database/Postgres74.php index 9effcb75..c54ddbd5 100644 --- a/src/database/Postgres74.php +++ b/src/database/Postgres74.php @@ -1,5 +1,6 @@ <?php namespace PHPPgAdmin\Database; + /** * A class that implements the DB interface for Postgres * Note: This class uses ADODB and returns RecordSets. @@ -7,86 +8,82 @@ namespace PHPPgAdmin\Database; * $Id: Postgres74.php,v 1.72 2008/02/20 21:06:18 ioguix Exp $ */ -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 = [ - 'table' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], - 'view' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], - 'sequence' => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'], - 'database' => ['CREATE', 'TEMPORARY', 'ALL PRIVILEGES'], - 'function' => ['EXECUTE', 'ALL PRIVILEGES'], - 'language' => ['USAGE', 'ALL PRIVILEGES'], - 'schema' => ['CREATE', 'USAGE', 'ALL PRIVILEGES'], - ]; - - // Help functions - - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc74.php'; - return $this->help_page; - } - - // Database 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 - * @return 0 success - * @return -1 transaction error - * @return -2 owner error - * @return -3 rename error - */ - function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') { - //ignore $newowner, not supported pre 8.0 - //ignore $comment, not supported pre 8.2 - $this->clean($dbName); - $this->clean($newName); - - $status = $this->alterDatabaseRename($dbName, $newName); - if ($status != 0) { - return -3; - } else { - return 0; - } - - } - - /** - * Return all database available on the server - * @return A list of databases, sorted alphabetically - */ - function getDatabases($currentdatabase = NULL) { - $conf = $this->conf; - $server_info = $this->server_info; - - if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { - $username = $server_info['username']; - $this->clean($username); - $clause = " AND pu.usename='{$username}'"; - } else { - $clause = ''; - } - - if ($currentdatabase != NULL) { - $this->clean($currentdatabase); - $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, +class Postgres74 extends Postgres80 +{ + + public $major_version = 7.4; + // List of all legal privileges that can be applied to different types + // of objects. + public $privlist = [ + 'table' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], + 'view' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], + 'sequence' => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'], + 'database' => ['CREATE', 'TEMPORARY', 'ALL PRIVILEGES'], + 'function' => ['EXECUTE', 'ALL PRIVILEGES'], + 'language' => ['USAGE', 'ALL PRIVILEGES'], + 'schema' => ['CREATE', 'USAGE', 'ALL PRIVILEGES'], + ]; + + // Database 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 + * @return 0 success + * @return -1 transaction error + * @return -2 owner error + * @return -3 rename error + */ + public function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') + { + //ignore $newowner, not supported pre 8.0 + //ignore $comment, not supported pre 8.2 + $this->clean($dbName); + $this->clean($newName); + + $status = $this->alterDatabaseRename($dbName, $newName); + if ($status != 0) { + return -3; + } else { + return 0; + } + + } + + /** + * Return all database available on the server + * @return A list of databases, sorted alphabetically + */ + public function getDatabases($currentdatabase = null) + { + $conf = $this->conf; + $server_info = $this->server_info; + + if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { + $username = $server_info['username']; + $this->clean($username); + $clause = " AND pu.usename='{$username}'"; + } else { + $clause = ''; + } + + if ($currentdatabase != null) { + $this->clean($currentdatabase); + $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 @@ -94,50 +91,51 @@ class Postgres74 extends Postgres80 { {$clause} {$orderby}"; - return $this->selectSet($sql); - } - - /** - * 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) { - $conf = $this->conf; - - /*about escaping: - * SET standard_conforming_string is not available before 8.2 - * So we must use PostgreSQL specific notation :/ - * E'' notation is not available before 8.1 - * $$ is available since 8.0 - * Nothing specific from 7.4 - */ - - // 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 .= " + return $this->selectSet($sql); + } + + /** + * 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 + */ + public function findObject($term, $filter) + { + $conf = $this->conf; + + /*about escaping: + * SET standard_conforming_string is not available before 8.2 + * So we must use PostgreSQL specific notation :/ + * E'' notation is not available before 8.1 + * $$ is available since 8.0 + * Nothing specific from 7.4 + */ + + // 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 @@ -194,9 +192,9 @@ class Postgres74 extends Postgres80 { 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 .= " + // 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 @@ -221,10 +219,10 @@ class Postgres74 extends Postgres80 { pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid AND po.opcname ILIKE '%{$term}%' {$where} "; - } - // Otherwise just add domains - else { - $sql .= " + } + // 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 @@ -232,178 +230,181 @@ class Postgres74 extends Postgres80 { 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}%' "; - } + 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"; + $sql .= "ORDER BY type, schemaname, relname, name"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - /** - * Returns table locks information in the current database - * @return A recordset - */ - function getLocks() { - $conf = $this->conf; + /** + * Returns table locks information in the current database + * @return A recordset + */ + public function getLocks() + { + $conf = $this->conf; - if (!$conf['show_system']) { - $where = "AND pn.nspname NOT LIKE 'pg\\\\_%'"; - } else { - $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; - } + 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.transaction, pl.pid, pl.mode, pl.granted + $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); - } - - /** - * Returns the current database encoding - * @return The encoding. eg. SQL_ASCII, UTF-8, etc. - */ - function getDatabaseEncoding() { - $sql = "SELECT getdatabaseencoding() AS encoding"; - - return $this->selectField($sql, 'encoding'); - } - - // 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 - */ - protected - function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) { - - /* $schema and tablespace not supported in pg74- */ - $this->fieldArrayClean($tblrs->fields); - - // 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; - } - - // Rename - $this->fieldClean($name); - $status = $this->alterTableName($tblrs, $name); - if ($status != 0) { - return -3; - } - - return 0; - } - - /** - * 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 -2 set not null error - * @return -3 set default error - * @return -4 rename column error - * @return -5 comment error - * @return -6 transaction error - */ - function alterColumn($table, $column, $name, $notnull, $oldnotnull, $default, $olddefault, - $type, $length, $array, $oldtype, $comment) { - $status = $this->beginTransaction(); - if ($status != 0) { - return -1; - } - - // @@ NEED TO HANDLE "NESTED" TRANSACTION HERE - if ($notnull != $oldnotnull) { - $status = $this->setColumnNull($table, $column, !$notnull); - if ($status != 0) { - $this->rollbackTransaction(); - return -2; - } - } - - // Set default, if it has changed - if ($default != $olddefault) { - if ($default == '') { - $status = $this->dropColumnDefault($table, $column); - } else { - $status = $this->setColumnDefault($table, $column, $default); - } - - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - } - - // Rename the column, if it has been changed - if ($column != $name) { - $status = $this->renameColumn($table, $column, $name); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - } - - // The $name and $table 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); - $status = $this->setComment('COLUMN', $name, $table, $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -5; - } - - return $this->endTransaction(); - } - - /** - * Returns table information - * @param $table The name of the table - * @return A recordset - */ - function getTable($table) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $sql = " + return $this->selectSet($sql); + } + + /** + * Returns the current database encoding + * @return The encoding. eg. SQL_ASCII, UTF-8, etc. + */ + public function getDatabaseEncoding() + { + $sql = "SELECT getdatabaseencoding() AS encoding"; + + return $this->selectField($sql, 'encoding'); + } + + // 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 + */ + protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) + { + + /* $schema and tablespace not supported in pg74- */ + $this->fieldArrayClean($tblrs->fields); + + // 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; + } + + // Rename + $this->fieldClean($name); + $status = $this->alterTableName($tblrs, $name); + if ($status != 0) { + return -3; + } + + return 0; + } + + /** + * 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 -2 set not null error + * @return -3 set default error + * @return -4 rename column error + * @return -5 comment error + * @return -6 transaction error + */ + public function alterColumn($table, $column, $name, $notnull, $oldnotnull, $default, $olddefault, + $type, $length, $array, $oldtype, $comment) { + $status = $this->beginTransaction(); + if ($status != 0) { + return -1; + } + + // @@ NEED TO HANDLE "NESTED" TRANSACTION HERE + if ($notnull != $oldnotnull) { + $status = $this->setColumnNull($table, $column, !$notnull); + if ($status != 0) { + $this->rollbackTransaction(); + return -2; + } + } + + // Set default, if it has changed + if ($default != $olddefault) { + if ($default == '') { + $status = $this->dropColumnDefault($table, $column); + } else { + $status = $this->setColumnDefault($table, $column, $default); + } + + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } + } + + // Rename the column, if it has been changed + if ($column != $name) { + $status = $this->renameColumn($table, $column, $name); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; + } + } + + // The $name and $table 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); + $status = $this->setComment('COLUMN', $name, $table, $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -5; + } + + return $this->endTransaction(); + } + + /** + * Returns table information + * @param $table The name of the table + * @return A recordset + */ + public function getTable($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + + $sql = " SELECT c.relname, n.nspname, u.usename AS relowner, pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment @@ -414,25 +415,26 @@ class Postgres74 extends Postgres80 { AND n.nspname = '{$c_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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - if ($all) { - // Exclude pg_catalog and information_schema tables - $sql = "SELECT schemaname AS nspname, tablename AS relname, tableowner AS relowner + 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 + */ + public function getTables($all = false) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + 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, + } 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 @@ -440,38 +442,40 @@ class Postgres74 extends Postgres80 { WHERE c.relkind = 'r' AND nspname='{$c_schema}' ORDER BY c.relname"; - } - - return $this->selectSet($sql); - } - - /** - * Returns the current default_with_oids setting - * @return default_with_oids setting - */ - function getDefaultWithOid() { - // 8.0 is the first release to have this setting - // Prior releases don't have this setting... oids always activated - return 'on'; - } - - // Constraint 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 - */ - function getConstraintsWithFields($table) { - - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - // get the max number of col used in a constraint for the table - $sql = "SELECT DISTINCT + } + + return $this->selectSet($sql); + } + + /** + * Returns the current default_with_oids setting + * @return default_with_oids setting + */ + public function getDefaultWithOid() + { + // 8.0 is the first release to have this setting + // Prior releases don't have this setting... oids always activated + return 'on'; + } + + // Constraint 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 + */ + public function getConstraintsWithFields($table) + { + + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->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) @@ -479,15 +483,15 @@ class Postgres74 extends Postgres80 { WHERE r.relname = '{$table}' AND ns.nspname='{$c_schema}'"; - $rs = $this->selectSet($sql); + $rs = $this->selectSet($sql); - if ($rs->EOF) { - $max_col = 0; - } else { - $max_col = $rs->fields['nb']; - } + if ($rs->EOF) { + $max_col = 0; + } else { + $max_col = $rs->fields['nb']; + } - $sql = ' + $sql = ' SELECT c.oid AS conid, 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, @@ -498,66 +502,68 @@ class Postgres74 extends Postgres80 { 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 .= ')) + 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)"; - } + for ($i = 2; $i <= $rs->fields['nb']; $i++) { + $sql .= " OR (c.confkey[$i]=f2.attnum AND c.conkey[$i]=f1.attnum)"; + } - $sql .= sprintf(")) + $sql .= sprintf(")) WHERE r1.relname = '%s' AND ns1.nspname='%s' ORDER BY 1", $table, $c_schema); - return $this->selectSet($sql); - } - - // Sequence functions - - /** - * Returns all sequences in the current database - * @return A recordset - */ - function getSequences($all = false) { - $c_schema = $this->_schema; - $this->clean($c_schema); - if ($all) { - // Exclude pg_catalog and information_schema tables - $sql = "SELECT n.nspname, c.relname AS seqname, u.usename AS seqowner + return $this->selectSet($sql); + } + + // Sequence functions + + /** + * Returns all sequences in the current database + * @return A recordset + */ + public function getSequences($all = false) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + 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 + } 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='{$c_schema}' ORDER BY seqname"; - } + } - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Function functions + // 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); + /** + * Returns all details for a particular function + * @param $func The name of the function to retrieve + * @return Function info + */ + public function getFunction($function_oid) + { + $this->clean($function_oid); - $sql = " + $sql = " SELECT pc.oid AS prooid, proname, @@ -581,27 +587,28 @@ class Postgres74 extends Postgres80 { AND n.oid = pc.pronamespace "; - return $this->selectSet($sql); - } - - /** - * Returns a list of all casts in the database - * @return All casts - */ - function getCasts() { - $conf = $this->conf; - - if ($conf['show_system']) { - $where = ''; - } else { - $where = " + return $this->selectSet($sql); + } + + /** + * Returns a list of all casts in the database + * @return All casts + */ + public function getCasts() + { + $conf = $this->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 = " + $sql = " SELECT c.castsource::pg_catalog.regtype AS castsource, c.casttarget::pg_catalog.regtype AS casttarget, @@ -624,18 +631,35 @@ class Postgres74 extends Postgres80 { ORDER BY 1, 2 "; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } + + // Capabilities + + public function hasAlterColumnType() + {return false;} + + public function hasCreateFieldWithConstraints() + {return false;} + + public function hasAlterDatabaseOwner() + {return false;} + + public function hasAlterSchemaOwner() + {return false;} + + public function hasFunctionAlterOwner() + {return false;} + + public function hasNamedParams() + {return false;} + + public function hasQueryCancel() + {return false;} - // Capabilities + public function hasTablespaces() + {return false;} - function hasAlterColumnType() {return false;} - function hasCreateFieldWithConstraints() {return false;} - function hasAlterDatabaseOwner() {return false;} - function hasAlterSchemaOwner() {return false;} - function hasFunctionAlterOwner() {return false;} - function hasNamedParams() {return false;} - function hasQueryCancel() {return false;} - function hasTablespaces() {return false;} - function hasMagicTypes() {return false;} + public function hasMagicTypes() + {return false;} } diff --git a/src/database/Postgres80.php b/src/database/Postgres80.php index 4bf4df20..3b1ff57c 100644 --- a/src/database/Postgres80.php +++ b/src/database/Postgres80.php @@ -1,88 +1,84 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 8.0 support * * $Id: Postgres80.php,v 1.28 2007/12/12 04:11:10 xzilla Exp $ */ -class Postgres80 extends Postgres81 { - - var $major_version = 8.0; - // 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 = [ - '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', - ]; - - // Help functions - - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc80.php'; - return $this->help_page; - } - - // Database functions - - /** - * Return all database available on the server - * @return A list of databases, sorted alphabetically - */ - function getDatabases($currentdatabase = NULL) { - $conf = $this->conf; - $server_info = $this->server_info; - - if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { - $username = $server_info['username']; - $this->clean($username); - $clause = " AND pu.usename='{$username}'"; - } else { - $clause = ''; - } - - if ($currentdatabase != NULL) { - $this->clean($currentdatabase); - $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, +class Postgres80 extends Postgres81 +{ + + public $major_version = 8.0; + // 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. + public $codemap = [ + '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', + ]; + + // Database functions + + /** + * Return all database available on the server + * @return A list of databases, sorted alphabetically + */ + public function getDatabases($currentdatabase = null) + { + $conf = $this->conf; + $server_info = $this->server_info; + + if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { + $username = $server_info['username']; + $this->clean($username); + $clause = " AND pu.usename='{$username}'"; + } else { + $clause = ''; + } + + if ($currentdatabase != null) { + $this->clean($currentdatabase); + $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, (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace FROM pg_database pdb, pg_user pu @@ -91,25 +87,26 @@ class Postgres80 extends Postgres81 { {$clause} {$orderby}"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Schema functions + // Schema functions - /** - * Return all schemas in the current database. - * @return All schemas, sorted alphabetically - */ - function getSchemas() { - $conf = $this->conf; + /** + * Return all schemas in the current database. + * @return All schemas, sorted alphabetically + */ + public function getSchemas() + { + $conf = $this->conf; - if (!$conf['show_system']) { - $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'"; - } else { - $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; - } + if (!$conf['show_system']) { + $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'"; + } else { + $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; + } - $sql = " + $sql = " SELECT pn.nspname, pu.usename AS nspowner, pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment FROM pg_catalog.pg_namespace pn @@ -117,220 +114,222 @@ class Postgres80 extends Postgres81 { {$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 = " + return $this->selectSet($sql); + } + + /** + * Return all information relating to a schema + * @param $schema The name of the schema + * @return Schema information + */ + public function getSchemaByName($schema) + { + $this->clean($schema); + $sql = " SELECT nspname, nspowner, u.usename AS ownername, nspacl, pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment FROM pg_catalog.pg_namespace pn LEFT JOIN pg_shadow as u ON pn.nspowner = u.usesysid WHERE nspname='{$schema}'"; - 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 - */ - protected - function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) { - - /* $schema not supported in pg80- */ - - // 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; - } - - return 0; - } - - // 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 - */ - protected - function _alterView($vwrs, $name, $owner, $schema, $comment) { - - /* $schema not supported in pg80- */ - $this->fieldArrayClean($vwrs->fields); - - // 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; - } - - 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 $restartvalue The starting value - * @param $cachevalue The cache value - * @param $cycledvalue True if cycled, false otherwise - * @param $startvalue The sequence start value when issueing a restart - * @return 0 success - * @return -3 rename error - * @return -4 comment error - * @return -5 owner error - * @return -6 get sequence props error - * @return -7 schema error - */ - protected - function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, - $minvalue, $maxvalue, $restartvalue, $cachevalue, $cycledvalue, $startvalue) { - - /* $schema not supported in pg80- */ - $this->fieldArrayClean($seqrs->fields); - - // Comment - $status = $this->setComment('SEQUENCE', $seqrs->fields['seqname'], '', $comment); - if ($status != 0) { - return -4; - } - - // 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($restartvalue); - $this->clean($cachevalue); - $this->clean($cycledvalue); - $this->clean($startvalue); - $status = $this->alterSequenceProps($seqrs, $increment, $minvalue, - $maxvalue, $restartvalue, $cachevalue, $cycledvalue, null); - if ($status != 0) { - return -6; - } - - // Rename - $this->fieldClean($name); - $status = $this->alterSequenceName($seqrs, $name); - if ($status != 0) { - return -3; - } - - return 0; - } - - // Role, User/group functions - - /** - * 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); - } - - // 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) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($name); - $this->clean($basetype); - - $sql = " + 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 + */ + protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) + { + + /* $schema not supported in pg80- */ + + // 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; + } + + return 0; + } + + // 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 + */ + protected function _alterView($vwrs, $name, $owner, $schema, $comment) + { + + /* $schema not supported in pg80- */ + $this->fieldArrayClean($vwrs->fields); + + // 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; + } + + 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 $restartvalue The starting value + * @param $cachevalue The cache value + * @param $cycledvalue True if cycled, false otherwise + * @param $startvalue The sequence start value when issueing a restart + * @return 0 success + * @return -3 rename error + * @return -4 comment error + * @return -5 owner error + * @return -6 get sequence props error + * @return -7 schema error + */ + protected function _alterSequence($seqrs, $name, $comment, $owner, $schema, $increment, + $minvalue, $maxvalue, $restartvalue, $cachevalue, $cycledvalue, $startvalue) { + + /* $schema not supported in pg80- */ + $this->fieldArrayClean($seqrs->fields); + + // Comment + $status = $this->setComment('SEQUENCE', $seqrs->fields['seqname'], '', $comment); + if ($status != 0) { + return -4; + } + + // 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($restartvalue); + $this->clean($cachevalue); + $this->clean($cycledvalue); + $this->clean($startvalue); + $status = $this->alterSequenceProps($seqrs, $increment, $minvalue, + $maxvalue, $restartvalue, $cachevalue, $cycledvalue, null); + if ($status != 0) { + return -6; + } + + // Rename + $this->fieldClean($name); + $status = $this->alterSequenceName($seqrs, $name); + if ($status != 0) { + return -3; + } + + return 0; + } + + // Role, User/group functions + + /** + * Changes a user's password + * @param $username The username + * @param $password The new password + * @return 0 success + */ + public 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); + } + + // 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 + */ + public function getAggregate($name, $basetype) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($name); + $this->clean($basetype); + + $sql = " SELECT p.proname, CASE p.proargtypes[0] WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL @@ -346,18 +345,35 @@ class Postgres80 extends Postgres81 { ELSE pg_catalog.format_type(p.proargtypes[0], NULL) END ='{$basetype}'"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } + + // Capabilities + + public function hasAggregateSortOp() + {return false;} + + public function hasAlterTableSchema() + {return false;} + + public function hasAutovacuum() + {return false;} + + public function hasDisableTriggers() + {return false;} + + public function hasFunctionAlterSchema() + {return false;} + + public function hasPreparedXacts() + {return false;} + + public function hasRoles() + {return false;} - // Capabilities + public function hasAlterSequenceSchema() + {return false;} - 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 hasAlterSequenceSchema() {return false;} - function hasServerAdminFuncs() {return false;} + public function hasServerAdminFuncs() + {return false;} } diff --git a/src/database/Postgres81.php b/src/database/Postgres81.php index f16a26f6..e4968a3c 100644 --- a/src/database/Postgres81.php +++ b/src/database/Postgres81.php @@ -1,83 +1,79 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 8.1 support * * $Id: Postgres81.php,v 1.21 2008/01/19 13:46:15 ioguix Exp $ */ -class Postgres81 extends Postgres82 { - - var $major_version = 8.1; - // List of all legal privileges that can be applied to different types - // of objects. - var $privlist = [ - 'table' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], - 'view' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], - 'sequence' => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'], - 'database' => ['CREATE', 'TEMPORARY', 'ALL PRIVILEGES'], - 'function' => ['EXECUTE', 'ALL PRIVILEGES'], - 'language' => ['USAGE', 'ALL PRIVILEGES'], - 'schema' => ['CREATE', 'USAGE', 'ALL PRIVILEGES'], - 'tablespace' => ['CREATE', 'ALL PRIVILEGES'], - ]; - // List of characters in acl lists and the privileges they - // refer to. - var $privmap = [ - '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 = ['BTREE', 'RTREE', 'GIST', 'HASH']; - - // Help functions - - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc81.php'; - return $this->help_page; - } - - // Database functions - - /** - * Returns all databases available on the server - * @return A list of databases, sorted alphabetically - */ - function getDatabases($currentdatabase = NULL) { - $conf = $this->conf; - $server_info = $this->server_info; - - if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { - $username = $server_info['username']; - $this->clean($username); - $clause = " AND pr.rolname='{$username}'"; - } else { - $clause = ''; - } - - if ($currentdatabase != NULL) { - $this->clean($currentdatabase); - $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, pr.rolname AS datowner, pg_encoding_to_char(encoding) AS datencoding, +class Postgres81 extends Postgres82 +{ + + public $major_version = 8.1; + // List of all legal privileges that can be applied to different types + // of objects. + public $privlist = [ + 'table' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], + 'view' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'RULE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], + 'sequence' => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'], + 'database' => ['CREATE', 'TEMPORARY', 'ALL PRIVILEGES'], + 'function' => ['EXECUTE', 'ALL PRIVILEGES'], + 'language' => ['USAGE', 'ALL PRIVILEGES'], + 'schema' => ['CREATE', 'USAGE', 'ALL PRIVILEGES'], + 'tablespace' => ['CREATE', 'ALL PRIVILEGES'], + ]; + // List of characters in acl lists and the privileges they + // refer to. + public $privmap = [ + '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 + public $typIndexes = ['BTREE', 'RTREE', 'GIST', 'HASH']; + + // Database functions + + /** + * Returns all databases available on the server + * @return A list of databases, sorted alphabetically + */ + public function getDatabases($currentdatabase = null) + { + $conf = $this->conf; + $server_info = $this->server_info; + + if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { + $username = $server_info['username']; + $this->clean($username); + $clause = " AND pr.rolname='{$username}'"; + } else { + $clause = ''; + } + + if ($currentdatabase != null) { + $this->clean($currentdatabase); + $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, pr.rolname AS datowner, pg_encoding_to_char(encoding) AS datencoding, (SELECT description FROM pg_catalog.pg_description 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 @@ -87,58 +83,59 @@ class Postgres81 extends Postgres82 { {$clause} {$orderby}"; - 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(); - } - - // Autovacuum functions - - function saveAutovacuum($table, $vacenabled, $vacthreshold, $vacscalefactor, - $anathresold, $anascalefactor, $vaccostdelay, $vaccostlimit) { - $defaults = $this->getAutovacuum(); - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $rs = $this->selectSet(" + 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 + */ + public 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(); + } + + // Autovacuum functions + + public function saveAutovacuum($table, $vacenabled, $vacthreshold, $vacscalefactor, + $anathresold, $anascalefactor, $vaccostdelay, $vaccostlimit) { + $defaults = $this->getAutovacuum(); + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + + $rs = $this->selectSet(" SELECT c.oid FROM pg_catalog.pg_class AS c LEFT JOIN pg_catalog.pg_namespace AS n ON (n.oid=c.relnamespace) @@ -146,45 +143,45 @@ class Postgres81 extends Postgres82 { c.relname = '{$table}' AND n.nspname = '{$c_schema}' "); - if ($rs->EOF) { - return -1; - } + if ($rs->EOF) { + return -1; + } - $toid = $rs->fields('oid'); - unset($rs); + $toid = $rs->fields('oid'); + unset($rs); - if (empty($_POST['autovacuum_vacuum_threshold'])) { - $_POST['autovacuum_vacuum_threshold'] = $defaults['autovacuum_vacuum_threshold']; - } + if (empty($_POST['autovacuum_vacuum_threshold'])) { + $_POST['autovacuum_vacuum_threshold'] = $defaults['autovacuum_vacuum_threshold']; + } - if (empty($_POST['autovacuum_vacuum_scale_factor'])) { - $_POST['autovacuum_vacuum_scale_factor'] = $defaults['autovacuum_vacuum_scale_factor']; - } + if (empty($_POST['autovacuum_vacuum_scale_factor'])) { + $_POST['autovacuum_vacuum_scale_factor'] = $defaults['autovacuum_vacuum_scale_factor']; + } - if (empty($_POST['autovacuum_analyze_threshold'])) { - $_POST['autovacuum_analyze_threshold'] = $defaults['autovacuum_analyze_threshold']; - } + if (empty($_POST['autovacuum_analyze_threshold'])) { + $_POST['autovacuum_analyze_threshold'] = $defaults['autovacuum_analyze_threshold']; + } - if (empty($_POST['autovacuum_analyze_scale_factor'])) { - $_POST['autovacuum_analyze_scale_factor'] = $defaults['autovacuum_analyze_scale_factor']; - } + if (empty($_POST['autovacuum_analyze_scale_factor'])) { + $_POST['autovacuum_analyze_scale_factor'] = $defaults['autovacuum_analyze_scale_factor']; + } - if (empty($_POST['autovacuum_vacuum_cost_delay'])) { - $_POST['autovacuum_vacuum_cost_delay'] = $defaults['autovacuum_vacuum_cost_delay']; - } + if (empty($_POST['autovacuum_vacuum_cost_delay'])) { + $_POST['autovacuum_vacuum_cost_delay'] = $defaults['autovacuum_vacuum_cost_delay']; + } - if (empty($_POST['autovacuum_vacuum_cost_limit'])) { - $_POST['autovacuum_vacuum_cost_limit'] = $defaults['autovacuum_vacuum_cost_limit']; - } + if (empty($_POST['autovacuum_vacuum_cost_limit'])) { + $_POST['autovacuum_vacuum_cost_limit'] = $defaults['autovacuum_vacuum_cost_limit']; + } - $rs = $this->selectSet("SELECT vacrelid + $rs = $this->selectSet("SELECT vacrelid FROM \"pg_catalog\".\"pg_autovacuum\" WHERE vacrelid = {$toid};"); - $status = -1; // ini - if ($rs->recordCount() and ($rs->fields['vacrelid'] == $toid)) { - // table exists in pg_autovacuum, UPDATE - $sql = sprintf("UPDATE \"pg_catalog\".\"pg_autovacuum\" SET + $status = -1; // ini + if ($rs->recordCount() and ($rs->fields['vacrelid'] == $toid)) { + // table exists in pg_autovacuum, UPDATE + $sql = sprintf("UPDATE \"pg_catalog\".\"pg_autovacuum\" SET enabled = '%s', vac_base_thresh = %s, vac_scale_factor = %s, @@ -194,97 +191,105 @@ class Postgres81 extends Postgres82 { vac_cost_limit = %s WHERE vacrelid = {$toid}; ", - ($_POST['autovacuum_enabled'] == 'on') ? 't' : 'f', - $_POST['autovacuum_vacuum_threshold'], - $_POST['autovacuum_vacuum_scale_factor'], - $_POST['autovacuum_analyze_threshold'], - $_POST['autovacuum_analyze_scale_factor'], - $_POST['autovacuum_vacuum_cost_delay'], - $_POST['autovacuum_vacuum_cost_limit'] - ); - $status = $this->execute($sql); - } else { - // table doesn't exists in pg_autovacuum, INSERT - $sql = sprintf("INSERT INTO \"pg_catalog\".\"pg_autovacuum\" + ($_POST['autovacuum_enabled'] == 'on') ? 't' : 'f', + $_POST['autovacuum_vacuum_threshold'], + $_POST['autovacuum_vacuum_scale_factor'], + $_POST['autovacuum_analyze_threshold'], + $_POST['autovacuum_analyze_scale_factor'], + $_POST['autovacuum_vacuum_cost_delay'], + $_POST['autovacuum_vacuum_cost_limit'] + ); + $status = $this->execute($sql); + } else { + // table doesn't exists in pg_autovacuum, INSERT + $sql = sprintf("INSERT INTO \"pg_catalog\".\"pg_autovacuum\" VALUES (%s, '%s', %s, %s, %s, %s, %s, %s)", - $toid, - ($_POST['autovacuum_enabled'] == 'on') ? 't' : 'f', - $_POST['autovacuum_vacuum_threshold'], - $_POST['autovacuum_vacuum_scale_factor'], - $_POST['autovacuum_analyze_threshold'], - $_POST['autovacuum_analyze_scale_factor'], - $_POST['autovacuum_vacuum_cost_delay'], - $_POST['autovacuum_vacuum_cost_limit'] - ); - $status = $this->execute($sql); - } - - return $status; - } - - /** - * 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 datname, usename, procpid AS pid, current_query AS query, query_start, + $toid, + ($_POST['autovacuum_enabled'] == 'on') ? 't' : 'f', + $_POST['autovacuum_vacuum_threshold'], + $_POST['autovacuum_vacuum_scale_factor'], + $_POST['autovacuum_analyze_threshold'], + $_POST['autovacuum_analyze_scale_factor'], + $_POST['autovacuum_vacuum_cost_delay'], + $_POST['autovacuum_vacuum_cost_limit'] + ); + $status = $this->execute($sql); + } + + return $status; + } + + /** + * Returns all available process information. + * @param $database (optional) Find only connections to specified database + * @return A recordset + */ + public function getProcesses($database = null) + { + if ($database === null) { + $sql = "SELECT datname, usename, procpid AS pid, current_query AS query, query_start, case when (select count(*) from pg_locks where pid=pg_stat_activity.procpid and granted is false) > 0 then 't' else 'f' end as waiting FROM pg_catalog.pg_stat_activity ORDER BY datname, usename, procpid"; - } else { - $this->clean($database); - $sql = "SELECT datname, usename, procpid AS pid, current_query AS query, query_start + } else { + $this->clean($database); + $sql = "SELECT datname, usename, procpid AS pid, current_query AS query, query_start case when (select count(*) from pg_locks where pid=pg_stat_activity.procpid and granted is false) > 0 then 't' else 'f' end as waiting FROM pg_catalog.pg_stat_activity WHERE datname='{$database}' ORDER BY usename, procpid"; - } + } - $rc = $this->selectSet($sql); + $rc = $this->selectSet($sql); - return $rc; - } + return $rc; + } - // Tablespace functions + // Tablespace functions - /** - * Retrieves a tablespace's information - * @return A recordset - */ - function getTablespace($spcname) { - $this->clean($spcname); + /** + * Retrieves a tablespace's information + * @return A recordset + */ + public function getTablespace($spcname) + { + $this->clean($spcname); - $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation + $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - /** - * 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) { - $conf = $this->conf; + /** + * Retrieves information for all tablespaces + * @param $all Include all tablespaces (necessary when moving objects back to the default space) + * @return A recordset + */ + public function getTablespaces($all = false) + { + $conf = $this->conf; - $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation + $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\_%$$'; - } + if (!$conf['show_system'] && !$all) { + $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$'; + } + + $sql .= " ORDER BY spcname"; + + return $this->selectSet($sql); + } - $sql .= " ORDER BY spcname"; + // Capabilities - return $this->selectSet($sql); - } + public function hasCreateTableLikeWithConstraints() + {return false;} - // Capabilities + public function hasSharedComments() + {return false;} - function hasCreateTableLikeWithConstraints() {return false;} - function hasSharedComments() {return false;} - function hasConcurrentIndexBuild() {return false;} + public function hasConcurrentIndexBuild() + {return false;} } diff --git a/src/database/Postgres82.php b/src/database/Postgres82.php index 2a69e69b..fd06080f 100644 --- a/src/database/Postgres82.php +++ b/src/database/Postgres82.php @@ -1,116 +1,115 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 8.2 support * * $Id: Postgres82.php,v 1.10 2007/12/28 16:21:25 ioguix Exp $ */ -class Postgres82 extends Postgres83 { - - var $major_version = 8.2; +class Postgres82 extends Postgres83 +{ - // Select operators - var $selectOps = ['=' => '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']; + public $major_version = 8.2; - // Help functions + // Select operators + public $selectOps = ['=' => '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']; - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc82.php'; - return $this->help_page; - } + // Database functions - // Database functions + /** + * Returns table locks information in the current database + * @return A recordset + */ + public function getLocks() + { + $conf = $this->conf; - /** - * Returns table locks information in the current database - * @return A recordset - */ - function getLocks() { - $conf = $this->conf; + if (!$conf['show_system']) { + $where = 'AND pn.nspname NOT LIKE $$pg\_%$$'; + } else { + $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; + } - 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.transaction, pl.pid, pl.mode, pl.granted + $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 - - /** - * Rename a sequence - * @param $seqrs The sequence RecordSet returned by getSequence() - * @param $name The new name for the sequence - * @return 0 success - */ - function alterSequenceName($seqrs, $name) { - /* vars are cleaned in _alterSequence */ - if (!empty($name) && ($seqrs->fields['seqname'] != $name)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $sql = "ALTER TABLE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" RENAME TO \"{$name}\""; - $status = $this->execute($sql); - if ($status == 0) { - $seqrs->fields['seqname'] = $name; - } else { - return $status; - } - - } - return 0; - } - - // View functions - - /** - * 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 alterViewName($vwrs, $name) { - // Rename (only if name has changed) - /* $vwrs and $name are cleaned in _alterView */ - if (!empty($name) && ($name != $vwrs->fields['relname'])) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $sql = "ALTER TABLE \"{$f_schema}\".\"{$vwrs->fields['relname']}\" RENAME TO \"{$name}\""; - $status = $this->execute($sql); - if ($status == 0) { - $vwrs->fields['relname'] = $name; - } else { - return $status; - } - - } - return 0; - } - - // 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 = '') { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $sql = "SELECT + return $this->selectSet($sql); + } + + // Sequence functions + + /** + * Rename a sequence + * @param $seqrs The sequence RecordSet returned by getSequence() + * @param $name The new name for the sequence + * @return 0 success + */ + public function alterSequenceName($seqrs, $name) + { + /* vars are cleaned in _alterSequence */ + if (!empty($name) && ($seqrs->fields['seqname'] != $name)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $sql = "ALTER TABLE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); + if ($status == 0) { + $seqrs->fields['seqname'] = $name; + } else { + return $status; + } + + } + return 0; + } + + // View functions + + /** + * Rename a view + * @param $vwrs The view recordSet returned by getView() + * @param $name The new view's name + * @return -1 Failed + * @return 0 success + */ + public function alterViewName($vwrs, $name) + { + // Rename (only if name has changed) + /* $vwrs and $name are cleaned in _alterView */ + if (!empty($name) && ($name != $vwrs->fields['relname'])) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $sql = "ALTER TABLE \"{$f_schema}\".\"{$vwrs->fields['relname']}\" RENAME TO \"{$name}\""; + $status = $this->execute($sql); + if ($status == 0) { + $vwrs->fields['relname'] = $name; + } else { + return $status; + } + + } + return 0; + } + + // Trigger functions + + /** + * Grabs a list of triggers on a table + * @param $table The name of a table whose triggers to retrieve + * @return A recordset + */ + public function getTriggers($table = '') + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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 @@ -124,20 +123,21 @@ class Postgres82 extends Postgres83 { AND p.oid=t.tgfoid AND p.pronamespace = ns.oid"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Function functions + // 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); + /** + * Returns all details for a particular function + * @param $func The name of the function to retrieve + * @return Function info + */ + public function getFunction($function_oid) + { + $this->clean($function_oid); - $sql = "SELECT + $sql = "SELECT pc.oid AS prooid, proname, pg_catalog.pg_get_userbyid(proowner) AS proowner, @@ -161,143 +161,146 @@ class Postgres82 extends Postgres83 { AND pc.pronamespace = pn.oid "; - return $this->selectSet($sql); - } - - /** - * 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 $comment The comment on the function - * @param $replace (optional) True if OR REPLACE, false for normal - * @return 0 success - * @return -1 create function failed - * @return -4 set comment failed - */ - function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, $replace = false) { - - // Begin a transaction - $status = $this->beginTransaction(); - if ($status != 0) { - $this->rollbackTransaction(); - return -1; - } - - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($funcname); - $this->clean($args); - $this->fieldClean($language); - $this->arrayClean($flags); - - $sql = "CREATE"; - if ($replace) { - $sql .= " OR REPLACE"; - } - - $sql .= " FUNCTION \"{$f_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}"; - } - - } - - $status = $this->execute($sql); - if ($status != 0) { - $this->rollbackTransaction(); - return -3; - } - - /* set the comment */ - $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment); - if ($status != 0) { - $this->rollbackTransaction(); - return -4; - } - - return $this->endTransaction(); - } - - // 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($table = '', $index = '') { - - $sql = 'CLUSTER'; - - // 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 - - if (!empty($table)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $this->fieldClean($table); - - if (!empty($index)) { - $this->fieldClean($index); - $sql .= " \"{$index}\" ON \"{$f_schema}\".\"{$table}\""; - } else { - $sql .= " \"{$f_schema}\".\"{$table}\""; - } - } - - return $this->execute($sql); - } - - // Operator functions - - /** - * 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 = " + return $this->selectSet($sql); + } + + /** + * 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 $comment The comment on the function + * @param $replace (optional) True if OR REPLACE, false for normal + * @return 0 success + * @return -1 create function failed + * @return -4 set comment failed + */ + public function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, $replace = false) + { + + // Begin a transaction + $status = $this->beginTransaction(); + if ($status != 0) { + $this->rollbackTransaction(); + return -1; + } + + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($funcname); + $this->clean($args); + $this->fieldClean($language); + $this->arrayClean($flags); + + $sql = "CREATE"; + if ($replace) { + $sql .= " OR REPLACE"; + } + + $sql .= " FUNCTION \"{$f_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}"; + } + + } + + $status = $this->execute($sql); + if ($status != 0) { + $this->rollbackTransaction(); + return -3; + } + + /* set the comment */ + $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment); + if ($status != 0) { + $this->rollbackTransaction(); + return -4; + } + + return $this->endTransaction(); + } + + // Index functions + + /** + * Clusters an index + * @param $index The name of the index + * @param $table The table the index is on + * @return 0 success + */ + public function clusterIndex($table = '', $index = '') + { + + $sql = 'CLUSTER'; + + // 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 + + if (!empty($table)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $this->fieldClean($table); + + if (!empty($index)) { + $this->fieldClean($index); + $sql .= " \"{$index}\" ON \"{$f_schema}\".\"{$table}\""; + } else { + $sql .= " \"{$f_schema}\".\"{$table}\""; + } + } + + return $this->execute($sql); + } + + // Operator functions + + /** + * Returns all details for a particular operator + * @param $operator_oid The oid of the operator + * @return Function info + */ + public function getOperator($operator_oid) + { + $this->clean($operator_oid); + + $sql = " SELECT po.oid, po.oprname, oprleft::pg_catalog.regtype AS oprleftname, @@ -319,19 +322,20 @@ class Postgres82 extends Postgres83 { po.oid='{$operator_oid}' "; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Operator Class functions + // Operator Class functions - /** - * Gets all opclasses - * @return A recordset - */ - function getOpClasses() { - $c_schema = $this->_schema; - $this->clean($c_schema); - $sql = " + /** + * Gets all opclasses + * @return A recordset + */ + public function getOpClasses() + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $sql = " SELECT pa.amname, po.opcname, @@ -347,16 +351,27 @@ class Postgres82 extends Postgres83 { ORDER BY 1,2 "; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } + + // Capabilities + + public function hasCreateTableLikeWithIndexes() + {return false;} + + public function hasEnumTypes() + {return false;} + + public function hasFTS() + {return false;} + + public function hasFunctionCosting() + {return false;} - // Capabilities + public function hasFunctionGUC() + {return false;} - function hasCreateTableLikeWithIndexes() {return false;} - function hasEnumTypes() {return false;} - function hasFTS() {return false;} - function hasFunctionCosting() {return false;} - function hasFunctionGUC() {return false;} - function hasVirtualTransactionId() {return false;} + public function hasVirtualTransactionId() + {return false;} } diff --git a/src/database/Postgres83.php b/src/database/Postgres83.php index 199dccc4..6bdfd7ec 100644 --- a/src/database/Postgres83.php +++ b/src/database/Postgres83.php @@ -1,85 +1,81 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 8.3 support * * $Id: Postgres82.php,v 1.10 2007/12/28 16:21:25 ioguix Exp $ */ -class Postgres83 extends Postgres84 { - - var $major_version = 8.3; - - // List of all legal privileges that can be applied to different types - // of objects. - var $privlist = [ - 'table' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], - 'view' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], - 'sequence' => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'], - 'database' => ['CREATE', 'TEMPORARY', 'CONNECT', 'ALL PRIVILEGES'], - 'function' => ['EXECUTE', 'ALL PRIVILEGES'], - 'language' => ['USAGE', 'ALL PRIVILEGES'], - 'schema' => ['CREATE', 'USAGE', 'ALL PRIVILEGES'], - 'tablespace' => ['CREATE', 'ALL PRIVILEGES'], - ]; - // List of characters in acl lists and the privileges they - // refer to. - var $privmap = [ - 'r' => 'SELECT', - 'w' => 'UPDATE', - 'a' => 'INSERT', - 'd' => 'DELETE', - 'R' => 'RULE', - 'x' => 'REFERENCES', - 't' => 'TRIGGER', - 'X' => 'EXECUTE', - 'U' => 'USAGE', - 'C' => 'CREATE', - 'T' => 'TEMPORARY', - 'c' => 'CONNECT', - ]; - - // Help functions - - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc83.php'; - return $this->help_page; - } - - // Databse functions - - /** - * Return all database available on the server - * @param $currentdatabase database name that should be on top of the resultset - * - * @return A list of databases, sorted alphabetically - */ - function getDatabases($currentdatabase = NULL) { - $conf = $this->conf; - $server_info = $this->server_info; - - if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { - $username = $server_info['username']; - $this->clean($username); - $clause = " AND pr.rolname='{$username}'"; - } else { - $clause = ''; - } - - if ($currentdatabase != NULL) { - $this->clean($currentdatabase); - $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 = " +class Postgres83 extends Postgres84 +{ + + public $major_version = 8.3; + + // List of all legal privileges that can be applied to different types + // of objects. + public $privlist = [ + 'table' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], + 'view' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], + 'sequence' => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'], + 'database' => ['CREATE', 'TEMPORARY', 'CONNECT', 'ALL PRIVILEGES'], + 'function' => ['EXECUTE', 'ALL PRIVILEGES'], + 'language' => ['USAGE', 'ALL PRIVILEGES'], + 'schema' => ['CREATE', 'USAGE', 'ALL PRIVILEGES'], + 'tablespace' => ['CREATE', 'ALL PRIVILEGES'], + ]; + // List of characters in acl lists and the privileges they + // refer to. + public $privmap = [ + 'r' => 'SELECT', + 'w' => 'UPDATE', + 'a' => 'INSERT', + 'd' => 'DELETE', + 'R' => 'RULE', + 'x' => 'REFERENCES', + 't' => 'TRIGGER', + 'X' => 'EXECUTE', + 'U' => 'USAGE', + 'C' => 'CREATE', + 'T' => 'TEMPORARY', + 'c' => 'CONNECT', + ]; + + // Databse functions + + /** + * Return all database available on the server + * @param $currentdatabase database name that should be on top of the resultset + * + * @return A list of databases, sorted alphabetically + */ + public function getDatabases($currentdatabase = null) + { + $conf = $this->conf; + $server_info = $this->server_info; + + if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { + $username = $server_info['username']; + $this->clean($username); + $clause = " AND pr.rolname='{$username}'"; + } else { + $clause = ''; + } + + if ($currentdatabase != null) { + $this->clean($currentdatabase); + $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, pr.rolname AS datowner, pg_encoding_to_char(encoding) AS datencoding, (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pdb.oid=pd.objoid AND pd.classoid='pg_database'::regclass) AS datcomment, (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace, @@ -90,24 +86,25 @@ class Postgres83 extends Postgres84 { {$clause} {$orderby}"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Administration functions + // Administration functions - /** - * Returns all available autovacuum per table information. - * @return A recordset - */ - function getTableAutovacuum($table = '') { - $sql = ''; + /** + * Returns all available autovacuum per table information. + * @return A recordset + */ + public function getTableAutovacuum($table = '') + { + $sql = ''; - if ($table !== '') { - $this->clean($table); - $c_schema = $this->_schema; - $this->clean($c_schema); + if ($table !== '') { + $this->clean($table); + $c_schema = $this->_schema; + $this->clean($c_schema); - $sql = " + $sql = " SELECT vacrelid, nspname, relname, CASE enabled WHEN 't' THEN 'on' @@ -122,8 +119,8 @@ class Postgres83 extends Postgres84 { WHERE c.relname = '{$table}' AND n.nspname = '{$c_schema}' ORDER BY nspname, relname "; - } else { - $sql = " + } else { + $sql = " SELECT vacrelid, nspname, relname, CASE enabled WHEN 't' THEN 'on' @@ -137,19 +134,19 @@ class Postgres83 extends Postgres84 { join pg_namespace AS n on (n.oid=c.relnamespace) ORDER BY nspname, relname "; - } + } - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - function saveAutovacuum($table, $vacenabled, $vacthreshold, $vacscalefactor, $anathresold, - $anascalefactor, $vaccostdelay, $vaccostlimit) { - $defaults = $this->getAutovacuum(); - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); + public function saveAutovacuum($table, $vacenabled, $vacthreshold, $vacscalefactor, $anathresold, + $anascalefactor, $vaccostdelay, $vaccostlimit) { + $defaults = $this->getAutovacuum(); + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); - $rs = $this->selectSet(" + $rs = $this->selectSet(" SELECT c.oid FROM pg_catalog.pg_class AS c LEFT JOIN pg_catalog.pg_namespace AS n ON (n.oid=c.relnamespace) @@ -157,53 +154,53 @@ class Postgres83 extends Postgres84 { c.relname = '{$table}' AND n.nspname = '{$c_schema}' "); - if ($rs->EOF) { - return -1; - } + if ($rs->EOF) { + return -1; + } - $toid = $rs->fields('oid'); - unset($rs); + $toid = $rs->fields('oid'); + unset($rs); - if (empty($_POST['autovacuum_vacuum_threshold'])) { - $_POST['autovacuum_vacuum_threshold'] = $defaults['autovacuum_vacuum_threshold']; - } + if (empty($_POST['autovacuum_vacuum_threshold'])) { + $_POST['autovacuum_vacuum_threshold'] = $defaults['autovacuum_vacuum_threshold']; + } - if (empty($_POST['autovacuum_vacuum_scale_factor'])) { - $_POST['autovacuum_vacuum_scale_factor'] = $defaults['autovacuum_vacuum_scale_factor']; - } + if (empty($_POST['autovacuum_vacuum_scale_factor'])) { + $_POST['autovacuum_vacuum_scale_factor'] = $defaults['autovacuum_vacuum_scale_factor']; + } - if (empty($_POST['autovacuum_analyze_threshold'])) { - $_POST['autovacuum_analyze_threshold'] = $defaults['autovacuum_analyze_threshold']; - } + if (empty($_POST['autovacuum_analyze_threshold'])) { + $_POST['autovacuum_analyze_threshold'] = $defaults['autovacuum_analyze_threshold']; + } - if (empty($_POST['autovacuum_analyze_scale_factor'])) { - $_POST['autovacuum_analyze_scale_factor'] = $defaults['autovacuum_analyze_scale_factor']; - } + if (empty($_POST['autovacuum_analyze_scale_factor'])) { + $_POST['autovacuum_analyze_scale_factor'] = $defaults['autovacuum_analyze_scale_factor']; + } - if (empty($_POST['autovacuum_vacuum_cost_delay'])) { - $_POST['autovacuum_vacuum_cost_delay'] = $defaults['autovacuum_vacuum_cost_delay']; - } + if (empty($_POST['autovacuum_vacuum_cost_delay'])) { + $_POST['autovacuum_vacuum_cost_delay'] = $defaults['autovacuum_vacuum_cost_delay']; + } - if (empty($_POST['autovacuum_vacuum_cost_limit'])) { - $_POST['autovacuum_vacuum_cost_limit'] = $defaults['autovacuum_vacuum_cost_limit']; - } + if (empty($_POST['autovacuum_vacuum_cost_limit'])) { + $_POST['autovacuum_vacuum_cost_limit'] = $defaults['autovacuum_vacuum_cost_limit']; + } - if (empty($_POST['vacuum_freeze_min_age'])) { - $_POST['vacuum_freeze_min_age'] = $defaults['vacuum_freeze_min_age']; - } + if (empty($_POST['vacuum_freeze_min_age'])) { + $_POST['vacuum_freeze_min_age'] = $defaults['vacuum_freeze_min_age']; + } - if (empty($_POST['autovacuum_freeze_max_age'])) { - $_POST['autovacuum_freeze_max_age'] = $defaults['autovacuum_freeze_max_age']; - } + if (empty($_POST['autovacuum_freeze_max_age'])) { + $_POST['autovacuum_freeze_max_age'] = $defaults['autovacuum_freeze_max_age']; + } - $rs = $this->selectSet("SELECT vacrelid + $rs = $this->selectSet("SELECT vacrelid FROM \"pg_catalog\".\"pg_autovacuum\" WHERE vacrelid = {$toid};"); - $status = -1; // ini - if ($rs->recordCount() and ($rs->fields['vacrelid'] == $toid)) { - // table exists in pg_autovacuum, UPDATE - $sql = sprintf("UPDATE \"pg_catalog\".\"pg_autovacuum\" SET + $status = -1; // ini + if ($rs->recordCount() and ($rs->fields['vacrelid'] == $toid)) { + // table exists in pg_autovacuum, UPDATE + $sql = sprintf("UPDATE \"pg_catalog\".\"pg_autovacuum\" SET enabled = '%s', vac_base_thresh = %s, vac_scale_factor = %s, @@ -215,44 +212,45 @@ class Postgres83 extends Postgres84 { freeze_max_age = %s WHERE vacrelid = {$toid}; ", - ($_POST['autovacuum_enabled'] == 'on') ? 't' : 'f', - $_POST['autovacuum_vacuum_threshold'], - $_POST['autovacuum_vacuum_scale_factor'], - $_POST['autovacuum_analyze_threshold'], - $_POST['autovacuum_analyze_scale_factor'], - $_POST['autovacuum_vacuum_cost_delay'], - $_POST['autovacuum_vacuum_cost_limit'], - $_POST['vacuum_freeze_min_age'], - $_POST['autovacuum_freeze_max_age'] - ); - $status = $this->execute($sql); - } else { - // table doesn't exists in pg_autovacuum, INSERT - $sql = sprintf("INSERT INTO \"pg_catalog\".\"pg_autovacuum\" + ($_POST['autovacuum_enabled'] == 'on') ? 't' : 'f', + $_POST['autovacuum_vacuum_threshold'], + $_POST['autovacuum_vacuum_scale_factor'], + $_POST['autovacuum_analyze_threshold'], + $_POST['autovacuum_analyze_scale_factor'], + $_POST['autovacuum_vacuum_cost_delay'], + $_POST['autovacuum_vacuum_cost_limit'], + $_POST['vacuum_freeze_min_age'], + $_POST['autovacuum_freeze_max_age'] + ); + $status = $this->execute($sql); + } else { + // table doesn't exists in pg_autovacuum, INSERT + $sql = sprintf("INSERT INTO \"pg_catalog\".\"pg_autovacuum\" VALUES (%s, '%s', %s, %s, %s, %s, %s, %s, %s, %s )", - $toid, - ($_POST['autovacuum_enabled'] == 'on') ? 't' : 'f', - $_POST['autovacuum_vacuum_threshold'], - $_POST['autovacuum_vacuum_scale_factor'], - $_POST['autovacuum_analyze_threshold'], - $_POST['autovacuum_analyze_scale_factor'], - $_POST['autovacuum_vacuum_cost_delay'], - $_POST['autovacuum_vacuum_cost_limit'], - $_POST['vacuum_freeze_min_age'], - $_POST['autovacuum_freeze_max_age'] - ); - $status = $this->execute($sql); - } - - return $status; - } - - function dropAutovacuum($table) { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $rs = $this->selectSet(" + $toid, + ($_POST['autovacuum_enabled'] == 'on') ? 't' : 'f', + $_POST['autovacuum_vacuum_threshold'], + $_POST['autovacuum_vacuum_scale_factor'], + $_POST['autovacuum_analyze_threshold'], + $_POST['autovacuum_analyze_scale_factor'], + $_POST['autovacuum_vacuum_cost_delay'], + $_POST['autovacuum_vacuum_cost_limit'], + $_POST['vacuum_freeze_min_age'], + $_POST['autovacuum_freeze_max_age'] + ); + $status = $this->execute($sql); + } + + return $status; + } + + public function dropAutovacuum($table) + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $this->clean($table); + + $rs = $this->selectSet(" SELECT c.oid FROM pg_catalog.pg_class AS c LEFT JOIN pg_catalog.pg_namespace AS n ON (n.oid=c.relnamespace) @@ -260,93 +258,95 @@ class Postgres83 extends Postgres84 { c.relname = '{$table}' AND n.nspname = '{$c_schema}' "); - return $this->deleteRow('pg_autovacuum', ['vacrelid' => $rs->fields['oid']], 'pg_catalog'); - } - - // Sequence functions - - /** - * 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 $restartvalue The sequence current value - * @param $cachevalue The sequence cache value - * @param $cycledvalue Sequence can cycle ? - * @param $startvalue The sequence start value when issueing a restart (ignored) - * @return 0 success - */ - function alterSequenceProps($seqrs, $increment, $minvalue, $maxvalue, - $restartvalue, $cachevalue, $cycledvalue, $startvalue) { - - $sql = ''; - /* vars are cleaned in _alterSequence */ - 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($restartvalue) && ($restartvalue != $seqrs->fields['last_value'])) { - $sql .= " RESTART {$restartvalue}"; - } - - 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 != '') { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" {$sql}"; - return $this->execute($sql); - } - 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. - /* vars are cleaned in _alterSequence */ - if (!empty($owner) && ($seqrs->fields['seqowner'] != $owner)) { - $f_schema = $this->_schema; - $this->fieldClean($f_schema); - $sql = "ALTER TABLE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" OWNER TO \"{$owner}\""; - return $this->execute($sql); - } - return 0; - } - - // 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 = " + return $this->deleteRow('pg_autovacuum', ['vacrelid' => $rs->fields['oid']], 'pg_catalog'); + } + + // Sequence functions + + /** + * 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 $restartvalue The sequence current value + * @param $cachevalue The sequence cache value + * @param $cycledvalue Sequence can cycle ? + * @param $startvalue The sequence start value when issueing a restart (ignored) + * @return 0 success + */ + public function alterSequenceProps($seqrs, $increment, $minvalue, $maxvalue, + $restartvalue, $cachevalue, $cycledvalue, $startvalue) { + + $sql = ''; + /* vars are cleaned in _alterSequence */ + 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($restartvalue) && ($restartvalue != $seqrs->fields['last_value'])) { + $sql .= " RESTART {$restartvalue}"; + } + + 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 != '') { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" {$sql}"; + return $this->execute($sql); + } + 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 + */ + public 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. + /* vars are cleaned in _alterSequence */ + if (!empty($owner) && ($seqrs->fields['seqowner'] != $owner)) { + $f_schema = $this->_schema; + $this->fieldClean($f_schema); + $sql = "ALTER TABLE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" OWNER TO \"{$owner}\""; + return $this->execute($sql); + } + return 0; + } + + // Function functions + + /** + * Returns all details for a particular function + * @param $func The name of the function to retrieve + * @return Function info + */ + public 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, @@ -364,11 +364,16 @@ class Postgres83 extends Postgres84 { AND pc.pronamespace = pn.oid "; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } + + // Capabilities + public function hasQueryKill() + {return false;} + + public function hasDatabaseCollation() + {return false;} - // Capabilities - function hasQueryKill() {return false;} - function hasDatabaseCollation() {return false;} - function hasAlterSequenceStart() {return false;} + public function hasAlterSequenceStart() + {return false;} } diff --git a/src/database/Postgres84.php b/src/database/Postgres84.php index 82c80513..defe0f5a 100644 --- a/src/database/Postgres84.php +++ b/src/database/Postgres84.php @@ -1,49 +1,45 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 8.4 support * * $Id: Postgres82.php,v 1.10 2007/12/28 16:21:25 ioguix Exp $ */ -class Postgres84 extends Postgres90 { - - var $major_version = 8.4; - - // List of all legal privileges that can be applied to different types - // of objects. - var $privlist = [ - 'table' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], - 'view' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], - 'sequence' => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'], - 'database' => ['CREATE', 'TEMPORARY', 'CONNECT', 'ALL PRIVILEGES'], - 'function' => ['EXECUTE', 'ALL PRIVILEGES'], - 'language' => ['USAGE', 'ALL PRIVILEGES'], - 'schema' => ['CREATE', 'USAGE', 'ALL PRIVILEGES'], - 'tablespace' => ['CREATE', 'ALL PRIVILEGES'], - 'column' => ['SELECT', 'INSERT', 'UPDATE', 'REFERENCES', 'ALL PRIVILEGES'], - ]; - - // Help functions - - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc84.php'; - return $this->help_page; - } - - // Database 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 = '') { - $c_schema = $this->_schema; - $this->clean($c_schema); - $this->clean($table); - - $sql = "SELECT +class Postgres84 extends Postgres90 +{ + + public $major_version = 8.4; + + // List of all legal privileges that can be applied to different types + // of objects. + public $privlist = [ + 'table' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], + 'view' => ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'], + 'sequence' => ['SELECT', 'UPDATE', 'ALL PRIVILEGES'], + 'database' => ['CREATE', 'TEMPORARY', 'CONNECT', 'ALL PRIVILEGES'], + 'function' => ['EXECUTE', 'ALL PRIVILEGES'], + 'language' => ['USAGE', 'ALL PRIVILEGES'], + 'schema' => ['CREATE', 'USAGE', 'ALL PRIVILEGES'], + 'tablespace' => ['CREATE', 'ALL PRIVILEGES'], + 'column' => ['SELECT', 'INSERT', 'UPDATE', 'REFERENCES', 'ALL PRIVILEGES'], + ]; + + // Database functions + + /** + * Grabs a list of triggers on a table + * @param $table The name of a table whose triggers to retrieve + * @return A recordset + */ + public function getTriggers($table = '') + { + $c_schema = $this->_schema; + $this->clean($c_schema); + $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, @@ -58,52 +54,53 @@ class Postgres84 extends Postgres90 { AND p.oid=t.tgfoid AND p.pronamespace = ns.oid"; - return $this->selectSet($sql); - } - - /** - * 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) { - $conf = $this->conf; - - /*about escaping: - * SET standard_conforming_string is not available before 8.2 - * So we must use PostgreSQL specific notation :/ - * E'' notation is not available before 8.1 - * $$ is available since 8.0 - * Nothing specific from 7.4 - */ - - // Escape search term for ILIKE match - $this->clean($term); - $this->clean($filter); - $term = str_replace('_', '\_', $term); - $term = str_replace('%', '\%', $term); - - // 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 \$_PATERN_\$pg\_%\$_PATERN_\$ AND pn.nspname != 'information_schema'"; - $lan_where = "AND pl.lanispl"; - } else { - $where = ''; - $lan_where = ''; - } - - // Apply outer filter - $sql = ''; - if ($filter != '') { - $sql = "SELECT * FROM ("; - } - - $term = "\$_PATERN_\$%{$term}%\$_PATERN_\$"; - - $sql .= " + return $this->selectSet($sql); + } + + /** + * 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 + */ + public function findObject($term, $filter) + { + $conf = $this->conf; + + /*about escaping: + * SET standard_conforming_string is not available before 8.2 + * So we must use PostgreSQL specific notation :/ + * E'' notation is not available before 8.1 + * $$ is available since 8.0 + * Nothing specific from 7.4 + */ + + // Escape search term for ILIKE match + $this->clean($term); + $this->clean($filter); + $term = str_replace('_', '\_', $term); + $term = str_replace('%', '\%', $term); + + // 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 \$_PATERN_\$pg\_%\$_PATERN_\$ AND pn.nspname != 'information_schema'"; + $lan_where = "AND pl.lanispl"; + } else { + $where = ''; + $lan_where = ''; + } + + // Apply outer filter + $sql = ''; + if ($filter != '') { + $sql = "SELECT * FROM ("; + } + + $term = "\$_PATERN_\$%{$term}%\$_PATERN_\$"; + + $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 @@ -160,9 +157,9 @@ class Postgres84 extends Postgres90 { 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 .= " + // 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 @@ -187,10 +184,10 @@ class Postgres84 extends Postgres90 { pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid AND po.opcname ILIKE {$term} {$where} "; - } - // Otherwise just add domains - else { - $sql .= " + } + // 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 @@ -198,20 +195,21 @@ class Postgres84 extends Postgres90 { 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}%' "; - } + 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"; + $sql .= "ORDER BY type, schemaname, relname, name"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Capabilities + // Capabilities - function hasByteaHexDefault() {return false;} + public function hasByteaHexDefault() + {return false;} } diff --git a/src/database/Postgres90.php b/src/database/Postgres90.php index ee6b6586..8777198d 100644 --- a/src/database/Postgres90.php +++ b/src/database/Postgres90.php @@ -1,22 +1,15 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 9.0 support * * $Id: Postgres82.php,v 1.10 2007/12/28 16:21:25 ioguix Exp $ */ -class Postgres90 extends Postgres91 { - - var $major_version = 9.0; - - // Help functions - - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc90.php'; - return $this->help_page; - } +class Postgres90 extends Postgres91 +{ - // Capabilities + public $major_version = 9.0; } diff --git a/src/database/Postgres91.php b/src/database/Postgres91.php index 3e44a28f..2b108bd7 100644 --- a/src/database/Postgres91.php +++ b/src/database/Postgres91.php @@ -1,84 +1,83 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 9.1 support * * $Id: Postgres82.php,v 1.10 2007/12/28 16:21:25 ioguix Exp $ */ -class Postgres91 extends Postgres92 { - - var $major_version = 9.1; - - // Help functions - - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc91.php'; - return $this->help_page; - } - - // Administration functions - /** - * 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 datname, usename, procpid AS pid, waiting, current_query AS query, query_start +class Postgres91 extends Postgres92 +{ + + public $major_version = 9.1; + + // Administration functions + /** + * Returns all available process information. + * @param $database (optional) Find only connections to specified database + * @return A recordset + */ + public function getProcesses($database = null) + { + if ($database === null) { + $sql = "SELECT datname, usename, procpid AS pid, waiting, current_query AS query, query_start FROM pg_catalog.pg_stat_activity ORDER BY datname, usename, procpid"; - } else { - $this->clean($database); - $sql = "SELECT datname, usename, procpid AS pid, waiting, current_query AS query, query_start + } else { + $this->clean($database); + $sql = "SELECT datname, usename, procpid AS pid, waiting, current_query AS query, query_start FROM pg_catalog.pg_stat_activity WHERE datname='{$database}' ORDER BY usename, procpid"; - } + } - $rc = $this->selectSet($sql); + $rc = $this->selectSet($sql); - return $rc; - } + return $rc; + } - // Tablespace functions + // 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) { - $conf = $this->conf; + /** + * Retrieves information for all tablespaces + * @param $all Include all tablespaces (necessary when moving objects back to the default space) + * @return A recordset + */ + public function getTablespaces($all = false) + { + $conf = $this->conf; - $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation, + $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 AND pd.classoid='pg_tablespace'::regclass) AS spccomment FROM pg_catalog.pg_tablespace"; - if (!$conf['show_system'] && !$all) { - $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$'; - } + if (!$conf['show_system'] && !$all) { + $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$'; + } - $sql .= " ORDER BY spcname"; + $sql .= " ORDER BY spcname"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - /** - * Retrieves a tablespace's information - * @return A recordset - */ - function getTablespace($spcname) { - $this->clean($spcname); + /** + * Retrieves a tablespace's information + * @return A recordset + */ + public function getTablespace($spcname) + { + $this->clean($spcname); - $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation, + $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 AND pd.classoid='pg_tablespace'::regclass) AS spccomment FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'"; - return $this->selectSet($sql); - } + return $this->selectSet($sql); + } - // Capabilities - function hasUserSignals() {return false;} + // Capabilities + public function hasUserSignals() + {return false;} } diff --git a/src/database/Postgres92.php b/src/database/Postgres92.php index c1122cf5..fb53098f 100644 --- a/src/database/Postgres92.php +++ b/src/database/Postgres92.php @@ -1,19 +1,14 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 9.2 support * */ -class Postgres92 extends Postgres93 { - - var $major_version = 9.2; - - // Help functions +class Postgres92 extends Postgres93 +{ - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc92.php'; - return $this->help_page; - } + public $major_version = 9.2; } diff --git a/src/database/Postgres93.php b/src/database/Postgres93.php index 7ec326f8..e1477a13 100644 --- a/src/database/Postgres93.php +++ b/src/database/Postgres93.php @@ -1,19 +1,14 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 9.3 support * */ -class Postgres93 extends Postgres { - - var $major_version = 9.3; - - // Help functions +class Postgres93 extends Postgres94 +{ - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc93.php'; - return $this->help_page; - } + public $major_version = 9.3; } diff --git a/src/database/Postgres94.php b/src/database/Postgres94.php index f1632507..f10fefff 100644 --- a/src/database/Postgres94.php +++ b/src/database/Postgres94.php @@ -1,19 +1,14 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 9.4 support * */ -class Postgres94 extends Postgres { - - var $major_version = 9.4; - - // Help functions +class Postgres94 extends Postgres95 +{ - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc94.php'; - return $this->help_page; - } + public $major_version = 9.4; } diff --git a/src/database/Postgres95.php b/src/database/Postgres95.php index 7b6d242b..30475268 100644 --- a/src/database/Postgres95.php +++ b/src/database/Postgres95.php @@ -1,19 +1,14 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 9.5 support * */ -class Postgres95 extends Postgres { - - var $major_version = 9.5; - - // Help functions +class Postgres95 extends Postgres96 +{ - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc95.php'; - return $this->help_page; - } + public $major_version = 9.5; } diff --git a/src/database/Postgres96.php b/src/database/Postgres96.php index d2c3468a..31a87329 100644 --- a/src/database/Postgres96.php +++ b/src/database/Postgres96.php @@ -1,45 +1,40 @@ <?php namespace PHPPgAdmin\Database; + /** * PostgreSQL 9.5 support * */ -class Postgres96 extends Postgres { - - var $major_version = 9.6; - - // Help functions - - function getHelpPages() { - include_once BASE_PATH . '/src/help/PostgresDoc96.php'; - - return $this->help_page; - } - - // Administration functions - /** - * 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 datid, datname, pid, usename, application_name, client_addr, state, wait_event_type, wait_event, state_change as query_start, +class Postgres96 extends Postgres +{ + + public $major_version = 9.6; + + // Administration functions + /** + * Returns all available process information. + * @param $database (optional) Find only connections to specified database + * @return A recordset + */ + public function getProcesses($database = null) + { + if ($database === null) { + $sql = "SELECT datid, datname, pid, usename, application_name, client_addr, state, wait_event_type, wait_event, state_change as query_start, CASE when state='idle in transaction' then ' in transaction' else query end as query FROM pg_catalog.pg_stat_activity ORDER BY datname, usename, pid"; - } else { - $this->clean($database); - $sql = "SELECT datid, datname, pid, usename, application_name, client_addr, state, wait_event_type, wait_event, state_change as query_start, + } else { + $this->clean($database); + $sql = "SELECT datid, datname, pid, usename, application_name, client_addr, state, wait_event_type, wait_event, state_change as query_start, CASE when state='idle in transaction' then ' in transaction' else query end as query FROM pg_catalog.pg_stat_activity WHERE datname='{$database}' ORDER BY usename, pid"; - } + } - $rc = $this->selectSet($sql); + $rc = $this->selectSet($sql); - return $rc; - } + return $rc; + } } diff --git a/src/help/PostgresDoc74.php b/src/help/PostgresDoc74.php index bfc05f53..134e45c7 100644 --- a/src/help/PostgresDoc74.php +++ b/src/help/PostgresDoc74.php @@ -1,155 +1,177 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 7.4 documentation */ - -$this->help_base = sprintf($this->conf['help_base'], strval($this->major_version)); - -# TODO: Check and fix links - -$this->help_page = [ - - 'pg.database' => 'managing-databases.html', - 'pg.database.create' => ['sql-createdatabase.html', 'manage-ag-createdb.html'], - 'pg.database.alter' => 'sql-alterdatabase.html', - 'pg.database.drop' => ['sql-dropdatabase.html', 'manage-ag-dropdb.html'], - - 'pg.admin.analyze' => 'sql-analyze.html', - 'pg.admin.vacuum' => 'sql-vacuum.html', - - 'pg.cast' => ['sql-expressions.html#SQL-SYNTAX-TYPE-CASTS', 'sql-createcast.html'], - 'pg.cast.create' => 'sql-createcast.html', - 'pg.cast.drop' => 'sql-dropcast.html', - - 'pg.column.add' => ['ddl-alter.html#AEN2115', 'sql-altertable.html'], - 'pg.column.alter' => ['ddl-alter.html', 'sql-altertable.html'], - 'pg.column.drop' => ['ddl-alter.html#AEN2124', 'sql-altertable.html'], - - 'pg.constraint' => 'ddl-constraints.html', - 'pg.constraint.add' => 'ddl-alter.html#AEN2131', - 'pg.constraint.check' => 'ddl-constraints.html#AEN1895', - 'pg.constraint.drop' => 'ddl-alter.html#AEN2140', - 'pg.constraint.foreign_key' => 'ddl-constraints.html#DDL-CONSTRAINTS-FK', - 'pg.constraint.primary_key' => 'ddl-constraints.html#AEN1972', - 'pg.constraint.unique_key' => 'ddl-constraints.html#AEN1950', - - 'pg.conversion' => 'multibyte.html', - 'pg.conversion.alter' => 'sql-alterconversion.html', - 'pg.conversion.create' => 'sql-createconversion.html', - 'pg.conversion.drop' => 'sql-dropconversion.html', - - 'pg.domain' => 'extend-type-system.html#AEN28657', - 'pg.domain.alter' => 'sql-alterdomain.html', - 'pg.domain.create' => 'sql-createdomain.html', - 'pg.domain.drop' => 'sql-dropdomain.html', - - 'pg.function' => ['xfunc.html', 'functions.html', 'sql-expressions.html#AEN1599'], - 'pg.function.alter' => 'sql-alterfunction.html', - 'pg.function.create' => 'sql-createfunction.html', - 'pg.function.create.c' => ['xfunc-c.html', 'sql-createfunction.html'], - 'pg.function.create.internal' => ['xfunc-internal.html', 'sql-createfunction.html'], - 'pg.function.create.pl' => ['xfunc-sql.html', 'xfunc-pl.html', 'sql-createfunction.html'], - 'pg.function.drop' => 'sql-dropfunction.html', - - 'pg.group' => 'groups.html', - 'pg.group.alter' => ['sql-altergroup.html', 'groups.html'], - 'pg.group.create' => 'sql-creategroup.html', - 'pg.group.drop' => 'sql-dropgroup.html', - - 'pg.index' => 'indexes.html', - 'pg.index.cluster' => 'sql-cluster.html', - 'pg.index.drop' => 'sql-dropindex.html', - 'pg.index.create' => 'sql-createindex.html', - 'pg.index.reindex' => 'sql-reindex.html', - - 'pg.language' => 'xplang.html', - 'pg.language.alter' => 'sql-alterlanguage.html', - 'pg.language.create' => 'sql-createlanguage.html', - 'pg.language.drop' => 'sql-droplanguage.html', - - 'pg.opclass' => 'indexes-opclass.html', - 'pg.opclass.alter' => 'sql-alteropclass.html', - 'pg.opclass.create' => 'sql-createopclass.html', - 'pg.opclass.drop' => 'sql-dropopclass.html', - - 'pg.operator' => ['xoper.html', 'functions.html', 'sql-expressions.html#AEN1570'], - 'pg.operator.alter' => 'sql-alteroperator.html', - 'pg.operator.create' => 'sql-createoperator.html', - 'pg.operator.drop' => 'sql-dropoperator.html', - - 'pg.pl' => 'xplang.html', - 'pg.pl.plperl' => 'plperl.html', - 'pg.pl.plpgsql' => 'plpgsql.html', - 'pg.pl.plpython' => 'plpython.html', - 'pg.pl.pltcl' => 'pltcl.html', - - 'pg.privilege' => ['privileges.html', 'ddl-priv.html'], - 'pg.privilege.grant' => 'sql-grant.html', - 'pg.privilege.revoke' => 'sql-revoke.html', - - 'pg.process' => 'monitoring.html', - - 'pg.rule' => 'rules.html', - 'pg.rule.create' => 'sql-createrule.html', - 'pg.rule.drop' => 'sql-droprule.html', - - 'pg.schema' => 'ddl-schemas.html', - 'pg.schema.alter' => 'sql-alterschema.html', - 'pg.schema.create' => ['sql-createschema.html', 'ddl-schemas.html#DDL-SCHEMAS-CREATE'], - 'pg.schema.drop' => 'sql-dropschema.html', - 'pg.schema.search_path' => 'ddl-schemas.html#DDL-SCHEMAS-PATH', - - 'pg.sequence' => 'functions-sequence.html', - 'pg.sequence.alter' => 'sql-altersequence.html', - 'pg.sequence.create' => 'sql-createsequence.html', - 'pg.sequence.drop' => 'sql-dropsequence.html', - - 'pg.sql' => ['sql.html', 'sql-commands.html'], - 'pg.sql.insert' => 'sql-insert.html', - 'pg.sql.select' => 'sql-select.html', - 'pg.sql.update' => 'sql-update.html', - - 'pg.table' => 'ddl.html#DDL-BASICS', - 'pg.table.alter' => 'sql-altertable.html', - 'pg.table.create' => 'sql-createtable.html', - 'pg.table.drop' => 'sql-droptable.html', - 'pg.table.empty' => 'sql-truncate.html', - - 'pg.tablespace' => 'manage-ag-tablespaces.html', - 'pg.tablespace.alter' => 'sql-altertablespace.html', - 'pg.tablespace.create' => 'sql-createtablespace.html', - 'pg.tablespace.drop' => 'sql-droptablespace.html', - - 'pg.trigger' => 'triggers.html', - 'pg.trigger.alter' => 'sql-altertrigger.html', - 'pg.trigger.create' => 'sql-createtrigger.html', - 'pg.trigger.drop' => 'sql-droptrigger.html', - - 'pg.type' => ['xtypes.html', 'datatype.html', 'extend-type-system.html'], - 'pg.type.alter' => 'sql-altertype.html', - 'pg.type.create' => 'sql-createtype.html', - 'pg.type.drop' => 'sql-droptype.html', - - 'pg.user.alter' => ['sql-alteruser.html', 'user-attributes.html'], - 'pg.user.create' => ['sql-createuser.html', 'user-manag.html#DATABASE-USERS'], - 'pg.user.drop' => ['sql-dropuser.html', 'user-manag.html#DATABASE-USERS'], - - 'pg.variable' => 'runtime-config.html', - - 'pg.view' => 'tutorial-views.html', - 'pg.view.alter' => ['sql-createview.html', 'sql-altertable.html'], - 'pg.view.create' => 'sql-createview.html', - 'pg.view.drop' => 'sql-dropview.html', - - 'pg.aggregate' => ['xaggr.html', 'tutorial-agg.html', 'functions-aggregate.html', 'sql-expressions.html#SYNTAX-AGGREGATES'], - 'pg.aggregate.create' => 'sql-createaggregate.html', - 'pg.aggregate.drop' => 'sql-dropaggregate.html', - 'pg.aggregate.alter' => 'sql-alteraggregate.html', - - 'pg.server' => 'admin.html', - - 'pg.user' => 'user-manag.html', - - 'pg.locks' => 'view-pg-locks.html', -]; +class PostgresDoc74 +{ + private $major_version; + private $help_page; + private $conf; + + public function __construct($conf, $major_version) + { + $this->major_version = $major_version; + $this->conf = $conf; + + # TODO: Check and fix links + $this->help_page = [ + + 'pg.database' => 'managing-databases.html', + 'pg.database.create' => ['sql-createdatabase.html', 'manage-ag-createdb.html'], + 'pg.database.alter' => 'sql-alterdatabase.html', + 'pg.database.drop' => ['sql-dropdatabase.html', 'manage-ag-dropdb.html'], + + 'pg.admin.analyze' => 'sql-analyze.html', + 'pg.admin.vacuum' => 'sql-vacuum.html', + + 'pg.cast' => ['sql-expressions.html#SQL-SYNTAX-TYPE-CASTS', 'sql-createcast.html'], + 'pg.cast.create' => 'sql-createcast.html', + 'pg.cast.drop' => 'sql-dropcast.html', + + 'pg.column.add' => ['ddl-alter.html#AEN2115', 'sql-altertable.html'], + 'pg.column.alter' => ['ddl-alter.html', 'sql-altertable.html'], + 'pg.column.drop' => ['ddl-alter.html#AEN2124', 'sql-altertable.html'], + + 'pg.constraint' => 'ddl-constraints.html', + 'pg.constraint.add' => 'ddl-alter.html#AEN2131', + 'pg.constraint.check' => 'ddl-constraints.html#AEN1895', + 'pg.constraint.drop' => 'ddl-alter.html#AEN2140', + 'pg.constraint.foreign_key' => 'ddl-constraints.html#DDL-CONSTRAINTS-FK', + 'pg.constraint.primary_key' => 'ddl-constraints.html#AEN1972', + 'pg.constraint.unique_key' => 'ddl-constraints.html#AEN1950', + + 'pg.conversion' => 'multibyte.html', + 'pg.conversion.alter' => 'sql-alterconversion.html', + 'pg.conversion.create' => 'sql-createconversion.html', + 'pg.conversion.drop' => 'sql-dropconversion.html', + + 'pg.domain' => 'extend-type-system.html#AEN28657', + 'pg.domain.alter' => 'sql-alterdomain.html', + 'pg.domain.create' => 'sql-createdomain.html', + 'pg.domain.drop' => 'sql-dropdomain.html', + + 'pg.function' => ['xfunc.html', 'functions.html', 'sql-expressions.html#AEN1599'], + 'pg.function.alter' => 'sql-alterfunction.html', + 'pg.function.create' => 'sql-createfunction.html', + 'pg.function.create.c' => ['xfunc-c.html', 'sql-createfunction.html'], + 'pg.function.create.internal' => ['xfunc-internal.html', 'sql-createfunction.html'], + 'pg.function.create.pl' => ['xfunc-sql.html', 'xfunc-pl.html', 'sql-createfunction.html'], + 'pg.function.drop' => 'sql-dropfunction.html', + + 'pg.group' => 'groups.html', + 'pg.group.alter' => ['sql-altergroup.html', 'groups.html'], + 'pg.group.create' => 'sql-creategroup.html', + 'pg.group.drop' => 'sql-dropgroup.html', + + 'pg.index' => 'indexes.html', + 'pg.index.cluster' => 'sql-cluster.html', + 'pg.index.drop' => 'sql-dropindex.html', + 'pg.index.create' => 'sql-createindex.html', + 'pg.index.reindex' => 'sql-reindex.html', + + 'pg.language' => 'xplang.html', + 'pg.language.alter' => 'sql-alterlanguage.html', + 'pg.language.create' => 'sql-createlanguage.html', + 'pg.language.drop' => 'sql-droplanguage.html', + + 'pg.opclass' => 'indexes-opclass.html', + 'pg.opclass.alter' => 'sql-alteropclass.html', + 'pg.opclass.create' => 'sql-createopclass.html', + 'pg.opclass.drop' => 'sql-dropopclass.html', + + 'pg.operator' => ['xoper.html', 'functions.html', 'sql-expressions.html#AEN1570'], + 'pg.operator.alter' => 'sql-alteroperator.html', + 'pg.operator.create' => 'sql-createoperator.html', + 'pg.operator.drop' => 'sql-dropoperator.html', + + 'pg.pl' => 'xplang.html', + 'pg.pl.plperl' => 'plperl.html', + 'pg.pl.plpgsql' => 'plpgsql.html', + 'pg.pl.plpython' => 'plpython.html', + 'pg.pl.pltcl' => 'pltcl.html', + + 'pg.privilege' => ['privileges.html', 'ddl-priv.html'], + 'pg.privilege.grant' => 'sql-grant.html', + 'pg.privilege.revoke' => 'sql-revoke.html', + + 'pg.process' => 'monitoring.html', + + 'pg.rule' => 'rules.html', + 'pg.rule.create' => 'sql-createrule.html', + 'pg.rule.drop' => 'sql-droprule.html', + + 'pg.schema' => 'ddl-schemas.html', + 'pg.schema.alter' => 'sql-alterschema.html', + 'pg.schema.create' => ['sql-createschema.html', 'ddl-schemas.html#DDL-SCHEMAS-CREATE'], + 'pg.schema.drop' => 'sql-dropschema.html', + 'pg.schema.search_path' => 'ddl-schemas.html#DDL-SCHEMAS-PATH', + + 'pg.sequence' => 'functions-sequence.html', + 'pg.sequence.alter' => 'sql-altersequence.html', + 'pg.sequence.create' => 'sql-createsequence.html', + 'pg.sequence.drop' => 'sql-dropsequence.html', + + 'pg.sql' => ['sql.html', 'sql-commands.html'], + 'pg.sql.insert' => 'sql-insert.html', + 'pg.sql.select' => 'sql-select.html', + 'pg.sql.update' => 'sql-update.html', + + 'pg.table' => 'ddl.html#DDL-BASICS', + 'pg.table.alter' => 'sql-altertable.html', + 'pg.table.create' => 'sql-createtable.html', + 'pg.table.drop' => 'sql-droptable.html', + 'pg.table.empty' => 'sql-truncate.html', + + 'pg.tablespace' => 'manage-ag-tablespaces.html', + 'pg.tablespace.alter' => 'sql-altertablespace.html', + 'pg.tablespace.create' => 'sql-createtablespace.html', + 'pg.tablespace.drop' => 'sql-droptablespace.html', + + 'pg.trigger' => 'triggers.html', + 'pg.trigger.alter' => 'sql-altertrigger.html', + 'pg.trigger.create' => 'sql-createtrigger.html', + 'pg.trigger.drop' => 'sql-droptrigger.html', + + 'pg.type' => ['xtypes.html', 'datatype.html', 'extend-type-system.html'], + 'pg.type.alter' => 'sql-altertype.html', + 'pg.type.create' => 'sql-createtype.html', + 'pg.type.drop' => 'sql-droptype.html', + + 'pg.user.alter' => ['sql-alteruser.html', 'user-attributes.html'], + 'pg.user.create' => ['sql-createuser.html', 'user-manag.html#DATABASE-USERS'], + 'pg.user.drop' => ['sql-dropuser.html', 'user-manag.html#DATABASE-USERS'], + + 'pg.variable' => 'runtime-config.html', + + 'pg.view' => 'tutorial-views.html', + 'pg.view.alter' => ['sql-createview.html', 'sql-altertable.html'], + 'pg.view.create' => 'sql-createview.html', + 'pg.view.drop' => 'sql-dropview.html', + + 'pg.aggregate' => ['xaggr.html', 'tutorial-agg.html', 'functions-aggregate.html', 'sql-expressions.html#SYNTAX-AGGREGATES'], + 'pg.aggregate.create' => 'sql-createaggregate.html', + 'pg.aggregate.drop' => 'sql-dropaggregate.html', + 'pg.aggregate.alter' => 'sql-alteraggregate.html', + + 'pg.server' => 'admin.html', + + 'pg.user' => 'user-manag.html', + + 'pg.locks' => 'view-pg-locks.html', + ]; + } + + public function getHelpBase() + { + + return sprintf($this->conf['help_base'], strval($this->major_version)); + } + + public function getHelpPage() + { + return $this->help_page; + } +} diff --git a/src/help/PostgresDoc80.php b/src/help/PostgresDoc80.php index f3f64fe5..53583d82 100644 --- a/src/help/PostgresDoc80.php +++ b/src/help/PostgresDoc80.php @@ -1,24 +1,33 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 8.0 documentation * * $Id: PostgresDoc80.php,v 1.5 2005/02/16 10:27:44 jollytoad Exp $ */ +class PostgresDoc80 extends PostgresDoc74 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); -include BASE_PATH . '/src/help/PostgresDoc74.php'; + $this->help_page['pg.column.add'][0] = 'ddl-alter.html#AEN2217'; + $this->help_page['pg.column.drop'][0] = 'ddl-alter.html#AEN2226'; -$this->help_page['pg.column.add'][0] = 'ddl-alter.html#AEN2217'; -$this->help_page['pg.column.drop'][0] = 'ddl-alter.html#AEN2226'; + $this->help_page['pg.constraint.add'] = 'ddl-alter.html#AEN2217'; + $this->help_page['pg.constraint.check'] = 'ddl-constraints.html#AEN1978'; + $this->help_page['pg.constraint.drop'] = 'ddl-alter.html#AEN2226'; + $this->help_page['pg.constraint.primary_key'] = 'ddl-constraints.html#AEN2055'; + $this->help_page['pg.constraint.unique_key'] = 'ddl-constraints.html#AEN2033'; -$this->help_page['pg.constraint.add'] = 'ddl-alter.html#AEN2217'; -$this->help_page['pg.constraint.check'] = 'ddl-constraints.html#AEN1978'; -$this->help_page['pg.constraint.drop'] = 'ddl-alter.html#AEN2226'; -$this->help_page['pg.constraint.primary_key'] = 'ddl-constraints.html#AEN2055'; -$this->help_page['pg.constraint.unique_key'] = 'ddl-constraints.html#AEN2033'; + $this->help_page['pg.domain'] = 'extend-type-system.html#AEN27940'; -$this->help_page['pg.domain'] = 'extend-type-system.html#AEN27940'; + $this->help_page['pg.function'][2] = 'sql-expressions.html#AEN1652'; -$this->help_page['pg.function'][2] = 'sql-expressions.html#AEN1652'; + $this->help_page['pg.operator'][2] = 'sql-expressions.html#AEN1623'; + } -$this->help_page['pg.operator'][2] = 'sql-expressions.html#AEN1623'; +} diff --git a/src/help/PostgresDoc81.php b/src/help/PostgresDoc81.php index ec119acf..df4c4b00 100644 --- a/src/help/PostgresDoc81.php +++ b/src/help/PostgresDoc81.php @@ -1,14 +1,23 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 8.1 documentation * * $Id: PostgresDoc81.php,v 1.3 2006/12/28 04:26:55 xzilla Exp $ */ +class PostgresDoc81 extends PostgresDoc80 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); -include BASE_PATH . '/src/help/PostgresDoc80.php'; + $this->help_page['pg.role'] = 'user-manag.html'; + $this->help_page['pg.role.create'] = ['sql-createrole.html', 'user-manag.html#DATABASE-ROLES']; + $this->help_page['pg.role.alter'] = ['sql-alterrole.html', 'role-attributes.html']; + $this->help_page['pg.role.drop'] = ['sql-droprole.html', 'user-manag.html#DATABASE-ROLES']; + } -$this->help_page['pg.role'] = 'user-manag.html'; -$this->help_page['pg.role.create'] = ['sql-createrole.html', 'user-manag.html#DATABASE-ROLES']; -$this->help_page['pg.role.alter'] = ['sql-alterrole.html', 'role-attributes.html']; -$this->help_page['pg.role.drop'] = ['sql-droprole.html', 'user-manag.html#DATABASE-ROLES']; +} diff --git a/src/help/PostgresDoc82.php b/src/help/PostgresDoc82.php index 529ad377..b4dc2f79 100644 --- a/src/help/PostgresDoc82.php +++ b/src/help/PostgresDoc82.php @@ -1,9 +1,18 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 8.2 documentation * * $Id: PostgresDoc82.php,v 1.3 2007/11/30 15:27:26 soranzo Exp $ */ +class PostgresDoc82 extends PostgresDoc81 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); + } -include BASE_PATH . '/src/help/PostgresDoc81.php'; +} diff --git a/src/help/PostgresDoc83.php b/src/help/PostgresDoc83.php index 06ef537b..f3edd313 100644 --- a/src/help/PostgresDoc83.php +++ b/src/help/PostgresDoc83.php @@ -1,24 +1,33 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 8.3 documentation * * $Id: PostgresDoc83.php,v 1.3 2008/03/17 21:35:48 ioguix Exp $ */ +class PostgresDoc83 extends PostgresDoc82 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); -include BASE_PATH . '/src/help/PostgresDoc82.php'; + $this->help_page['pg.fts'] = 'textsearch.html'; -$this->help_page['pg.fts'] = 'textsearch.html'; + $this->help_page['pg.ftscfg'] = 'textsearch-intro.html#TEXTSEARCH-INTRO-CONFIGURATIONS'; + $this->help_page['pg.ftscfg.example'] = 'textsearch-configuration.html'; + $this->help_page['pg.ftscfg.drop'] = 'sql-droptsconfig.html'; + $this->help_page['pg.ftscfg.create'] = 'sql-createtsconfig.html'; + $this->help_page['pg.ftscfg.alter'] = 'sql-altertsconfig.html'; -$this->help_page['pg.ftscfg'] = 'textsearch-intro.html#TEXTSEARCH-INTRO-CONFIGURATIONS'; -$this->help_page['pg.ftscfg.example'] = 'textsearch-configuration.html'; -$this->help_page['pg.ftscfg.drop'] = 'sql-droptsconfig.html'; -$this->help_page['pg.ftscfg.create'] = 'sql-createtsconfig.html'; -$this->help_page['pg.ftscfg.alter'] = 'sql-altertsconfig.html'; + $this->help_page['pg.ftsdict'] = 'textsearch-dictionaries.html'; + $this->help_page['pg.ftsdict.drop'] = 'sql-droptsdictionary.html'; + $this->help_page['pg.ftsdict.create'] = ['sql-createtsdictionary.html', 'sql-createtstemplate.html']; + $this->help_page['pg.ftsdict.alter'] = 'sql-altertsdictionary.html'; -$this->help_page['pg.ftsdict'] = 'textsearch-dictionaries.html'; -$this->help_page['pg.ftsdict.drop'] = 'sql-droptsdictionary.html'; -$this->help_page['pg.ftsdict.create'] = ['sql-createtsdictionary.html', 'sql-createtstemplate.html']; -$this->help_page['pg.ftsdict.alter'] = 'sql-altertsdictionary.html'; + $this->help_page['pg.ftsparser'] = 'textsearch-parsers.html'; + } -$this->help_page['pg.ftsparser'] = 'textsearch-parsers.html'; +} diff --git a/src/help/PostgresDoc84.php b/src/help/PostgresDoc84.php index 34cbbb55..2a1d54ae 100644 --- a/src/help/PostgresDoc84.php +++ b/src/help/PostgresDoc84.php @@ -1,9 +1,18 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 8.4 documentation * * $Id: PostgresDoc84.php,v 1.3 2008/11/18 21:35:48 ioguix Exp $ */ +class PostgresDoc84 extends PostgresDoc83 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); + } -include BASE_PATH . '/src/help/PostgresDoc83.php'; +} diff --git a/src/help/PostgresDoc90.php b/src/help/PostgresDoc90.php index 519dbee3..aed033fb 100644 --- a/src/help/PostgresDoc90.php +++ b/src/help/PostgresDoc90.php @@ -1,9 +1,18 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 9.0 documentation * * $Id: PostgresDoc84.php,v 1.3 2008/11/18 21:35:48 ioguix Exp $ */ +class PostgresDoc90 extends PostgresDoc84 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); + } -include BASE_PATH . '/src/help/PostgresDoc84.php'; +} diff --git a/src/help/PostgresDoc91.php b/src/help/PostgresDoc91.php index 226a11d8..494bc795 100644 --- a/src/help/PostgresDoc91.php +++ b/src/help/PostgresDoc91.php @@ -1,9 +1,18 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 9.1 documentation * * $Id: PostgresDoc84.php,v 1.3 2008/11/18 21:35:48 ioguix Exp $ */ +class PostgresDoc91 extends PostgresDoc90 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); + } -include BASE_PATH . '/src/help/PostgresDoc90.php'; +} diff --git a/src/help/PostgresDoc92.php b/src/help/PostgresDoc92.php index 08b76448..67f29d89 100644 --- a/src/help/PostgresDoc92.php +++ b/src/help/PostgresDoc92.php @@ -1,9 +1,18 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 9.2 documentation * * $Id: PostgresDoc84.php,v 1.3 2008/11/18 21:35:48 ioguix Exp $ */ +class PostgresDoc92 extends PostgresDoc91 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); + } -include BASE_PATH . '/src/help/PostgresDoc91.php'; +} diff --git a/src/help/PostgresDoc93.php b/src/help/PostgresDoc93.php index 0de70635..a43c12f3 100644 --- a/src/help/PostgresDoc93.php +++ b/src/help/PostgresDoc93.php @@ -1,9 +1,18 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 9.3 documentation * * $Id: PostgresDoc84.php,v 1.3 2008/11/18 21:35:48 ioguix Exp $ */ +class PostgresDoc93 extends PostgresDoc92 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); + } -include BASE_PATH . '/src/help/PostgresDoc92.php'; +} diff --git a/src/help/PostgresDoc94.php b/src/help/PostgresDoc94.php index 25116772..10b64f18 100644 --- a/src/help/PostgresDoc94.php +++ b/src/help/PostgresDoc94.php @@ -1,9 +1,18 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 9.4 documentation * * $Id: PostgresDoc84.php,v 1.3 2008/11/18 21:35:48 ioguix Exp $ */ +class PostgresDoc94 extends PostgresDoc93 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); + } -include BASE_PATH . '/src/help/PostgresDoc93.php'; +} diff --git a/src/help/PostgresDoc95.php b/src/help/PostgresDoc95.php index 9a71a862..a456dbfb 100644 --- a/src/help/PostgresDoc95.php +++ b/src/help/PostgresDoc95.php @@ -1,10 +1,20 @@ <?php +namespace PHPPgAdmin\Help; + /** * Help links for PostgreSQL 9.5 documentation * */ +class PostgresDoc95 extends PostgresDoc94 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); + + $this->help_page['pg.matview'] = 'sql-creatematerializedview.html'; -include BASE_PATH . '/src/help/PostgresDoc94.php'; + } -$this->help_page['pg.matview'] = 'sql-creatematerializedview.html'; +} diff --git a/src/help/PostgresDoc96.php b/src/help/PostgresDoc96.php index 087dcd33..6206ef00 100644 --- a/src/help/PostgresDoc96.php +++ b/src/help/PostgresDoc96.php @@ -1,8 +1,17 @@ <?php +namespace PHPPgAdmin\Help; + /** - * Help links for PostgreSQL 9.5 documentation + * Help links for PostgreSQL 9.6 documentation * */ +class PostgresDoc96 extends PostgresDoc95 +{ + + public function __construct($conf, $major_version) + { + parent::__construct($conf, $major_version); + } -include BASE_PATH . '/src/help/PostgresDoc95.php'; +} |