10 defined(
'JPATH_PLATFORM') or die;
27 public $name =
'postgresql';
35 protected $nameQuote =
'"';
43 protected $nullDate =
'1970-01-01 00:00:00';
51 protected static $dbMinimum =
'8.3.18';
59 protected $concat_operator =
'||';
67 protected $queryObject = null;
76 public function __construct( $options )
78 $options[
'host'] = (isset($options[
'host'])) ? $options[
'host'] :
'localhost';
79 $options[
'user'] = (isset($options[
'user'])) ? $options[
'user'] :
'';
80 $options[
'password'] = (isset($options[
'password'])) ? $options[
'password'] :
'';
81 $options[
'database'] = (isset($options[
'database'])) ? $options[
'database'] :
'';
84 parent::__construct($options);
92 public function __destruct()
105 public function connect()
107 if ($this->connection)
113 if (!function_exists(
'pg_connect'))
115 throw new RuntimeException(
'PHP extension pg_connect is not available.');
119 $dsn =
"host={$this->options['host']} dbname={$this->options['database']} user={$this->options['user']} password={$this->options['password']}";
122 if (!($this->connection = @pg_connect($dsn)))
124 throw new RuntimeException(
'Error connecting to PGSQL database.');
127 pg_set_error_verbosity($this->connection, PGSQL_ERRORS_DEFAULT);
128 pg_query(
'SET standard_conforming_strings=off');
138 public function disconnect()
141 if (is_resource($this->connection))
143 foreach ($this->disconnectHandlers as $h)
145 call_user_func_array($h, array( &$this));
148 pg_close($this->connection);
151 $this->connection = null;
164 public function escape($text, $extra =
false)
168 $result = pg_escape_string($this->connection, $text);
172 $result = addcslashes($result,
'%_');
185 public static function test()
187 return (function_exists(
'pg_connect'));
197 public function connected()
201 if (is_resource($this->connection))
203 return pg_ping($this->connection);
220 public function dropTable($tableName, $ifExists =
true)
224 $this->setQuery(
'DROP TABLE ' . ($ifExists ?
'IF EXISTS ' :
'') . $this->quoteName($tableName));
237 public function getAffectedRows()
241 return pg_affected_rows($this->cursor);
252 public function getCollation()
256 $this->setQuery(
'SHOW LC_COLLATE');
257 $array = $this->loadAssocList();
259 return $array[0][
'lc_collate'];
271 public function getNumRows($cur = null)
275 return pg_num_rows((
int) $cur ? $cur : $this->cursor);
289 public function getQuery($new =
false, $asObj =
false)
294 if (!class_exists(
'JDatabaseQueryPostgresql'))
296 throw new RuntimeException(
'JDatabaseQueryPostgresql Class not found.');
301 return $this->queryObject;
307 return $this->queryObject;
327 public function getTableCreate($tables)
343 public function getTableColumns($table, $typeOnly =
true)
349 $tableSub = $this->replacePrefix($table);
352 SELECT a.attname AS "column_name",
353 pg_catalog.format_type(a.atttypid, a.atttypmod) as "type",
354 CASE WHEN a.attnotnull IS TRUE
358 CASE WHEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) IS NOT NULL
359 THEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true)
361 CASE WHEN pg_catalog.col_description(a.attrelid, a.attnum) IS NULL
363 ELSE pg_catalog.col_description(a.attrelid, a.attnum)
365 FROM pg_catalog.pg_attribute a
366 LEFT JOIN pg_catalog.pg_attrdef adef ON a.attrelid=adef.adrelid AND a.attnum=adef.adnum
367 LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
369 (SELECT oid FROM pg_catalog.pg_class WHERE relname=' . $this->quote($tableSub) .
'
370 AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
371 nspname = \'public\')
373 AND a.attnum > 0 AND NOT a.attisdropped
377 $fields = $this->loadObjectList();
381 foreach ($fields as $field)
383 $result[$field->column_name] = preg_replace(
"/[(0-9)]/",
'', $field->type);
388 foreach ($fields as $field)
390 $result[$field->column_name] = $field;
395 foreach ($fields as $field)
397 if (preg_match(
"/^NULL::*/", $field->Default))
399 $field->Default = null;
416 public function getTableKeys($table)
421 $tableList = $this->getTableList();
423 if (in_array($table, $tableList))
427 SELECT indexname AS "idxName", indisprimary AS "isPrimary", indisunique AS "isUnique",
428 CASE WHEN indisprimary = true THEN
429 ( SELECT \'ALTER TABLE \' || tablename || \' ADD \' || pg_catalog.pg_get_constraintdef(const.oid, true)
430 FROM pg_constraint AS const WHERE const.conname= pgClassFirst.relname )
431 ELSE pg_catalog.pg_get_indexdef(indexrelid, 0, true)
434 LEFT JOIN pg_class AS pgClassFirst ON indexname=pgClassFirst.relname
435 LEFT JOIN pg_index AS pgIndex ON pgClassFirst.oid=pgIndex.indexrelid
436 WHERE tablename=' . $this->quote($table) .
' ORDER BY indkey'
439 $keys = $this->loadObjectList();
455 public function getTableList()
459 $query = $this->getQuery(
true)
460 ->select(
'table_name')
461 ->from(
'information_schema.tables')
462 ->where(
'table_type=' . $this->quote(
'BASE TABLE'))
463 ->where(
'table_schema NOT IN (' . $this->quote(
'pg_catalog') .
', ' . $this->quote(
'information_schema') .
')')
464 ->order(
'table_name ASC');
466 $this->setQuery($query);
467 $tables = $this->loadColumn();
482 public function getTableSequences($table)
487 $tableList = $this->getTableList();
489 if (in_array($table, $tableList))
492 's.relname',
'n.nspname',
't.relname',
'a.attname',
'info.data_type',
'info.minimum_value',
'info.maximum_value',
493 'info.increment',
'info.cycle_option'
495 $as = array(
'sequence',
'schema',
'table',
'column',
'data_type',
'minimum_value',
'maximum_value',
'increment',
'cycle_option');
497 if (version_compare($this->getVersion(),
'9.1.0') >= 0)
499 $name[] .=
'info.start_value';
500 $as[] .=
'start_value';
504 $query = $this->getQuery(
true)
505 ->select($this->quoteName($name, $as))
506 ->from(
'pg_class AS s')
507 ->join(
'LEFT',
"pg_depend d ON d.objid=s.oid AND d.classid='pg_class'::regclass AND d.refclassid='pg_class'::regclass")
508 ->join(
'LEFT',
'pg_class t ON t.oid=d.refobjid')
509 ->join(
'LEFT',
'pg_namespace n ON n.oid=t.relnamespace')
510 ->join(
'LEFT',
'pg_attribute a ON a.attrelid=t.oid AND a.attnum=d.refobjsubid')
511 ->join(
'LEFT',
'information_schema.sequences AS info ON info.sequence_name=s.relname')
512 ->where(
"s.relkind='S' AND d.deptype='a' AND t.relname=" . $this->quote($table));
513 $this->setQuery($query);
514 $seq = $this->loadObjectList();
529 public function getVersion()
532 $version = pg_version($this->connection);
534 return $version[
'server'];
567 public function insertid()
570 $insertQuery = $this->getQuery(
false,
true);
571 $table = $insertQuery->__get(
'insert')->getElements();
574 $colNameQuery = $this->getQuery(
true);
575 $colNameQuery->select(
'column_default')
576 ->from(
'information_schema.columns')
577 ->where(
"table_name=" . $this->quote($this->replacePrefix(str_replace(
'"',
'', $table[0]))),
'AND')
578 ->where(
"column_default LIKE '%nextval%'");
580 $this->setQuery($colNameQuery);
581 $colName = $this->loadRow();
582 $changedColName = str_replace(
'nextval',
'currval', $colName);
584 $insertidQuery = $this->getQuery(
true);
585 $insertidQuery->select($changedColName);
586 $this->setQuery($insertidQuery);
587 $insertVal = $this->loadRow();
589 return $insertVal[0];
602 public function lockTable($tableName)
604 $this->transactionStart();
605 $this->setQuery(
'LOCK TABLE ' . $this->quoteName($tableName) .
' IN ACCESS EXCLUSIVE MODE')->execute();
618 public function execute()
622 if (!is_resource($this->connection))
625 throw new RuntimeException($this->errorMsg, $this->errorNum);
629 $query = $this->replacePrefix((
string) $this->sql);
631 if (!($this->sql instanceof
JDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
633 $query .=
' LIMIT ' . $this->limit .
' OFFSET ' . $this->offset;
641 $this->errorMsg =
'';
647 $this->log[] = $query;
651 $this->timings[] = microtime(
true);
655 $this->cursor = @pg_query($this->connection, $query);
659 $this->timings[] = microtime(
true);
661 if (defined(
'DEBUG_BACKTRACE_IGNORE_ARGS'))
663 $this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
667 $this->callStacks[] = debug_backtrace();
675 if (!$this->connected())
680 $this->connection = null;
684 catch (RuntimeException $e)
687 $this->errorNum = (int) pg_result_error_field($this->cursor, PGSQL_DIAG_SQLSTATE) .
' ';
688 $this->errorMsg =
JText::_(
'JLIB_DATABASE_QUERY_FAILED') .
"\n" . pg_last_error($this->connection) .
"\nSQL=" . $query;
692 throw new RuntimeException($this->errorMsg);
696 return $this->execute();
702 $this->errorNum = (int) pg_result_error_field($this->cursor, PGSQL_DIAG_SQLSTATE) .
' ';
703 $this->errorMsg =
JText::_(
'JLIB_DATABASE_QUERY_FAILED') .
"\n" . pg_last_error($this->connection) .
"\nSQL=" . $query;
707 throw new RuntimeException($this->errorMsg);
711 return $this->cursor;
727 public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
732 $tableList = $this->getTableList();
735 if (!in_array($oldTable, $tableList))
738 throw new RuntimeException(
'Table not found in Postgresql database.');
748 FROM pg_index, pg_class
749 WHERE pg_class.relname=' . $this->quote($oldTable,
true) .
'
750 AND pg_class.oid=pg_index.indrelid );'
753 $oldIndexes = $this->loadColumn();
755 foreach ($oldIndexes as $oldIndex)
757 $changedIdxName = str_replace($oldTable, $newTable, $oldIndex);
758 $this->setQuery(
'ALTER INDEX ' . $this->escape($oldIndex) .
' RENAME TO ' . $this->escape($changedIdxName));
766 WHERE relkind = \'S\'
767 AND relnamespace IN (
770 WHERE nspname NOT LIKE \'pg_%\'
771 AND nspname != \'information_schema\'
773 AND relname LIKE \'%' . $oldTable .
'%\' ;'
776 $oldSequences = $this->loadColumn();
778 foreach ($oldSequences as $oldSequence)
780 $changedSequenceName = str_replace($oldTable, $newTable, $oldSequence);
781 $this->setQuery(
'ALTER SEQUENCE ' . $this->escape($oldSequence) .
' RENAME TO ' . $this->escape($changedSequenceName));
786 $this->setQuery(
'ALTER TABLE ' . $this->escape($oldTable) .
' RENAME TO ' . $this->escape($newTable));
802 public function select($database)
814 public function setUTF()
818 return pg_set_client_encoding($this->connection,
'UTF8');
832 public function sqlValue($columns, $field_name, $field_value)
834 switch ($columns[$field_name])
839 if ($field_value ==
't')
843 elseif ($field_value ==
'f')
859 $val = strlen($field_value) == 0 ?
'NULL' : $field_value;
863 case 'timestamp without time zone':
864 if (empty($field_value))
866 $field_value = $this->getNullDate();
869 $val = $this->quote($field_value);
873 $val = $this->quote($field_value);
890 public function transactionCommit($toSavepoint =
false)
894 if (!$toSavepoint || $this->transactionDepth <= 1)
896 if ($this->setQuery(
'COMMIT')->execute())
898 $this->transactionDepth = 0;
904 $this->transactionDepth--;
917 public function transactionRollback($toSavepoint =
false)
921 if (!$toSavepoint || $this->transactionDepth <= 1)
923 if ($this->setQuery(
'ROLLBACK')->execute())
925 $this->transactionDepth = 0;
931 $savepoint =
'SP_' . ($this->transactionDepth - 1);
932 $this->setQuery(
'ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));
934 if ($this->execute())
936 $this->transactionDepth--;
937 $this->setQuery(
'RELEASE SAVEPOINT ' . $this->quoteName($savepoint))->execute();
951 public function transactionStart($asSavepoint =
false)
955 if (!$asSavepoint || !$this->transactionDepth)
957 if ($this->setQuery(
'START TRANSACTION')->execute())
959 $this->transactionDepth = 1;
965 $savepoint =
'SP_' . $this->transactionDepth;
966 $this->setQuery(
'SAVEPOINT ' . $this->quoteName($savepoint));
968 if ($this->execute())
970 $this->transactionDepth++;
983 protected function fetchArray($cursor = null)
985 return pg_fetch_row($cursor ? $cursor : $this->cursor);
997 protected function fetchAssoc($cursor = null)
999 return pg_fetch_assoc($cursor ? $cursor : $this->cursor);
1012 protected function fetchObject($cursor = null, $class =
'stdClass')
1014 return pg_fetch_object(is_null($cursor) ? $this->cursor : $cursor, null, $class);
1026 protected function freeResult($cursor = null)
1028 pg_free_result($cursor ? $cursor : $this->cursor);
1043 public function insertObject($table, &$object, $key = null)
1045 $columns = $this->getTableColumns($table);
1051 foreach (get_object_vars($object) as $k => $v)
1054 if (is_array($v) or is_object($v) or $v === null)
1066 $fields[] = $this->quoteName($k);
1067 $values[] = $this->sqlValue($columns, $k, $v);
1071 $query = $this->getQuery(
true)
1072 ->insert($this->quoteName($table))
1074 ->values(implode(
',', $values));
1080 $query->returning($key);
1083 $this->setQuery($query);
1085 $id = $this->loadResult();
1089 $object->$key = $id;
1096 $this->setQuery($query);
1098 if ($this->execute())
1114 public static function isSupported()
1116 return (function_exists(
'pg_connect'));
1126 public function showTables()
1130 $query = $this->getQuery(
true)
1131 ->select(
'table_name')
1132 ->from(
'information_schema.tables')
1133 ->where(
'table_type = ' . $this->quote(
'BASE TABLE'))
1134 ->where(
'table_schema NOT IN (' . $this->quote(
'pg_catalog') .
', ' . $this->quote(
'information_schema') .
' )');
1136 $this->setQuery($query);
1137 $tableList = $this->loadColumn();
1152 public function getStringPositionSQL( $substring, $string )
1156 $query =
"SELECT POSITION( $substring IN $string )";
1157 $this->setQuery($query);
1158 $position = $this->loadRow();
1160 return $position[
'position'];
1170 public function getRandom()
1174 $this->setQuery(
'SELECT RANDOM()');
1175 $random = $this->loadAssoc();
1177 return $random[
'random'];
1189 public function getAlterDbCharacterSet( $dbName )
1191 $query =
'ALTER DATABASE ' . $this->quoteName($dbName) .
' SET CLIENT_ENCODING TO ' . $this->quote(
'UTF8');
1206 public function getCreateDbQuery($options, $utf)
1208 $query =
'CREATE DATABASE ' . $this->quoteName($options->db_name) .
' OWNER ' . $this->quoteName($options->db_user);
1212 $query .=
' ENCODING ' . $this->quote(
'UTF-8');
1229 public function replacePrefix($query, $prefix =
'#__')
1231 $query = trim($query);
1233 if (strpos($query,
'\''))
1236 if (strpos($query,
'currval'))
1238 $query = explode(
'currval', $query);
1240 for ($nIndex = 1; $nIndex < count($query); $nIndex = $nIndex + 2)
1242 $query[$nIndex] = str_replace($prefix, $this->tablePrefix, $query[$nIndex]);
1245 $query = implode(
'currval', $query);
1249 if (strpos($query,
'nextval'))
1251 $query = explode(
'nextval', $query);
1253 for ($nIndex = 1; $nIndex < count($query); $nIndex = $nIndex + 2)
1255 $query[$nIndex] = str_replace($prefix, $this->tablePrefix, $query[$nIndex]);
1258 $query = implode(
'nextval', $query);
1262 if (strpos($query,
'setval'))
1264 $query = explode(
'setval', $query);
1266 for ($nIndex = 1; $nIndex < count($query); $nIndex = $nIndex + 2)
1268 $query[$nIndex] = str_replace($prefix, $this->tablePrefix, $query[$nIndex]);
1271 $query = implode(
'setval', $query);
1274 $explodedQuery = explode(
'\'', $query);
1276 for ($nIndex = 0; $nIndex < count($explodedQuery); $nIndex = $nIndex + 2)
1278 if (strpos($explodedQuery[$nIndex], $prefix))
1280 $explodedQuery[$nIndex] = str_replace($prefix, $this->tablePrefix, $explodedQuery[$nIndex]);
1284 $replacedQuery = implode(
'\'', $explodedQuery);
1288 $replacedQuery = str_replace($prefix, $this->tablePrefix, $query);
1291 return $replacedQuery;
1303 public function releaseTransactionSavepoint( $savepointName )
1306 $this->setQuery(
'RELEASE SAVEPOINT ' . $this->quoteName($this->escape($savepointName)));
1319 public function transactionSavepoint( $savepointName )
1322 $this->setQuery(
'SAVEPOINT ' . $this->quoteName($this->escape($savepointName)));
1335 public function unlockTables()
1337 $this->transactionCommit();
1355 public function updateObject($table, &$object, $key, $nulls =
false)
1357 $columns = $this->getTableColumns($table);
1361 if (is_string($key))
1366 if (is_object($key))
1368 $key = (array) $key;
1372 $statement =
'UPDATE ' . $this->quoteName($table) .
' SET %s WHERE %s';
1375 foreach (get_object_vars($object) as $k => $v)
1378 if (is_array($v) or is_object($v) or $k[0] ==
'_')
1384 if (in_array($k, $key))
1386 $key_val = $this->sqlValue($columns, $k, $v);
1387 $where[] = $this->quoteName($k) .
'=' . $key_val;
1408 $val = $this->sqlValue($columns, $k, $v);
1412 $fields[] = $this->quoteName($k) .
'=' . $val;
1422 $this->setQuery(sprintf($statement, implode(
",", $fields), implode(
' AND ', $where)));
1424 return $this->execute();