8035 lines
238 KiB
PHP
8035 lines
238 KiB
PHP
<?php
|
|
|
|
/**
|
|
* A Class that implements the DB Interface for Postgres
|
|
* Note: This Class uses ADODB and returns RecordSets.
|
|
*
|
|
* $Id: Postgres.php,v 1.320 2008/02/20 20:43:09 ioguix Exp $
|
|
*/
|
|
|
|
include_once('./classes/database/ADODB_base.php');
|
|
|
|
class Postgres extends ADODB_base {
|
|
|
|
var $major_version = 9.0;
|
|
// Max object name length
|
|
var $_maxNameLen = 63;
|
|
// Store the current schema
|
|
var $_schema;
|
|
// Map of database encoding names to HTTP encoding names. If a
|
|
// database encoding does not appear in this list, then its HTTP
|
|
// encoding name is the same as its database encoding name.
|
|
var $codemap = array(
|
|
'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 = array('', '', '');
|
|
// Extra "magic" types. BIGSERIAL was added in PostgreSQL 7.2.
|
|
var $extraTypes = array('SERIAL', 'BIGSERIAL');
|
|
// Foreign key stuff. First element MUST be the default.
|
|
var $fkactions = array('NO ACTION', 'RESTRICT', 'CASCADE', 'SET NULL', 'SET DEFAULT');
|
|
var $fkdeferrable = array('NOT DEFERRABLE', 'DEFERRABLE');
|
|
var $fkinitial = array('INITIALLY IMMEDIATE', 'INITIALLY DEFERRED');
|
|
var $fkmatches = array('MATCH SIMPLE', 'MATCH FULL');
|
|
// Function properties
|
|
var $funcprops = array( array('', 'VOLATILE', 'IMMUTABLE', 'STABLE'),
|
|
array('', 'CALLED ON NULL INPUT', 'RETURNS NULL ON NULL INPUT'),
|
|
array('', 'SECURITY INVOKER', 'SECURITY DEFINER'));
|
|
// Default help URL
|
|
var $help_base;
|
|
// Help sub pages
|
|
var $help_page;
|
|
// Name of id column
|
|
var $id = 'oid';
|
|
// Supported join operations for use with view wizard
|
|
var $joinOps = array('INNER JOIN' => 'INNER JOIN', 'LEFT JOIN' => 'LEFT JOIN', 'RIGHT JOIN' => 'RIGHT JOIN', 'FULL JOIN' => 'FULL JOIN');
|
|
// Map of internal language name to syntax highlighting name
|
|
var $langmap = array(
|
|
'sql' => 'SQL',
|
|
'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 = array('abstime','aclitem','bigserial','boolean','bytea','cid','cidr','circle','date','float4','float8','gtsvector','inet','int2','int4','int8','macaddr','money','oid','path','polygon','refcursor','regclass','regoper','regoperator','regproc','regprocedure','regtype','reltime','serial','smgr','text','tid','tinterval','tsquery','tsvector','varbit','void','xid');
|
|
// List of all legal privileges that can be applied to different types
|
|
// of objects.
|
|
var $privlist = array(
|
|
'table' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'),
|
|
'view' => array('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'REFERENCES', 'TRIGGER', 'ALL PRIVILEGES'),
|
|
'sequence' => array('SELECT', 'UPDATE', 'ALL PRIVILEGES'),
|
|
'database' => array('CREATE', 'TEMPORARY', 'CONNECT', 'ALL PRIVILEGES'),
|
|
'function' => array('EXECUTE', 'ALL PRIVILEGES'),
|
|
'language' => array('USAGE', 'ALL PRIVILEGES'),
|
|
'schema' => array('CREATE', 'USAGE', 'ALL PRIVILEGES'),
|
|
'tablespace' => array('CREATE', 'ALL PRIVILEGES'),
|
|
'column' => array('SELECT', 'INSERT', 'UPDATE', 'REFERENCES','ALL PRIVILEGES')
|
|
);
|
|
// List of characters in acl lists and the privileges they
|
|
// refer to.
|
|
var $privmap = array(
|
|
'r' => 'SELECT',
|
|
'w' => 'UPDATE',
|
|
'a' => 'INSERT',
|
|
'd' => 'DELETE',
|
|
'D' => 'TRUNCATE',
|
|
'R' => 'RULE',
|
|
'x' => 'REFERENCES',
|
|
't' => 'TRIGGER',
|
|
'X' => 'EXECUTE',
|
|
'U' => 'USAGE',
|
|
'C' => 'CREATE',
|
|
'T' => 'TEMPORARY',
|
|
'c' => 'CONNECT'
|
|
);
|
|
// Rule action types
|
|
var $rule_events = array('SELECT', 'INSERT', 'UPDATE', 'DELETE');
|
|
// Select operators
|
|
var $selectOps = array('=' => 'i', '!=' => 'i', '<' => 'i', '>' => 'i', '<=' => 'i', '>=' => 'i',
|
|
'<<' => 'i', '>>' => 'i', '<<=' => 'i', '>>=' => 'i',
|
|
'LIKE' => 'i', 'NOT LIKE' => 'i', 'ILIKE' => 'i', 'NOT ILIKE' => 'i', 'SIMILAR TO' => 'i',
|
|
'NOT SIMILAR TO' => 'i', '~' => 'i', '!~' => 'i', '~*' => 'i', '!~*' => 'i',
|
|
'IS NULL' => 'p', 'IS NOT NULL' => 'p', 'IN' => 'x', 'NOT IN' => 'x',
|
|
'@@' => 'i', '@@@' => 'i', '@>' => 'i', '<@' => 'i',
|
|
'@@ to_tsquery' => 't', '@@@ to_tsquery' => 't', '@> to_tsquery' => 't', '<@ to_tsquery' => 't',
|
|
'@@ plainto_tsquery' => 't', '@@@ plainto_tsquery' => 't', '@> plainto_tsquery' => 't', '<@ plainto_tsquery' => 't');
|
|
// Array of allowed trigger events
|
|
var $triggerEvents= array('INSERT', 'UPDATE', 'DELETE', 'INSERT OR UPDATE', 'INSERT OR DELETE',
|
|
'DELETE OR UPDATE', 'INSERT OR DELETE OR UPDATE');
|
|
// When to execute the trigger
|
|
var $triggerExecTimes = array('BEFORE', 'AFTER');
|
|
// How often to execute the trigger
|
|
var $triggerFrequency = array('ROW','STATEMENT');
|
|
// Array of allowed type alignments
|
|
var $typAligns = array('char', 'int2', 'int4', 'double');
|
|
// The default type alignment
|
|
var $typAlignDef = 'int4';
|
|
// Default index type
|
|
var $typIndexDef = 'BTREE';
|
|
// Array of allowed index types
|
|
var $typIndexes = array('BTREE', 'RTREE', 'GIST', 'GIN', 'HASH');
|
|
// Array of allowed type storage attributes
|
|
var $typStorages = array('plain', 'external', 'extended', 'main');
|
|
// The default type storage
|
|
var $typStorageDef = 'plain';
|
|
|
|
/**
|
|
* Constructor
|
|
* @param $conn The database connection
|
|
*/
|
|
function Postgres($conn) {
|
|
$this->ADODB_base($conn);
|
|
}
|
|
|
|
// 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);
|
|
if (function_exists('pg_escape_string'))
|
|
$str = pg_escape_string($str);
|
|
else
|
|
$str = addslashes($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;
|
|
if (function_exists('pg_escape_string'))
|
|
$arr[$k] = pg_escape_string($v);
|
|
else
|
|
$arr[$k] = addslashes($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) {
|
|
if (function_exists('pg_escape_bytea'))
|
|
return stripslashes(pg_escape_bytea($data));
|
|
else {
|
|
$translations = array('\\a' => '\\007', '\\b' => '\\010', '\\t' => '\\011', '\\n' => '\\012', '\\v' => '\\013', '\\f' => '\\014', '\\r' => '\\015');
|
|
return strtr(addCSlashes($data, "\0..\37\177..\377"), $translations);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 = array()) {
|
|
global $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[]':
|
|
$value = $this->escapeBytea($value);
|
|
case 'text':
|
|
case 'text[]':
|
|
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 = array();
|
|
foreach ($this->help_page[$help] as $link) {
|
|
$urls[] = $this->help_base . $link;
|
|
}
|
|
return $urls;
|
|
} else
|
|
return $this->help_base . $this->help_page[$help];
|
|
} else
|
|
return null;
|
|
}
|
|
|
|
function getHelpPages() {
|
|
include_once('./help/PostgresDoc90.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) {
|
|
global $conf, $misc;
|
|
|
|
$server_info = $misc->getServerInfo();
|
|
|
|
if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser($server_info['username'])) {
|
|
$username = $server_info['username'];
|
|
$this->clean($username);
|
|
$clause = " AND pr.rolname='{$username}'";
|
|
}
|
|
else $clause = '';
|
|
|
|
if ($currentdatabase != NULL) {
|
|
$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) AS datcomment,
|
|
(SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace,
|
|
CASE WHEN pg_catalog.has_database_privilege(current_user, pdb.oid, 'CONNECT')
|
|
THEN pg_catalog.pg_database_size(pdb.oid)
|
|
ELSE -1 -- set this magic value, which we will convert to no access later
|
|
END as dbsize, pdb.datcollate, pdb.datctype
|
|
FROM pg_catalog.pg_database pdb
|
|
LEFT JOIN pg_catalog.pg_roles pr ON (pdb.datdba = pr.oid)
|
|
WHERE true
|
|
{$where}
|
|
{$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) 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() {
|
|
// Try to avoid a query if at all possible (5)
|
|
if (function_exists('pg_parameter_status')) {
|
|
$encoding = pg_parameter_status($this->conn->_connectionID, 'server_encoding');
|
|
if ($encoding !== false) return $encoding;
|
|
}
|
|
|
|
$sql = "SELECT getdatabaseencoding() AS encoding";
|
|
|
|
return $this->selectField($sql, 'encoding');
|
|
}
|
|
|
|
/**
|
|
* Returns the current default_with_oids setting
|
|
* @return default_with_oids setting
|
|
*/
|
|
function getDefaultWithOid() {
|
|
// Try to avoid a query if at all possible (5)
|
|
if (function_exists('pg_parameter_status')) {
|
|
$default = pg_parameter_status($this->conn->_connectionID, 'default_with_oids');
|
|
if ($default !== false) return $default;
|
|
}
|
|
|
|
$sql = "SHOW default_with_oids";
|
|
|
|
return $this->selectField($sql, 'default_with_oids');
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
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) {
|
|
global $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
|
|
SELECT CASE WHEN relkind='r' THEN 'TABLE' WHEN relkind='v' THEN 'VIEW' WHEN relkind='S' THEN 'SEQUENCE' END, pc.oid,
|
|
pn.nspname, NULL, pc.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn
|
|
WHERE pc.relnamespace=pn.oid AND relkind IN ('r', 'v', 'S') AND relname ILIKE {$term} {$where}
|
|
UNION ALL
|
|
SELECT CASE WHEN pc.relkind='r' THEN 'COLUMNTABLE' ELSE 'COLUMNVIEW' END, NULL, pn.nspname, pc.relname, pa.attname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
|
|
pg_catalog.pg_attribute pa WHERE pc.relnamespace=pn.oid AND pc.oid=pa.attrelid
|
|
AND pa.attname ILIKE {$term} AND pa.attnum > 0 AND NOT pa.attisdropped AND pc.relkind IN ('r', 'v') {$where}
|
|
UNION ALL
|
|
SELECT 'FUNCTION', pp.oid, pn.nspname, NULL, pp.proname || '(' || pg_catalog.oidvectortypes(pp.proargtypes) || ')' FROM pg_catalog.pg_proc pp, pg_catalog.pg_namespace pn
|
|
WHERE pp.pronamespace=pn.oid AND NOT pp.proisagg AND pp.proname ILIKE {$term} {$where}
|
|
UNION ALL
|
|
SELECT 'INDEX', NULL, pn.nspname, pc.relname, pc2.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
|
|
pg_catalog.pg_index pi, pg_catalog.pg_class pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pi.indrelid
|
|
AND pi.indexrelid=pc2.oid
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
|
|
ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
|
|
WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
|
|
)
|
|
AND pc2.relname ILIKE {$term} {$where}
|
|
UNION ALL
|
|
SELECT 'CONSTRAINTTABLE', NULL, pn.nspname, pc.relname, pc2.conname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
|
|
pg_catalog.pg_constraint pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pc2.conrelid AND pc2.conrelid != 0
|
|
AND CASE WHEN pc2.contype IN ('f', 'c') THEN TRUE ELSE NOT EXISTS (
|
|
SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
|
|
ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
|
|
WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p')
|
|
) END
|
|
AND pc2.conname ILIKE {$term} {$where}
|
|
UNION ALL
|
|
SELECT 'CONSTRAINTDOMAIN', pt.oid, pn.nspname, pt.typname, pc.conname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn,
|
|
pg_catalog.pg_constraint pc WHERE pt.typnamespace=pn.oid AND pt.oid=pc.contypid AND pc.contypid != 0
|
|
AND pc.conname ILIKE {$term} {$where}
|
|
UNION ALL
|
|
SELECT 'TRIGGER', NULL, pn.nspname, pc.relname, pt.tgname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn,
|
|
pg_catalog.pg_trigger pt WHERE pc.relnamespace=pn.oid AND pc.oid=pt.tgrelid
|
|
AND ( pt.tgconstraint = 0 OR NOT EXISTS
|
|
(SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
|
|
ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
|
|
WHERE d.classid = pt.tableoid AND d.objid = pt.oid AND d.deptype = 'i' AND c.contype = 'f'))
|
|
AND pt.tgname ILIKE {$term} {$where}
|
|
UNION ALL
|
|
SELECT 'RULETABLE', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
|
|
JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
|
|
LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
|
|
WHERE c.relkind='r' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
|
|
UNION ALL
|
|
SELECT 'RULEVIEW', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r
|
|
JOIN pg_catalog.pg_class c ON c.oid = r.ev_class
|
|
LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace
|
|
WHERE c.relkind='v' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where}
|
|
";
|
|
|
|
// Add advanced objects if show_advanced is set
|
|
if ($conf['show_advanced']) {
|
|
$sql .= "
|
|
UNION ALL
|
|
SELECT CASE WHEN pt.typtype='d' THEN 'DOMAIN' ELSE 'TYPE' END, pt.oid, pn.nspname, NULL,
|
|
pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
|
|
WHERE pt.typnamespace=pn.oid AND typname ILIKE {$term}
|
|
AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
|
|
{$where}
|
|
UNION ALL
|
|
SELECT 'OPERATOR', po.oid, pn.nspname, NULL, po.oprname FROM pg_catalog.pg_operator po, pg_catalog.pg_namespace pn
|
|
WHERE po.oprnamespace=pn.oid AND oprname ILIKE {$term} {$where}
|
|
UNION ALL
|
|
SELECT 'CONVERSION', pc.oid, pn.nspname, NULL, pc.conname FROM pg_catalog.pg_conversion pc,
|
|
pg_catalog.pg_namespace pn WHERE pc.connamespace=pn.oid AND conname ILIKE {$term} {$where}
|
|
UNION ALL
|
|
SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_catalog.pg_language pl
|
|
WHERE lanname ILIKE {$term} {$lan_where}
|
|
UNION ALL
|
|
SELECT DISTINCT ON (p.proname) 'AGGREGATE', p.oid, pn.nspname, NULL, p.proname FROM pg_catalog.pg_proc p
|
|
LEFT JOIN pg_catalog.pg_namespace pn ON p.pronamespace=pn.oid
|
|
WHERE p.proisagg AND p.proname ILIKE {$term} {$where}
|
|
UNION ALL
|
|
SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, pn.nspname, NULL, po.opcname FROM pg_catalog.pg_opclass po,
|
|
pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid
|
|
AND po.opcname ILIKE {$term} {$where}
|
|
";
|
|
}
|
|
// Otherwise just add domains
|
|
else {
|
|
$sql .= "
|
|
UNION ALL
|
|
SELECT 'DOMAIN', pt.oid, pn.nspname, NULL,
|
|
pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn
|
|
WHERE pt.typnamespace=pn.oid AND pt.typtype='d' AND typname ILIKE {$term}
|
|
AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid))
|
|
{$where}
|
|
";
|
|
}
|
|
|
|
if ($filter != '') {
|
|
// We use like to make RULE, CONSTRAINT and COLUMN searches work
|
|
$sql .= ") AS sub WHERE type LIKE '{$filter}%' ";
|
|
}
|
|
|
|
$sql .= "ORDER BY type, schemaname, relname, name";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns all available variable information.
|
|
* @return A recordset
|
|
*/
|
|
function getVariables() {
|
|
$sql = "SHOW ALL";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
// Schema functons
|
|
|
|
/**
|
|
* Return all schemas in the current database.
|
|
* @return All schemas, sorted alphabetically
|
|
*/
|
|
function getSchemas() {
|
|
global $conf, $slony;
|
|
|
|
if (!$conf['show_system']) {
|
|
$where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'";
|
|
if (isset($slony) && $slony->isEnabled()) {
|
|
$temp = $slony->slony_schema;
|
|
$this->clean($temp);
|
|
$where .= " AND nspname != '{$temp}'";
|
|
}
|
|
|
|
}
|
|
else $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
|
|
$sql = "
|
|
SELECT pn.nspname, pu.rolname AS nspowner,
|
|
pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment
|
|
FROM pg_catalog.pg_namespace pn
|
|
LEFT JOIN pg_catalog.pg_roles pu ON (pn.nspowner = pu.oid)
|
|
{$where}
|
|
ORDER BY nspname";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Return all information relating to a schema
|
|
* @param $schema The name of the schema
|
|
* @return Schema information
|
|
*/
|
|
function getSchemaByName($schema) {
|
|
$this->clean($schema);
|
|
$sql = "
|
|
SELECT nspname, nspowner, 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 = array();
|
|
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}'
|
|
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 = "
|
|
SELECT
|
|
c.relname, n.nspname, u.usename AS relowner,
|
|
pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
|
|
(SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
|
|
FROM pg_catalog.pg_class c
|
|
LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
|
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE c.relkind = 'r'
|
|
AND n.nspname = '{$c_schema}'
|
|
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
|
|
FROM pg_catalog.pg_tables
|
|
WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
ORDER BY schemaname, tablename";
|
|
} else {
|
|
$sql = "SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
|
|
pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment,
|
|
reltuples::bigint,
|
|
(SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace
|
|
FROM pg_catalog.pg_class c
|
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE c.relkind = 'r'
|
|
AND nspname='{$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 = "
|
|
SELECT
|
|
a.attname, a.attnum,
|
|
pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
|
|
a.atttypmod,
|
|
a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
|
|
a.attstattarget, a.attstorage, t.typstorage,
|
|
(
|
|
SELECT 1 FROM pg_catalog.pg_depend pd, pg_catalog.pg_class pc
|
|
WHERE pd.objid=pc.oid
|
|
AND pd.classid=pc.tableoid
|
|
AND pd.refclassid=pc.tableoid
|
|
AND pd.refobjid=a.attrelid
|
|
AND pd.refobjsubid=a.attnum
|
|
AND pd.deptype='i'
|
|
AND pc.relkind='S'
|
|
) IS NOT NULL AS attisserial,
|
|
pg_catalog.col_description(a.attrelid, a.attnum) AS comment
|
|
FROM
|
|
pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
|
|
ON a.attrelid=adef.adrelid
|
|
AND a.attnum=adef.adnum
|
|
LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
|
|
WHERE
|
|
a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
|
|
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
|
|
nspname = '{$c_schema}'))
|
|
AND a.attnum > 0 AND NOT a.attisdropped
|
|
ORDER BY a.attnum";
|
|
}
|
|
else {
|
|
$sql = "
|
|
SELECT
|
|
a.attname, a.attnum,
|
|
pg_catalog.format_type(a.atttypid, a.atttypmod) as type,
|
|
pg_catalog.format_type(a.atttypid, NULL) as base_type,
|
|
a.atttypmod,
|
|
a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc,
|
|
a.attstattarget, a.attstorage, t.typstorage,
|
|
pg_catalog.col_description(a.attrelid, a.attnum) AS comment
|
|
FROM
|
|
pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
|
|
ON a.attrelid=adef.adrelid
|
|
AND a.attnum=adef.adnum
|
|
LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
|
|
WHERE
|
|
a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
|
|
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
|
|
nspname = '{$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 = "
|
|
SELECT
|
|
pn.nspname, relname
|
|
FROM
|
|
pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
|
|
WHERE
|
|
pc.oid=pi.inhparent
|
|
AND pc.relnamespace=pn.oid
|
|
AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
|
|
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}'))
|
|
ORDER BY
|
|
pi.inhseqno
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Finds the names and schemas of child tables
|
|
* @param $table The table to find the children for
|
|
* @return A recordset
|
|
*/
|
|
function getTableChildren($table) {
|
|
$c_schema = $this->_schema;
|
|
$this->clean($c_schema);
|
|
$this->clean($table);
|
|
|
|
$sql = "
|
|
SELECT
|
|
pn.nspname, relname
|
|
FROM
|
|
pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn
|
|
WHERE
|
|
pc.oid=pi.inhrelid
|
|
AND pc.relnamespace=pn.oid
|
|
AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}'
|
|
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$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) {
|
|
// Nasty hack to support pre-7.4 PostgreSQL
|
|
if ($triggers->fields['tgdef'] !== null)
|
|
$sql .= $triggers->fields['tgdef'];
|
|
else
|
|
$sql .= $this->getTriggerDef($triggers->fields);
|
|
|
|
$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 = array();
|
|
for ($i = 0; $i < $fields; $i++) {
|
|
if (isset($primarykey[$i])) {
|
|
$primarykeycolumns[] = "\"{$field[$i]}\"";
|
|
}
|
|
}
|
|
if (count($primarykeycolumns) > 0) {
|
|
$sql .= ", PRIMARY KEY (" . implode(", ", $primarykeycolumns) . ")";
|
|
}
|
|
|
|
$sql .= ")";
|
|
|
|
// WITHOUT OIDS
|
|
if ($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 array();
|
|
|
|
$sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE
|
|
attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND
|
|
relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
|
|
AND attnum IN ('" . join("','", $atts) . "')";
|
|
|
|
$rs = $this->selectSet($sql);
|
|
if ($rs->recordCount() != sizeof($atts)) {
|
|
return -2;
|
|
}
|
|
else {
|
|
$temp = array();
|
|
while (!$rs->EOF) {
|
|
$temp[$rs->fields['attnum']] = $rs->fields['attname'];
|
|
$rs->moveNext();
|
|
}
|
|
return $temp;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
if ($status != 0) {
|
|
$this->rollbackTransaction();
|
|
return -6;
|
|
}
|
|
|
|
// Rename the column, if it has been changed
|
|
if ($column != $name) {
|
|
$status = $this->renameColumn($table, $column, $name);
|
|
if ($status != 0) {
|
|
$this->rollbackTransaction();
|
|
return -4;
|
|
}
|
|
}
|
|
|
|
$f_schema = $this->_schema;
|
|
$this->fieldClean($f_schema);
|
|
$this->fieldClean($name);
|
|
$this->fieldClean($table);
|
|
$this->fieldClean($column);
|
|
|
|
$toAlter = array();
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// Update the comment on the column
|
|
$status = $this->setComment('COLUMN', $name, $table, $comment);
|
|
if ($status != 0) {
|
|
$this->rollbackTransaction();
|
|
return -5;
|
|
}
|
|
|
|
return $this->endTransaction();
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
FROM pg_class c
|
|
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE c.relkind = 'r'::\"char\"
|
|
AND n.nspname NOT IN ('pg_catalog','information_schema')
|
|
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
|
|
FROM pg_class c
|
|
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE c.relkind = 'r'::\"char\"
|
|
AND n.nspname NOT IN ('pg_catalog','information_schema')
|
|
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 = array();
|
|
while (!$_autovacs->EOF) {
|
|
$_ = array(
|
|
'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();
|
|
}
|
|
|
|
include_once('./classes/ArrayRecordSet.php');
|
|
return new 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 = "
|
|
SELECT indrelid, indkey
|
|
FROM pg_catalog.pg_index
|
|
WHERE indisunique AND indrelid=(
|
|
SELECT oid FROM pg_catalog.pg_class
|
|
WHERE relname='{$table}' AND relnamespace=(
|
|
SELECT oid FROM pg_catalog.pg_namespace
|
|
WHERE nspname='{$c_schema}'
|
|
)
|
|
) AND indpred IS NULL AND indexprs IS NULL
|
|
ORDER BY indisprimary DESC LIMIT 1";
|
|
$rs = $this->selectSet($sql);
|
|
|
|
// If none, check for an OID column. Even though OIDs can be duplicated, the edit and delete row
|
|
// functions check that they're only modiying a single row. Otherwise, return empty array.
|
|
if ($rs->recordCount() == 0) {
|
|
// Check for OID column
|
|
$temp = array();
|
|
if ($this->hasObjectID($table)) {
|
|
$temp = array('oid');
|
|
}
|
|
$this->endTransaction();
|
|
return $temp;
|
|
}
|
|
// Otherwise find the names of the keys
|
|
else {
|
|
$attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey']));
|
|
if (!is_array($attnames)) {
|
|
$this->rollbackTransaction();
|
|
return -1;
|
|
}
|
|
else {
|
|
$this->endTransaction();
|
|
return $attnames;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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(array('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 = "
|
|
SELECT c.relname AS seqname, s.*,
|
|
pg_catalog.obj_description(s.tableoid, 'pg_class') AS seqcomment,
|
|
u.usename AS seqowner, n.nspname
|
|
FROM \"{$sequence}\" AS s, pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n
|
|
WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid
|
|
AND c.relname = '{$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
|
|
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,
|
|
(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 = "
|
|
SELECT c.relname, n.nspname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
|
|
pg_catalog.pg_get_viewdef(c.oid, true) AS vwdefinition,
|
|
pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment
|
|
FROM pg_catalog.pg_class c
|
|
LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
|
|
WHERE (c.relname = '{$view}') AND n.nspname='{$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 = "
|
|
SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner,
|
|
pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment
|
|
FROM pg_catalog.pg_class c
|
|
LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
|
|
WHERE (n.nspname='{$c_schema}') AND (c.relkind = 'v'::\"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 = "
|
|
SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered,
|
|
pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef
|
|
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
|
|
WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid)
|
|
AND c.oid = i.indrelid AND i.indexrelid = c2.oid
|
|
";
|
|
if ($unique) $sql .= " AND i.indisunique ";
|
|
$sql .= " ORDER BY c2.relname";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
|
|
$c_schema = $this->_schema;
|
|
$this->clean($c_schema);
|
|
$this->clean($table);
|
|
|
|
$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
|
|
AND c.relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
|
|
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
|
|
pc.conname,
|
|
pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc,
|
|
pc.contype,
|
|
CASE WHEN pc.contype='u' OR pc.contype='p' THEN (
|
|
SELECT
|
|
indisclustered
|
|
FROM
|
|
pg_catalog.pg_depend pd,
|
|
pg_catalog.pg_class pl,
|
|
pg_catalog.pg_index pi
|
|
WHERE
|
|
pd.refclassid=pc.tableoid
|
|
AND pd.refobjid=pc.oid
|
|
AND pd.objid=pl.oid
|
|
AND pl.oid=pi.indexrelid
|
|
) ELSE
|
|
NULL
|
|
END AS indisclustered
|
|
FROM
|
|
pg_catalog.pg_constraint pc
|
|
WHERE
|
|
pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
|
|
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
|
|
WHERE nspname='{$c_schema}'))
|
|
ORDER BY
|
|
1
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all constraints on a table,
|
|
* including constraint name, definition, related col and referenced namespace,
|
|
* table and col if needed
|
|
* @param $table the table where we are looking for fk
|
|
* @return a recordset
|
|
*/
|
|
function getConstraintsWithFields($table) {
|
|
|
|
$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)
|
|
JOIN pg_catalog.pg_namespace AS ns ON (r.relnamespace=ns.oid)
|
|
WHERE
|
|
r.relname = '{$table}' AND ns.nspname='{$c_schema}'";
|
|
|
|
$rs = $this->selectSet($sql);
|
|
|
|
if ($rs->EOF) $max_col = 0;
|
|
else $max_col = $rs->fields['nb'];
|
|
|
|
$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,
|
|
r2.relname as f_table, f1.attname as p_field, f1.attnum AS p_attnum, f2.attname as f_field,
|
|
f2.attnum AS f_attnum, pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment,
|
|
c.conrelid, c.confrelid
|
|
FROM
|
|
pg_catalog.pg_constraint AS c
|
|
JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid)
|
|
JOIN pg_catalog.pg_attribute AS f1 ON (f1.attrelid=r1.oid AND (f1.attnum=c.conkey[1]';
|
|
for ($i = 2; $i <= $rs->fields['nb']; $i++) {
|
|
$sql.= " OR f1.attnum=c.conkey[$i]";
|
|
}
|
|
$sql.= '))
|
|
JOIN pg_catalog.pg_namespace AS ns1 ON r1.relnamespace=ns1.oid
|
|
LEFT JOIN (
|
|
pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid)
|
|
) ON (c.confrelid=r2.oid)
|
|
LEFT JOIN pg_catalog.pg_attribute AS f2 ON
|
|
(f2.attrelid=r2.oid AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)';
|
|
for ($i = 2; $i <= $rs->fields['nb']; $i++)
|
|
$sql.= " OR (c.confkey[$i]=f2.attnum AND c.conkey[$i]=f1.attnum)";
|
|
|
|
$sql .= sprintf("))
|
|
WHERE
|
|
r1.relname = '%s' AND ns1.nspname='%s'
|
|
ORDER BY 1", $table, $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}'
|
|
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
|
|
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 = "
|
|
SELECT DISTINCT
|
|
array_dims(pc.conkey) AS arr_dim,
|
|
pgc1.relname AS p_table
|
|
FROM
|
|
pg_catalog.pg_constraint AS pc,
|
|
pg_catalog.pg_class AS pgc1
|
|
WHERE
|
|
pc.contype = 'f'
|
|
AND (pc.conrelid = pgc1.relfilenode OR pc.confrelid = pgc1.relfilenode)
|
|
AND pgc1.relname IN ($tables_list)
|
|
";
|
|
|
|
//parse our output to find the highest dimension of foreign keys since pc.conkey is stored in an array
|
|
$rs = $this->selectSet($sql);
|
|
while (!$rs->EOF) {
|
|
$arrData = explode(':', $rs->fields['arr_dim']);
|
|
$tmpDimension = intval(substr($arrData[1], 0, strlen($arrData[1] - 1)));
|
|
$maxDimension = $tmpDimension > $maxDimension ? $tmpDimension : $maxDimension;
|
|
$rs->MoveNext();
|
|
}
|
|
|
|
//we know the highest index for foreign keys that conkey goes up to, expand for us in an IN query
|
|
$cons_str = '( (pfield.attnum = conkey[1] AND cfield.attnum = confkey[1]) ';
|
|
for ($i = 2; $i <= $maxDimension; $i++) {
|
|
$cons_str .= "OR (pfield.attnum = conkey[{$i}] AND cfield.attnum = confkey[{$i}]) ";
|
|
}
|
|
$cons_str .= ') ';
|
|
|
|
$sql = "
|
|
SELECT
|
|
pgc1.relname AS p_table,
|
|
pgc2.relname AS f_table,
|
|
pfield.attname AS p_field,
|
|
cfield.attname AS f_field,
|
|
pgns1.nspname AS p_schema,
|
|
pgns2.nspname AS f_schema
|
|
FROM
|
|
pg_catalog.pg_constraint AS pc,
|
|
pg_catalog.pg_class AS pgc1,
|
|
pg_catalog.pg_class AS pgc2,
|
|
pg_catalog.pg_attribute AS pfield,
|
|
pg_catalog.pg_attribute AS cfield,
|
|
(SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN ($schema_list) ) AS pgns1,
|
|
(SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN ($schema_list) ) AS pgns2
|
|
WHERE
|
|
pc.contype = 'f'
|
|
AND pgc1.relnamespace = pgns1.ns_id
|
|
AND pgc2.relnamespace = pgns2.ns_id
|
|
AND pc.conrelid = pgc1.relfilenode
|
|
AND pc.confrelid = pgc2.relfilenode
|
|
AND pfield.attrelid = pc.conrelid
|
|
AND cfield.attrelid = pc.confrelid
|
|
AND $cons_str
|
|
AND pgns1.nspname || '.' || pgc1.relname IN ($schema_tables_list)
|
|
AND pgns2.nspname || '.' || pgc2.relname IN ($schema_tables_list)
|
|
";
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* 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 = "
|
|
SELECT
|
|
pn.nspname,
|
|
pl.relname,
|
|
pc.conname,
|
|
pg_catalog.pg_get_constraintdef(pc.oid) AS consrc
|
|
FROM
|
|
pg_catalog.pg_constraint pc,
|
|
pg_catalog.pg_namespace pn,
|
|
pg_catalog.pg_class pl
|
|
WHERE
|
|
pc.connamespace = pn.oid
|
|
AND pc.conrelid = pl.oid
|
|
AND pc.contype = 'f'
|
|
AND confrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
|
|
AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
|
|
WHERE nspname='{$c_schema}'))
|
|
ORDER BY 1,2,3
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
// 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);
|
|
|
|
$sql = "
|
|
SELECT
|
|
t.typname AS domname,
|
|
pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype,
|
|
t.typnotnull AS domnotnull,
|
|
t.typdefault AS domdef,
|
|
pg_catalog.pg_get_userbyid(t.typowner) AS domowner,
|
|
pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment
|
|
FROM
|
|
pg_catalog.pg_type t
|
|
WHERE
|
|
t.typtype = 'd'
|
|
AND t.typname = '{$domain}'
|
|
AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace
|
|
WHERE nspname = '{$c_schema}')";
|
|
|
|
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);
|
|
|
|
$sql = "
|
|
SELECT
|
|
t.typname AS domname,
|
|
pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype,
|
|
t.typnotnull AS domnotnull,
|
|
t.typdefault AS domdef,
|
|
pg_catalog.pg_get_userbyid(t.typowner) AS domowner,
|
|
pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment
|
|
FROM
|
|
pg_catalog.pg_type t
|
|
WHERE
|
|
t.typtype = 'd'
|
|
AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace
|
|
WHERE nspname='{$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 = "
|
|
SELECT
|
|
conname,
|
|
contype,
|
|
pg_catalog.pg_get_constraintdef(oid, true) AS consrc
|
|
FROM
|
|
pg_catalog.pg_constraint
|
|
WHERE
|
|
contypid = (
|
|
SELECT oid FROM pg_catalog.pg_type
|
|
WHERE typname='{$domain}'
|
|
AND typnamespace = (
|
|
SELECT oid FROM pg_catalog.pg_namespace
|
|
WHERE nspname = '{$c_schema}')
|
|
)
|
|
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 = "
|
|
SELECT
|
|
pc.oid AS prooid, proname, pg_catalog.pg_get_userbyid(proowner) AS proowner,
|
|
nspname as proschema, lanname as prolanguage, procost, prorows,
|
|
pg_catalog.format_type(prorettype, NULL) as proresult, prosrc,
|
|
probin, proretset, proisstrict, provolatile, prosecdef,
|
|
pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments,
|
|
proargnames AS proargnames,
|
|
pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment,
|
|
proconfig
|
|
FROM
|
|
pg_catalog.pg_proc pc, pg_catalog.pg_language pl,
|
|
pg_catalog.pg_namespace pn
|
|
WHERE
|
|
pc.oid = '{$function_oid}'::oid AND pc.prolang = pl.oid
|
|
AND pc.pronamespace = pn.oid
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns 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 = "
|
|
SELECT
|
|
{$distinct}
|
|
p.oid AS prooid,
|
|
p.proname,
|
|
p.proretset,
|
|
pg_catalog.format_type(p.prorettype, NULL) AS proresult,
|
|
pg_catalog.oidvectortypes(p.proargtypes) AS proarguments,
|
|
pl.lanname AS prolanguage,
|
|
pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment,
|
|
p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
|
|
CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns,
|
|
u.usename AS proowner
|
|
FROM pg_catalog.pg_proc p
|
|
INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
|
INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang
|
|
LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner
|
|
WHERE NOT p.proisagg
|
|
AND {$where}
|
|
ORDER BY p.proname, proresult
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns an array containing a function's properties
|
|
* @param $f The array of data for the function
|
|
* @return An array containing the properties
|
|
*/
|
|
function getFunctionProperties($f) {
|
|
$temp = array();
|
|
|
|
// Volatility
|
|
if ($f['provolatile'] == 'v')
|
|
$temp[] = 'VOLATILE';
|
|
elseif ($f['provolatile'] == 'i')
|
|
$temp[] = 'IMMUTABLE';
|
|
elseif ($f['provolatile'] == 's')
|
|
$temp[] = 'STABLE';
|
|
else
|
|
return -1;
|
|
|
|
// Null handling
|
|
$f['proisstrict'] = $this->phpBool($f['proisstrict']);
|
|
if ($f['proisstrict'])
|
|
$temp[] = 'RETURNS NULL ON NULL INPUT';
|
|
else
|
|
$temp[] = 'CALLED ON NULL INPUT';
|
|
|
|
// Security
|
|
$f['prosecdef'] = $this->phpBool($f['prosecdef']);
|
|
if ($f['prosecdef'])
|
|
$temp[] = 'SECURITY DEFINER';
|
|
else
|
|
$temp[] = 'SECURITY INVOKER';
|
|
|
|
return $temp;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
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
|
|
t.typname AS basename,
|
|
pg_catalog.format_type(t.oid, NULL) AS typname,
|
|
pu.usename AS typowner,
|
|
t.typtype,
|
|
pg_catalog.obj_description(t.oid, 'pg_type') AS typcomment
|
|
FROM (pg_catalog.pg_type t
|
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace)
|
|
LEFT JOIN pg_catalog.pg_user pu ON t.typowner = pu.usesysid
|
|
WHERE (t.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid {$where2}))
|
|
AND t.typname !~ '^_'
|
|
AND {$where}
|
|
ORDER BY typname
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* 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 = "
|
|
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
|
|
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() {
|
|
global $conf;
|
|
|
|
if ($conf['show_system'])
|
|
$where = '';
|
|
else
|
|
$where = '
|
|
AND n1.nspname NOT LIKE $$pg\_%$$
|
|
AND n2.nspname NOT LIKE $$pg\_%$$
|
|
AND n3.nspname NOT LIKE $$pg\_%$$
|
|
';
|
|
|
|
$sql = "
|
|
SELECT
|
|
c.castsource::pg_catalog.regtype AS castsource,
|
|
c.casttarget::pg_catalog.regtype AS casttarget,
|
|
CASE WHEN c.castfunc=0 THEN NULL
|
|
ELSE c.castfunc::pg_catalog.regprocedure END AS castfunc,
|
|
c.castcontext,
|
|
obj_description(c.oid, 'pg_cast') as castcomment
|
|
FROM
|
|
(pg_catalog.pg_cast c LEFT JOIN pg_catalog.pg_proc p ON c.castfunc=p.oid JOIN pg_catalog.pg_namespace n3 ON p.pronamespace=n3.oid),
|
|
pg_catalog.pg_type t1,
|
|
pg_catalog.pg_type t2,
|
|
pg_catalog.pg_namespace n1,
|
|
pg_catalog.pg_namespace n2
|
|
WHERE
|
|
c.castsource=t1.oid
|
|
AND c.casttarget=t2.oid
|
|
AND t1.typnamespace=n1.oid
|
|
AND t2.typnamespace=n2.oid
|
|
{$where}
|
|
ORDER BY 1, 2
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all conversions in the database
|
|
* @return All conversions
|
|
*/
|
|
function getConversions() {
|
|
$c_schema = $this->_schema;
|
|
$this->clean($c_schema);
|
|
$sql = "
|
|
SELECT
|
|
c.conname,
|
|
pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding,
|
|
pg_catalog.pg_encoding_to_char(c.contoencoding) AS contoencoding,
|
|
c.condefault,
|
|
pg_catalog.obj_description(c.oid, 'pg_conversion') AS concomment
|
|
FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n
|
|
WHERE n.oid = c.connamespace
|
|
AND n.nspname='{$c_schema}'
|
|
ORDER BY 1;
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
// Rule functions
|
|
|
|
/**
|
|
* Returns a list of all rules on a table OR view
|
|
* @param $table The table to find rules for
|
|
* @return A recordset
|
|
*/
|
|
function getRules($table) {
|
|
$c_schema = $this->_schema;
|
|
$this->clean($c_schema);
|
|
$this->clean($table);
|
|
|
|
$sql = "
|
|
SELECT *
|
|
FROM pg_catalog.pg_rules
|
|
WHERE
|
|
schemaname='{$c_schema}' AND tablename='{$table}'
|
|
ORDER BY rulename
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Edits a rule on a table OR view
|
|
* @param $name The name of the new rule
|
|
* @param $event SELECT, INSERT, UPDATE or DELETE
|
|
* @param $table Table on which to create the rule
|
|
* @param $where When to execute the rule, '' indicates always
|
|
* @param $instead True if an INSTEAD rule, false otherwise
|
|
* @param $type NOTHING for a do nothing rule, SOMETHING to use given action
|
|
* @param $action The action to take
|
|
* @return 0 success
|
|
* @return -1 invalid event
|
|
*/
|
|
function setRule($name, $event, $table, $where, $instead, $type, $action) {
|
|
return $this->createRule($name, $event, $table, $where, $instead, $type, $action, true);
|
|
}
|
|
|
|
/**
|
|
* Creates a rule
|
|
* @param $name The name of the new rule
|
|
* @param $event SELECT, INSERT, UPDATE or DELETE
|
|
* @param $table Table on which to create the rule
|
|
* @param $where When to execute the rule, '' indicates always
|
|
* @param $instead True if an INSTEAD rule, false otherwise
|
|
* @param $type NOTHING for a do nothing rule, SOMETHING to use given action
|
|
* @param $action The action to take
|
|
* @param $replace (optional) True to replace existing rule, false otherwise
|
|
* @return 0 success
|
|
* @return -1 invalid event
|
|
*/
|
|
function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false) {
|
|
$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 = "
|
|
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
|
|
t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef,
|
|
CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid,
|
|
p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto,
|
|
ns.nspname AS pronamespace
|
|
FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns
|
|
WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}'
|
|
AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}'))
|
|
AND ( tgconstraint = 0 OR NOT EXISTS
|
|
(SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c
|
|
ON (d.refclassid = c.tableoid AND d.refobjid = c.oid)
|
|
WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))
|
|
AND p.oid=t.tgfoid
|
|
AND p.pronamespace = ns.oid";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* A helper function for getTriggers that translates
|
|
* an array of attribute numbers to an array of field names.
|
|
* @param $trigger An array containing fields from the trigger table
|
|
* @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}
|
|
{$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 = "
|
|
SELECT
|
|
po.oid, po.oprname,
|
|
(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname,
|
|
(SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname,
|
|
po.oprresult::pg_catalog.regtype AS resultname,
|
|
pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment
|
|
FROM
|
|
pg_catalog.pg_operator po
|
|
WHERE
|
|
po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')
|
|
ORDER BY
|
|
po.oprname, oprleftname, oprrightname
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns all details for a particular operator
|
|
* @param $operator_oid The oid of the operator
|
|
* @return Function info
|
|
*/
|
|
function getOperator($operator_oid) {
|
|
$this->clean($operator_oid);
|
|
|
|
$sql = "
|
|
SELECT
|
|
po.oid, po.oprname,
|
|
oprleft::pg_catalog.regtype AS oprleftname,
|
|
oprright::pg_catalog.regtype AS oprrightname,
|
|
oprresult::pg_catalog.regtype AS resultname,
|
|
po.oprcanhash,
|
|
oprcanmerge,
|
|
oprcom::pg_catalog.regoperator AS oprcom,
|
|
oprnegate::pg_catalog.regoperator AS oprnegate,
|
|
po.oprcode::pg_catalog.regproc AS oprcode,
|
|
po.oprrest::pg_catalog.regproc AS oprrest,
|
|
po.oprjoin::pg_catalog.regproc AS oprjoin
|
|
FROM
|
|
pg_catalog.pg_operator po
|
|
WHERE
|
|
po.oid='{$operator_oid}'
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Drops an operator
|
|
* @param $operator_oid The OID of the operator to drop
|
|
* @param $cascade True to cascade drop, false to restrict
|
|
* @return 0 success
|
|
*/
|
|
function dropOperator($operator_oid, $cascade) {
|
|
// Function comes in with $object as operator OID
|
|
$opr = $this->getOperator($operator_oid);
|
|
$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 = "
|
|
SELECT
|
|
pa.amname, po.opcname,
|
|
po.opcintype::pg_catalog.regtype AS opcintype,
|
|
po.opcdefault,
|
|
pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment
|
|
FROM
|
|
pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn
|
|
WHERE
|
|
po.opcmethod=pa.oid
|
|
AND po.opcnamespace=pn.oid
|
|
AND pn.nspname='{$c_schema}'
|
|
ORDER BY 1,2
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
// FTS functions
|
|
|
|
/**
|
|
* Creates a new FTS configuration.
|
|
* @param string $cfgname The name of the FTS configuration to create
|
|
* @param string $parser The parser to be used in new FTS configuration
|
|
* @param string $locale Locale of the FTS configuration
|
|
* @param string $template The existing FTS configuration to be used as template for the new one
|
|
* @param string $withmap Should we copy whole map of existing FTS configuration to the new one
|
|
* @param string $makeDefault Should this configuration be the default for locale given
|
|
* @param string $comment If omitted, defaults to nothing
|
|
*
|
|
* @return 0 success
|
|
*/
|
|
function createFtsConfiguration($cfgname, $parser = '', $template = '', $comment = '') {
|
|
$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 = "
|
|
SELECT
|
|
n.nspname as schema,
|
|
c.cfgname as name,
|
|
pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment
|
|
FROM
|
|
pg_catalog.pg_ts_config c
|
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace
|
|
WHERE
|
|
pg_catalog.pg_ts_config_is_visible(c.oid)";
|
|
|
|
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 = "
|
|
SELECT
|
|
n.nspname as schema,
|
|
c.cfgname as name,
|
|
p.prsname as parser,
|
|
c.cfgparser as parser_id,
|
|
pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment
|
|
FROM pg_catalog.pg_ts_config c
|
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace
|
|
LEFT JOIN pg_catalog.pg_ts_parser p ON p.oid = c.cfgparser
|
|
WHERE pg_catalog.pg_ts_config_is_visible(c.oid)
|
|
AND c.cfgname = '{$ftscfg}'
|
|
AND n.nspname='{$c_schema}'";
|
|
|
|
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) {
|
|
|
|
$c_schema = $this->_schema;
|
|
$this->clean($c_schema);
|
|
$this->fieldClean($ftscfg);
|
|
|
|
$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'];
|
|
|
|
$sql = "
|
|
SELECT
|
|
(SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name,
|
|
(SELECT t.description FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS description,
|
|
c.cfgname AS cfgname, n.nspname ||'.'|| d.dictname as dictionaries
|
|
FROM
|
|
pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d,
|
|
pg_catalog.pg_namespace n
|
|
WHERE
|
|
c.oid = {$oid}
|
|
AND m.mapcfg = c.oid
|
|
AND m.mapdict = d.oid
|
|
AND d.dictnamespace = n.oid
|
|
ORDER BY name
|
|
";
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns 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 = "
|
|
SELECT
|
|
n.nspname as schema,
|
|
p.prsname as name,
|
|
pg_catalog.obj_description(p.oid, 'pg_ts_parser') as comment
|
|
FROM pg_catalog.pg_ts_parser p
|
|
LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = p.prsnamespace)
|
|
WHERE pg_catalog.pg_ts_parser_is_visible(p.oid)";
|
|
|
|
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 = "
|
|
SELECT
|
|
n.nspname as schema, d.dictname as name,
|
|
pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment
|
|
FROM pg_catalog.pg_ts_dict d
|
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace
|
|
WHERE pg_catalog.pg_ts_dict_is_visible(d.oid)";
|
|
|
|
if (!$all)
|
|
$sql.= " AND n.nspname='{$c_schema}'\n";
|
|
|
|
$sql.= "ORDER BY name;";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns all FTS dictionary templates available
|
|
*/
|
|
function getFtsDictionaryTemplates() {
|
|
|
|
$sql = "
|
|
SELECT
|
|
n.nspname as schema,
|
|
t.tmplname as name,
|
|
( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname
|
|
FROM pg_catalog.pg_proc p
|
|
LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace
|
|
WHERE t.tmplinit = p.oid ) AS init,
|
|
( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname
|
|
FROM pg_catalog.pg_proc p
|
|
LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace
|
|
WHERE t.tmpllexize = p.oid ) AS lexize,
|
|
pg_catalog.obj_description(t.oid, 'pg_ts_template') as comment
|
|
FROM pg_catalog.pg_ts_template t
|
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace
|
|
WHERE pg_catalog.pg_ts_template_is_visible(t.oid)
|
|
ORDER BY 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
|
|
n.nspname as schema,
|
|
d.dictname as name,
|
|
( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM
|
|
pg_catalog.pg_ts_template t
|
|
LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = t.tmplnamespace
|
|
WHERE d.dicttemplate = t.oid ) AS template,
|
|
d.dictinitoption as init,
|
|
pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment
|
|
FROM pg_catalog.pg_ts_dict d
|
|
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace
|
|
WHERE d.dictname = '{$ftsdict}'
|
|
AND pg_catalog.pg_ts_dict_is_visible(d.oid)
|
|
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
|
|
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'];
|
|
|
|
$tokenIdSet = $this->selectSet("SELECT tokid
|
|
FROM pg_catalog.ts_token_type({$cfgparser})
|
|
WHERE alias = '{$mapping}'");
|
|
|
|
$tokid = $tokenIdSet->fields['tokid'];
|
|
|
|
$sql = "SELECT
|
|
(SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name,
|
|
d.dictname as dictionaries
|
|
FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d
|
|
WHERE c.oid = {$oid} AND m.mapcfg = c.oid AND m.maptokentype = {$tokid} AND m.mapdict = d.oid
|
|
LIMIT 1;";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Return list of FTS mappings possible for given parser
|
|
* (specified by given configuration since configuration can only have 1 parser)
|
|
* @param $ftscfg The config's name that use the parser
|
|
*
|
|
* @return 0 on success
|
|
*/
|
|
function getFtsMappings($ftscfg) {
|
|
|
|
$cfg = $this->getFtsConfigurationByName($ftscfg);
|
|
|
|
$sql = "SELECT alias AS name, description
|
|
FROM pg_catalog.ts_token_type({$cfg->fields['parser_id']})
|
|
ORDER BY name";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
// Language functions
|
|
|
|
/**
|
|
* Gets all languages
|
|
* @param $all True to get all languages, regardless of show_system
|
|
* @return A recordset
|
|
*/
|
|
function getLanguages($all = false) {
|
|
global $conf;
|
|
|
|
if ($conf['show_system'] || $all)
|
|
$where = '';
|
|
else
|
|
$where = 'WHERE lanispl';
|
|
|
|
$sql = "
|
|
SELECT
|
|
lanname, lanpltrusted,
|
|
lanplcallfoid::pg_catalog.regproc AS lanplcallf
|
|
FROM
|
|
pg_catalog.pg_language
|
|
{$where}
|
|
ORDER BY lanname
|
|
";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
// Aggregate functions
|
|
|
|
/**
|
|
* Creates a new aggregate in the database
|
|
* @param $name The name of the aggregate
|
|
* @param $basetype The input data type of the aggregate
|
|
* @param $sfunc The name of the state transition function for the aggregate
|
|
* @param $stype The data type for the aggregate's state value
|
|
* @param $ffunc The name of the final function for the aggregate
|
|
* @param $initcond The initial setting for the state value
|
|
* @param $sortop The sort operator for the aggregate
|
|
* @param $comment Aggregate comment
|
|
* @return 0 success
|
|
* @return -1 error
|
|
*/
|
|
function createAggregate($name, $basetype, $sfunc, $stype, $ffunc, $initcond, $sortop, $comment) {
|
|
$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 = "
|
|
SELECT p.proname, CASE p.proargtypes[0]
|
|
WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL
|
|
ELSE pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes,
|
|
a.aggtransfn, format_type(a.aggtranstype, NULL) AS aggstype, a.aggfinalfn,
|
|
a.agginitval, a.aggsortop, u.usename, pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment
|
|
FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a
|
|
WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid
|
|
AND p.proisagg AND n.nspname='{$c_schema}'
|
|
AND p.proname='" . $name . "'
|
|
AND CASE p.proargtypes[0]
|
|
WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN ''
|
|
ELSE pg_catalog.format_type(p.proargtypes[0], NULL)
|
|
END ='" . $basetype . "'";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Gets all aggregates
|
|
* @return A recordset
|
|
*/
|
|
function getAggregates() {
|
|
$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 = '
|
|
SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit,
|
|
rolcanlogin, rolconnlimit, rolvaliduntil, rolconfig
|
|
FROM pg_catalog.pg_roles';
|
|
if($rolename) $sql .= " WHERE rolname!='{$rolename}'";
|
|
$sql .= ' ORDER BY rolname';
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns information about a single role
|
|
* @param $rolename The name of the role to retrieve
|
|
* @return The role's data
|
|
*/
|
|
function getRole($rolename) {
|
|
$this->clean($rolename);
|
|
|
|
$sql = "
|
|
SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit,
|
|
rolcanlogin, rolconnlimit, rolvaliduntil, rolconfig
|
|
FROM pg_catalog.pg_roles WHERE rolname='{$rolename}'";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Grants membership in a role
|
|
* @param $role The name of the target role
|
|
* @param $rolename The name of the role that will belong to the target role
|
|
* @param $admin (optional) Flag to grant the admin option
|
|
* @return 0 success
|
|
*/
|
|
function grantRole($role, $rolename, $admin=0) {
|
|
$this->fieldClean($role);
|
|
$this->fieldClean($rolename);
|
|
|
|
$sql = "GRANT \"{$role}\" TO \"{$rolename}\"";
|
|
if($admin == 1) $sql .= ' WITH ADMIN OPTION';
|
|
|
|
return $this->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Revokes membership in a role
|
|
* @param $role The name of the target role
|
|
* @param $rolename The name of the role that will not belong to the target role
|
|
* @param $admin (optional) Flag to revoke only the admin option
|
|
* @param $type (optional) Type of revoke: RESTRICT | CASCADE
|
|
* @return 0 success
|
|
*/
|
|
function revokeRole($role, $rolename, $admin = 0, $type = 'RESTRICT') {
|
|
$this->fieldClean($role);
|
|
$this->fieldClean($rolename);
|
|
|
|
$sql = "REVOKE ";
|
|
if($admin == 1) $sql .= 'ADMIN OPTION FOR ';
|
|
$sql .= "\"{$role}\" FROM \"{$rolename}\" {$type}";
|
|
|
|
return $this->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns all users in the database cluster
|
|
* @return All users
|
|
*/
|
|
function getUsers() {
|
|
$sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, useconfig
|
|
FROM pg_user
|
|
ORDER BY usename";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns information about a single user
|
|
* @param $username The username of the user to retrieve
|
|
* @return The user's data
|
|
*/
|
|
function getUser($username) {
|
|
$this->clean($username);
|
|
|
|
$sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, 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 (function_exists('pg_parameter_status')) {
|
|
$val = pg_parameter_status($this->conn->_connectionID, 'is_superuser');
|
|
if ($val !== false) return $val == 'on';
|
|
}
|
|
|
|
$sql = "SELECT usesuper FROM pg_user WHERE usename='{$username}'";
|
|
|
|
$usesuper = $this->selectField($sql, 'usesuper');
|
|
if ($usesuper == -1) return false;
|
|
else return $usesuper == 't';
|
|
}
|
|
|
|
/**
|
|
* Changes a role's password
|
|
* @param $rolename The role name
|
|
* @param $password The new password
|
|
* @return 0 success
|
|
*/
|
|
function changePassword($rolename, $password) {
|
|
$enc = $this->_encryptPassword($rolename, $password);
|
|
$this->fieldClean($rolename);
|
|
$this->clean($enc);
|
|
|
|
$sql = "ALTER ROLE \"{$rolename}\" WITH ENCRYPTED PASSWORD '{$enc}'";
|
|
|
|
return $this->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Adds a group member
|
|
* @param $groname The name of the group
|
|
* @param $user The name of the user to add to the group
|
|
* @return 0 success
|
|
*/
|
|
function addGroupMember($groname, $user) {
|
|
$this->fieldClean($groname);
|
|
$this->fieldClean($user);
|
|
|
|
$sql = "ALTER GROUP \"{$groname}\" ADD USER \"{$user}\"";
|
|
|
|
return $this->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns all role names which the role belongs to
|
|
* @param $rolename The role name
|
|
* @return All role names
|
|
*/
|
|
function getMemberOf($rolename) {
|
|
$this->clean($rolename);
|
|
|
|
$sql = "
|
|
SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M
|
|
WHERE R.oid=M.roleid
|
|
AND member IN (
|
|
SELECT oid FROM pg_catalog.pg_roles
|
|
WHERE rolname='{$rolename}')
|
|
ORDER BY rolname";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns all role names that are members of a role
|
|
* @param $rolename The role name
|
|
* @param $admin (optional) Find only admin members
|
|
* @return All role names
|
|
*/
|
|
function getMembers($rolename, $admin = 'f') {
|
|
$this->clean($rolename);
|
|
|
|
$sql = "
|
|
SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M
|
|
WHERE R.oid=M.member AND admin_option='{$admin}'
|
|
AND roleid IN (SELECT oid FROM pg_catalog.pg_roles
|
|
WHERE rolname='{$rolename}')
|
|
ORDER BY rolname";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Removes a group member
|
|
* @param $groname The name of the group
|
|
* @param $user The name of the user to remove from the group
|
|
* @return 0 success
|
|
*/
|
|
function dropGroupMember($groname, $user) {
|
|
$this->fieldClean($groname);
|
|
$this->fieldClean($user);
|
|
|
|
$sql = "ALTER GROUP \"{$groname}\" DROP USER \"{$user}\"";
|
|
|
|
return $this->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Return users in a specific group
|
|
* @param $groname The name of the group
|
|
* @return All users in the group
|
|
*/
|
|
function getGroup($groname) {
|
|
$this->clean($groname);
|
|
|
|
$sql = "
|
|
SELECT s.usename FROM pg_catalog.pg_user s, pg_catalog.pg_group g
|
|
WHERE g.groname='{$groname}' AND s.usesysid = ANY (g.grolist)
|
|
ORDER BY s.usename";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns all groups in the database cluser
|
|
* @return All groups
|
|
*/
|
|
function getGroups() {
|
|
$sql = "SELECT groname FROM pg_group ORDER BY groname";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Creates a new group
|
|
* @param $groname The name of the group
|
|
* @param $users An array of users to add to the group
|
|
* @return 0 success
|
|
*/
|
|
function createGroup($groname, $users) {
|
|
$this->fieldClean($groname);
|
|
|
|
$sql = "CREATE GROUP \"{$groname}\"";
|
|
|
|
if (is_array($users) && sizeof($users) > 0) {
|
|
$this->fieldArrayClean($users);
|
|
$sql .= ' WITH USER "' . join('", "', $users) . '"';
|
|
}
|
|
|
|
return $this->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Removes a group
|
|
* @param $groname The name of the group to drop
|
|
* @return 0 success
|
|
*/
|
|
function dropGroup($groname) {
|
|
$this->fieldClean($groname);
|
|
|
|
$sql = "DROP GROUP \"{$groname}\"";
|
|
|
|
return $this->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* Internal function used for parsing ACLs
|
|
* @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 = array();
|
|
$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 = array();
|
|
|
|
// 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 = array($atype, $entity, array(), '', array());
|
|
|
|
// 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 = "
|
|
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)
|
|
LEFT JOIN pg_catalog.pg_namespace n ON (c.relnamespace=n.oid)
|
|
WHERE n.nspname='{$c_schema}'
|
|
AND c.relname='{$table}'
|
|
AND a.attname='{$object}'";
|
|
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 array();
|
|
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) {
|
|
global $conf;
|
|
|
|
$sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation,
|
|
(SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid) AS spccomment
|
|
FROM pg_catalog.pg_tablespace";
|
|
|
|
if (!$conf['show_system'] && !$all) {
|
|
$sql .= ' WHERE spcname NOT LIKE $$pg\_%$$';
|
|
}
|
|
|
|
$sql .= " ORDER BY spcname";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Retrieves a tablespace's information
|
|
* @return A recordset
|
|
*/
|
|
function getTablespace($spcname) {
|
|
$this->clean($spcname);
|
|
|
|
$sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, spclocation,
|
|
(SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid) AS spccomment
|
|
FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
FROM pg_catalog.pg_settings
|
|
WHERE
|
|
name = 'autovacuum'
|
|
OR name = 'autovacuum_vacuum_threshold'
|
|
OR name = 'autovacuum_vacuum_scale_factor'
|
|
OR name = 'autovacuum_analyze_threshold'
|
|
OR name = 'autovacuum_analyze_scale_factor'
|
|
OR name = 'autovacuum_vacuum_cost_delay'
|
|
OR name = 'autovacuum_vacuum_cost_limit'
|
|
OR name = 'vacuum_freeze_min_age'
|
|
OR name = 'autovacuum_freeze_max_age'
|
|
"
|
|
);
|
|
|
|
$ret = array();
|
|
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("
|
|
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 * FROM pg_catalog.pg_stat_activity ORDER BY datname, usename, procpid";
|
|
else {
|
|
$this->clean($database);
|
|
$sql = "
|
|
SELECT * FROM pg_catalog.pg_stat_activity
|
|
WHERE datname='{$database}' ORDER BY usename, procpid";
|
|
}
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Returns table locks information in the current database
|
|
* @return A recordset
|
|
*/
|
|
|
|
function getLocks() {
|
|
global $conf;
|
|
|
|
if (!$conf['show_system'])
|
|
$where = 'AND pn.nspname NOT LIKE $$pg\_%$$';
|
|
else
|
|
$where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'";
|
|
|
|
$sql = "
|
|
SELECT
|
|
pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction,
|
|
(select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid'
|
|
and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction
|
|
FROM
|
|
pg_catalog.pg_locks pl,
|
|
pg_catalog.pg_class pc,
|
|
pg_catalog.pg_namespace pn
|
|
WHERE
|
|
pl.relation = pc.oid AND pc.relnamespace=pn.oid
|
|
{$where}
|
|
ORDER BY pid,nspname,tablename";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets the client encoding
|
|
* @param $encoding The encoding to for the client
|
|
* @return 0 success
|
|
*/
|
|
function setClientEncoding($encoding) {
|
|
$this->clean($encoding);
|
|
|
|
$sql = "SET CLIENT_ENCODING TO '{$encoding}'";
|
|
|
|
return $this->execute($sql);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
global $data;
|
|
|
|
// This whole function isn't very encapsulated, but hey...
|
|
$conn = $data->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 (supporting 4.1.x PHP...). PHP cannot execute
|
|
// empty queries, unlike libpq
|
|
if (function_exists('pg_query'))
|
|
$res = @pg_query($conn, $query_buf);
|
|
else
|
|
$res = @pg_exec($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 (supporting 4.1.x PHP...)
|
|
if (function_exists('pg_query'))
|
|
$res = @pg_query($conn, $query_buf);
|
|
else
|
|
$res = @pg_exec($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 = array()) {
|
|
$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 = array($sortkey => $sortdir);
|
|
else $orderby = array();
|
|
$query = $this->getSelectSQL($table, array(), array(), array(), $orderby);
|
|
break;
|
|
case 'QUERY':
|
|
case 'SELECT':
|
|
// Trim query
|
|
$query = trim($query);
|
|
// Trim off trailing semi-colon if there is one
|
|
if (substr($query, strlen($query) - 1, 1) == ';')
|
|
$query = substr($query, 0, strlen($query) - 1);
|
|
break;
|
|
default:
|
|
return -4;
|
|
}
|
|
|
|
// Generate count query
|
|
$count = "SELECT COUNT(*) AS total FROM ($query) AS sub";
|
|
|
|
// Open a transaction
|
|
$status = $this->beginTransaction();
|
|
if ($status != 0) return -1;
|
|
|
|
// If backend supports read only queries, then specify read only mode
|
|
// to avoid side effects from repeating queries that do writes.
|
|
if ($this->hasReadOnlyQueries()) {
|
|
$status = $this->execute("SET TRANSACTION READ ONLY");
|
|
if ($status != 0) {
|
|
$this->rollbackTransaction();
|
|
return -5;
|
|
}
|
|
}
|
|
|
|
|
|
// Count the number of rows
|
|
$total = $this->browseQueryCount($query, $count);
|
|
if ($total < 0) {
|
|
$this->rollbackTransaction();
|
|
return -2;
|
|
}
|
|
|
|
// Calculate max pages
|
|
$max_pages = ceil($total / $page_size);
|
|
|
|
// Check that page is less than or equal to max pages
|
|
if (!is_numeric($page) || $page != (int)$page || $page > $max_pages || $page < 1) {
|
|
$this->rollbackTransaction();
|
|
return -3;
|
|
}
|
|
|
|
// Set fetch mode to NUM so that duplicate field names are properly returned
|
|
// for non-table queries. Since the SELECT feature only allows selecting one
|
|
// table, duplicate fields shouldn't appear.
|
|
if ($type == 'QUERY') $this->conn->setFetchMode(ADODB_FETCH_NUM);
|
|
|
|
// Figure out ORDER BY. Sort key is always the column number (based from one)
|
|
// of the column to order by. Only need to do this for non-TABLE queries
|
|
if ($type != 'TABLE' && 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
|
|
WHERE schemaname='{$c_schema}' AND relname='{$table}'";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Fetches I/0 statistics for a table
|
|
* @param $table The table to fetch stats for
|
|
* @return A recordset
|
|
*/
|
|
function getStatsTableIO($table) {
|
|
$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}'";
|
|
|
|
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);
|
|
|
|
$sql = "SELECT * FROM pg_stat_all_indexes
|
|
WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname";
|
|
|
|
return $this->selectSet($sql);
|
|
}
|
|
|
|
/**
|
|
* Fetches I/0 statistics for all indexes on a table
|
|
* @param $table The table to fetch index stats for
|
|
* @return A recordset
|
|
*/
|
|
function getStatsIndexIO($table) {
|
|
$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);
|
|
}
|
|
|
|
// 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 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; }
|
|
|
|
}
|
|
?>
|