MediaWiki  master
DatabasePostgres.php
Go to the documentation of this file.
1 <?php
23 namespace Wikimedia\Rdbms;
24 
25 use RuntimeException;
26 use Wikimedia\Timestamp\ConvertibleTimestamp;
27 use Wikimedia\WaitConditionLoop;
28 
32 class DatabasePostgres extends Database {
34  private $port;
36  private $coreSchema;
38  private $tempSchema;
42  private $numericVersion;
43 
46 
54  public function __construct( array $params ) {
55  $this->port = intval( $params['port'] ?? null );
56 
57  if ( isset( $params['keywordTableMap'] ) ) {
58  wfDeprecatedMsg( 'Passing keywordTableMap parameter to ' .
59  'DatabasePostgres::__construct() is deprecated', '1.37'
60  );
61 
62  $this->keywordTableMap = $params['keywordTableMap'];
63  }
64 
65  parent::__construct( $params );
66  }
67 
68  public function getType() {
69  return 'postgres';
70  }
71 
72  public function implicitOrderby() {
73  return false;
74  }
75 
76  protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
77  if ( !function_exists( 'pg_connect' ) ) {
78  throw $this->newExceptionAfterConnectError(
79  "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
80  "option? (Note: if you recently installed PHP, you may need to restart your\n" .
81  "webserver and database)"
82  );
83  }
84 
85  $this->close( __METHOD__ );
86 
87  $connectVars = [
88  // A database must be specified in order to connect to Postgres. If $dbName is not
89  // specified, then use the standard "postgres" database that should exist by default.
90  'dbname' => strlen( $db ) ? $db : 'postgres',
91  'user' => $user,
92  'password' => $password
93  ];
94  if ( strlen( $server ) ) {
95  $connectVars['host'] = $server;
96  }
97  if ( $this->port > 0 ) {
98  $connectVars['port'] = $this->port;
99  }
100  if ( $this->getFlag( self::DBO_SSL ) ) {
101  $connectVars['sslmode'] = 'require';
102  }
103  $connectString = $this->makeConnectionString( $connectVars );
104 
105  $this->installErrorHandler();
106  try {
107  $this->conn = pg_connect( $connectString, PGSQL_CONNECT_FORCE_NEW ) ?: null;
108  } catch ( RuntimeException $e ) {
109  $this->restoreErrorHandler();
110  throw $this->newExceptionAfterConnectError( $e->getMessage() );
111  }
112  $error = $this->restoreErrorHandler();
113 
114  if ( !$this->conn ) {
115  throw $this->newExceptionAfterConnectError( $error ?: $this->lastError() );
116  }
117 
118  try {
119  // Since no transaction is active at this point, any SET commands should apply
120  // for the entire session (e.g. will not be reverted on transaction rollback).
121  // See https://www.postgresql.org/docs/8.3/sql-set.html
122  $variables = [
123  'client_encoding' => 'UTF8',
124  'datestyle' => 'ISO, YMD',
125  'timezone' => 'GMT',
126  'standard_conforming_strings' => 'on',
127  'bytea_output' => 'escape',
128  'client_min_messages' => 'ERROR'
129  ];
130  foreach ( $variables as $var => $val ) {
131  $this->query(
132  'SET ' . $this->addIdentifierQuotes( $var ) . ' = ' . $this->addQuotes( $val ),
133  __METHOD__,
134  self::QUERY_IGNORE_DBO_TRX | self::QUERY_NO_RETRY | self::QUERY_CHANGE_TRX
135  );
136  }
137  $this->determineCoreSchema( $schema );
138  $this->currentDomain = new DatabaseDomain( $db, $schema, $tablePrefix );
139  } catch ( RuntimeException $e ) {
140  throw $this->newExceptionAfterConnectError( $e->getMessage() );
141  }
142  }
143 
144  protected function relationSchemaQualifier() {
145  if ( $this->coreSchema === $this->currentDomain->getSchema() ) {
146  // The schema to be used is now in the search path; no need for explicit qualification
147  return '';
148  }
149 
150  return parent::relationSchemaQualifier();
151  }
152 
153  public function databasesAreIndependent() {
154  return true;
155  }
156 
157  public function doSelectDomain( DatabaseDomain $domain ) {
158  if ( $this->getDBname() !== $domain->getDatabase() ) {
159  // Postgres doesn't support selectDB in the same way MySQL does.
160  // So if the DB name doesn't match the open connection, open a new one
161  $this->open(
162  $this->connectionParams[self::CONN_HOST],
163  $this->connectionParams[self::CONN_USER],
164  $this->connectionParams[self::CONN_PASSWORD],
165  $domain->getDatabase(),
166  $domain->getSchema(),
167  $domain->getTablePrefix()
168  );
169  } else {
170  $this->currentDomain = $domain;
171  }
172 
173  return true;
174  }
175 
180  private function makeConnectionString( $vars ) {
181  $s = '';
182  foreach ( $vars as $name => $value ) {
183  $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
184  }
185 
186  return $s;
187  }
188 
189  protected function closeConnection() {
190  return $this->conn ? pg_close( $this->conn ) : true;
191  }
192 
193  protected function isTransactableQuery( $sql ) {
194  return parent::isTransactableQuery( $sql ) &&
195  !preg_match( '/^SELECT\s+pg_(try_|)advisory_\w+\‍(/', $sql );
196  }
197 
202  public function doQuery( $sql ) {
203  $conn = $this->getBindingHandle();
204 
205  $sql = mb_convert_encoding( $sql, 'UTF-8' );
206  // Clear previously left over PQresult
207  while ( $res = pg_get_result( $conn ) ) {
208  pg_free_result( $res );
209  }
210  if ( pg_send_query( $conn, $sql ) === false ) {
211  throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
212  }
213  $this->lastResultHandle = pg_get_result( $conn );
214  if ( pg_result_error( $this->lastResultHandle ) ) {
215  return false;
216  }
217 
218  return $this->lastResultHandle ?
219  new PostgresResultWrapper( $this, $this->getBindingHandle(), $this->lastResultHandle ) : false;
220  }
221 
222  protected function dumpError() {
223  $diags = [
224  PGSQL_DIAG_SEVERITY,
225  PGSQL_DIAG_SQLSTATE,
226  PGSQL_DIAG_MESSAGE_PRIMARY,
227  PGSQL_DIAG_MESSAGE_DETAIL,
228  PGSQL_DIAG_MESSAGE_HINT,
229  PGSQL_DIAG_STATEMENT_POSITION,
230  PGSQL_DIAG_INTERNAL_POSITION,
231  PGSQL_DIAG_INTERNAL_QUERY,
232  PGSQL_DIAG_CONTEXT,
233  PGSQL_DIAG_SOURCE_FILE,
234  PGSQL_DIAG_SOURCE_LINE,
235  PGSQL_DIAG_SOURCE_FUNCTION
236  ];
237  foreach ( $diags as $d ) {
238  $this->queryLogger->debug( sprintf( "PgSQL ERROR(%d): %s",
239  $d, pg_result_error_field( $this->lastResultHandle, $d ) ) );
240  }
241  }
242 
243  public function insertId() {
244  $res = $this->query(
245  "SELECT lastval()",
246  __METHOD__,
247  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
248  );
249  $row = $this->fetchRow( $res );
250 
251  return $row[0] === null ? null : (int)$row[0];
252  }
253 
254  public function lastError() {
255  if ( $this->conn ) {
256  if ( $this->lastResultHandle ) {
257  return pg_result_error( $this->lastResultHandle );
258  } else {
259  return pg_last_error();
260  }
261  }
262 
263  return $this->getLastPHPError() ?: 'No database connection';
264  }
265 
266  public function lastErrno() {
267  if ( $this->lastResultHandle ) {
268  return pg_result_error_field( $this->lastResultHandle, PGSQL_DIAG_SQLSTATE );
269  } else {
270  return false;
271  }
272  }
273 
274  protected function fetchAffectedRowCount() {
275  if ( !$this->lastResultHandle ) {
276  return 0;
277  }
278 
279  return pg_affected_rows( $this->lastResultHandle );
280  }
281 
297  public function estimateRowCount( $table, $var = '*', $conds = '',
298  $fname = __METHOD__, $options = [], $join_conds = []
299  ) {
300  $conds = $this->normalizeConditions( $conds, $fname );
301  $column = $this->extractSingleFieldFromList( $var );
302  if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
303  $conds[] = "$column IS NOT NULL";
304  }
305 
306  $options['EXPLAIN'] = true;
307  $res = $this->select( $table, $var, $conds, $fname, $options, $join_conds );
308  $rows = -1;
309  if ( $res ) {
310  $row = $this->fetchRow( $res );
311  $count = [];
312  if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
313  $rows = (int)$count[1];
314  }
315  }
316 
317  return $rows;
318  }
319 
320  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
321  $res = $this->query(
322  "SELECT indexname FROM pg_indexes WHERE tablename='$table'",
323  $fname,
324  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
325  );
326  if ( !$res ) {
327  return null;
328  }
329  foreach ( $res as $row ) {
330  if ( $row->indexname == $this->indexName( $index ) ) {
331  return $row;
332  }
333  }
334 
335  return false;
336  }
337 
338  public function indexAttributes( $index, $schema = false ) {
339  if ( $schema === false ) {
340  $schemas = $this->getCoreSchemas();
341  } else {
342  $schemas = [ $schema ];
343  }
344 
345  $eindex = $this->addQuotes( $index );
346 
347  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
348  foreach ( $schemas as $schema ) {
349  $eschema = $this->addQuotes( $schema );
350  /*
351  * A subquery would be not needed if we didn't care about the order
352  * of attributes, but we do
353  */
354  $sql = <<<__INDEXATTR__
355 
356  SELECT opcname,
357  attname,
358  i.indoption[s.g] as option,
359  pg_am.amname
360  FROM
361  (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
362  FROM
363  pg_index isub
364  JOIN pg_class cis
365  ON cis.oid=isub.indexrelid
366  JOIN pg_namespace ns
367  ON cis.relnamespace = ns.oid
368  WHERE cis.relname=$eindex AND ns.nspname=$eschema) AS s,
369  pg_attribute,
370  pg_opclass opcls,
371  pg_am,
372  pg_class ci
373  JOIN pg_index i
374  ON ci.oid=i.indexrelid
375  JOIN pg_class ct
376  ON ct.oid = i.indrelid
377  JOIN pg_namespace n
378  ON ci.relnamespace = n.oid
379  WHERE
380  ci.relname=$eindex AND n.nspname=$eschema
381  AND attrelid = ct.oid
382  AND i.indkey[s.g] = attnum
383  AND i.indclass[s.g] = opcls.oid
384  AND pg_am.oid = opcls.opcmethod
385 __INDEXATTR__;
386  $res = $this->query( $sql, __METHOD__, $flags );
387  $a = [];
388  if ( $res ) {
389  foreach ( $res as $row ) {
390  $a[] = [
391  $row->attname,
392  $row->opcname,
393  $row->amname,
394  $row->option ];
395  }
396  return $a;
397  }
398  }
399  return null;
400  }
401 
402  public function indexUnique( $table, $index, $fname = __METHOD__ ) {
403  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
404  $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
405  " AND indexdef LIKE 'CREATE UNIQUE%(" .
406  $this->strencode( $this->indexName( $index ) ) .
407  ")'";
408  $res = $this->query( $sql, $fname, $flags );
409  if ( !$res ) {
410  return null;
411  }
412 
413  return $res->numRows() > 0;
414  }
415 
416  public function selectSQLText(
417  $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
418  ) {
419  if ( is_string( $options ) ) {
420  $options = [ $options ];
421  }
422 
423  // Change the FOR UPDATE option as necessary based on the join conditions. Then pass
424  // to the parent function to get the actual SQL text.
425  // In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
426  // can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to
427  // do so causes a DB error. This wrapper checks which tables can be locked and adjusts it
428  // accordingly.
429  // MySQL uses "ORDER BY NULL" as an optimization hint, but that is illegal in PostgreSQL.
430  if ( is_array( $options ) ) {
431  $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
432  if ( $forUpdateKey !== false && $join_conds ) {
433  unset( $options[$forUpdateKey] );
434  $options['FOR UPDATE'] = [];
435 
436  $toCheck = $table;
437  reset( $toCheck );
438  while ( $toCheck ) {
439  $alias = key( $toCheck );
440  $name = $toCheck[$alias];
441  unset( $toCheck[$alias] );
442 
443  $hasAlias = !is_numeric( $alias );
444  if ( !$hasAlias && is_string( $name ) ) {
445  $alias = $name;
446  }
447 
448  if ( !isset( $join_conds[$alias] ) ||
449  !preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_conds[$alias][0] )
450  ) {
451  if ( is_array( $name ) ) {
452  // It's a parenthesized group, process all the tables inside the group.
453  $toCheck = array_merge( $toCheck, $name );
454  } else {
455  // Quote alias names so $this->tableName() won't mangle them
456  $options['FOR UPDATE'][] = $hasAlias ? $this->addIdentifierQuotes( $alias ) : $alias;
457  }
458  }
459  }
460  }
461 
462  if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
463  unset( $options['ORDER BY'] );
464  }
465  }
466 
467  return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
468  }
469 
471  return [ 'INSERT INTO', 'ON CONFLICT DO NOTHING' ];
472  }
473 
474  public function doInsertNonConflicting( $table, array $rows, $fname ) {
475  // Postgres 9.5 supports "ON CONFLICT"
476  if ( $this->getServerVersion() >= 9.5 ) {
477  parent::doInsertNonConflicting( $table, $rows, $fname );
478 
479  return;
480  }
481 
482  $affectedRowCount = 0;
483  // Emulate INSERT IGNORE via savepoints/rollback
484  $tok = $this->startAtomic( "$fname (outer)", self::ATOMIC_CANCELABLE );
485  try {
486  $encTable = $this->tableName( $table );
487  foreach ( $rows as $row ) {
488  list( $sqlColumns, $sqlTuples ) = $this->makeInsertLists( [ $row ] );
489  $tempsql = "INSERT INTO $encTable ($sqlColumns) VALUES $sqlTuples";
490 
491  $this->startAtomic( "$fname (inner)", self::ATOMIC_CANCELABLE );
492  try {
493  $this->query( $tempsql, $fname, self::QUERY_CHANGE_ROWS );
494  $this->endAtomic( "$fname (inner)" );
496  } catch ( DBQueryError $e ) {
497  $this->cancelAtomic( "$fname (inner)" );
498  // Our IGNORE is supposed to ignore duplicate key errors, but not others.
499  // (even though MySQL's version apparently ignores all errors)
500  if ( $e->errno !== '23505' ) {
501  throw $e;
502  }
503  }
504  }
505  } catch ( RuntimeException $e ) {
506  $this->cancelAtomic( "$fname (outer)", $tok );
507  throw $e;
508  }
509  $this->endAtomic( "$fname (outer)" );
510  // Set the affected row count for the whole operation
511  $this->affectedRowCount = $affectedRowCount;
512  }
513 
514  protected function makeUpdateOptionsArray( $options ) {
515  $options = $this->normalizeOptions( $options );
516  // PostgreSQL doesn't support anything like "ignore" for UPDATE.
517  $options = array_diff( $options, [ 'IGNORE' ] );
518 
519  return parent::makeUpdateOptionsArray( $options );
520  }
521 
540  protected function doInsertSelectNative(
541  $destTable,
542  $srcTable,
543  array $varMap,
544  $conds,
545  $fname,
546  array $insertOptions,
547  array $selectOptions,
548  $selectJoinConds
549  ) {
550  if ( in_array( 'IGNORE', $insertOptions ) ) {
551  if ( $this->getServerVersion() >= 9.5 ) {
552  // Use "ON CONFLICT DO" if we have it for IGNORE
553  $destTable = $this->tableName( $destTable );
554 
555  $selectSql = $this->selectSQLText(
556  $srcTable,
557  array_values( $varMap ),
558  $conds,
559  $fname,
560  $selectOptions,
561  $selectJoinConds
562  );
563 
564  $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' .
565  $selectSql . ' ON CONFLICT DO NOTHING';
566 
567  $this->query( $sql, $fname, self::QUERY_CHANGE_ROWS );
568  } else {
569  // IGNORE and we don't have ON CONFLICT DO NOTHING, so just use the non-native version
570  $this->doInsertSelectGeneric(
571  $destTable, $srcTable, $varMap, $conds, $fname,
572  $insertOptions, $selectOptions, $selectJoinConds
573  );
574  }
575  } else {
576  parent::doInsertSelectNative( $destTable, $srcTable, $varMap, $conds, $fname,
577  $insertOptions, $selectOptions, $selectJoinConds );
578  }
579  }
580 
581  public function tableName( $name, $format = 'quoted' ) {
582  return parent::tableName( $name, $format );
583  }
584 
590  public function remappedTableName( $name ) {
591  wfDeprecated( __METHOD__, '1.37' );
592 
593  return $this->keywordTableMap[$name] ?? $name;
594  }
595 
601  public function realTableName( $name, $format = 'quoted' ) {
602  return parent::tableName( $name, $format );
603  }
604 
605  public function nextSequenceValue( $seqName ) {
606  return new NextSequenceValue;
607  }
608 
615  public function currentSequenceValue( $seqName ) {
616  $res = $this->query(
617  "SELECT currval('" . str_replace( "'", "''", $seqName ) . "')",
618  __METHOD__,
619  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
620  );
621  $row = $this->fetchRow( $res );
622  $currval = $row[0];
623 
624  return $currval;
625  }
626 
627  public function textFieldSize( $table, $field ) {
628  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
629  $encTable = $this->tableName( $table );
630  $sql = "SELECT t.typname as ftype,a.atttypmod as size
631  FROM pg_class c, pg_attribute a, pg_type t
632  WHERE relname='$encTable' AND a.attrelid=c.oid AND
633  a.atttypid=t.oid and a.attname='$field'";
634  $res = $this->query( $sql, __METHOD__, $flags );
635  $row = $this->fetchObject( $res );
636  if ( $row->ftype == 'varchar' ) {
637  $size = $row->size - 4;
638  } else {
639  $size = $row->size;
640  }
641 
642  return $size;
643  }
644 
645  public function limitResult( $sql, $limit, $offset = false ) {
646  return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
647  }
648 
649  public function wasDeadlock() {
650  // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
651  return $this->lastErrno() === '40P01';
652  }
653 
654  public function wasLockTimeout() {
655  // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
656  return $this->lastErrno() === '55P03';
657  }
658 
659  public function wasConnectionError( $errno ) {
660  // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
661  static $codes = [ '08000', '08003', '08006', '08001', '08004', '57P01', '57P03', '53300' ];
662 
663  return in_array( $errno, $codes, true );
664  }
665 
666  protected function wasKnownStatementRollbackError() {
667  return false; // transaction has to be rolled-back from error state
668  }
669 
670  public function duplicateTableStructure(
671  $oldName, $newName, $temporary = false, $fname = __METHOD__
672  ) {
673  $newNameE = $this->addIdentifierQuotes( $newName );
674  $oldNameE = $this->addIdentifierQuotes( $oldName );
675 
676  $temporary = $temporary ? 'TEMPORARY' : '';
677 
678  $ret = $this->query(
679  "CREATE $temporary TABLE $newNameE " .
680  "(LIKE $oldNameE INCLUDING DEFAULTS INCLUDING INDEXES)",
681  $fname,
682  self::QUERY_PSEUDO_PERMANENT | self::QUERY_CHANGE_SCHEMA
683  );
684  if ( !$ret ) {
685  return $ret;
686  }
687 
688  $res = $this->query(
689  'SELECT attname FROM pg_class c'
690  . ' JOIN pg_namespace n ON (n.oid = c.relnamespace)'
691  . ' JOIN pg_attribute a ON (a.attrelid = c.oid)'
692  . ' JOIN pg_attrdef d ON (c.oid=d.adrelid and a.attnum=d.adnum)'
693  . ' WHERE relkind = \'r\''
694  . ' AND nspname = ' . $this->addQuotes( $this->getCoreSchema() )
695  . ' AND relname = ' . $this->addQuotes( $oldName )
696  . ' AND pg_get_expr(adbin, adrelid) LIKE \'nextval(%\'',
697  $fname,
698  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
699  );
700  $row = $this->fetchObject( $res );
701  if ( $row ) {
702  $field = $row->attname;
703  $newSeq = "{$newName}_{$field}_seq";
704  $fieldE = $this->addIdentifierQuotes( $field );
705  $newSeqE = $this->addIdentifierQuotes( $newSeq );
706  $newSeqQ = $this->addQuotes( $newSeq );
707  $this->query(
708  "CREATE $temporary SEQUENCE $newSeqE OWNED BY $newNameE.$fieldE",
709  $fname,
710  self::QUERY_CHANGE_SCHEMA
711  );
712  $this->query(
713  "ALTER TABLE $newNameE ALTER COLUMN $fieldE SET DEFAULT nextval({$newSeqQ}::regclass)",
714  $fname,
715  self::QUERY_CHANGE_SCHEMA
716  );
717  }
718 
719  return $ret;
720  }
721 
722  protected function doTruncate( array $tables, $fname ) {
723  $encTables = $this->tableNamesN( ...$tables );
724  $sql = "TRUNCATE TABLE " . implode( ',', $encTables ) . " RESTART IDENTITY";
725  $this->query( $sql, $fname, self::QUERY_CHANGE_SCHEMA );
726  }
727 
734  public function listTables( $prefix = '', $fname = __METHOD__ ) {
735  $eschemas = implode( ',', array_map( [ $this, 'addQuotes' ], $this->getCoreSchemas() ) );
736  $result = $this->query(
737  "SELECT DISTINCT tablename FROM pg_tables WHERE schemaname IN ($eschemas)",
738  $fname,
739  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
740  );
741  $endArray = [];
742 
743  foreach ( $result as $table ) {
744  $vars = get_object_vars( $table );
745  $table = array_pop( $vars );
746  if ( $prefix == '' || strpos( $table, $prefix ) === 0 ) {
747  $endArray[] = $table;
748  }
749  }
750 
751  return $endArray;
752  }
753 
754  public function timestamp( $ts = 0 ) {
755  $ct = new ConvertibleTimestamp( $ts );
756 
757  return $ct->getTimestamp( TS_POSTGRES );
758  }
759 
778  private function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
779  if ( $limit === false ) {
780  $limit = strlen( $text ) - 1;
781  $output = [];
782  }
783  if ( $text == '{}' ) {
784  return $output;
785  }
786  do {
787  if ( $text[$offset] != '{' ) {
788  preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
789  $text, $match, 0, $offset );
790  $offset += strlen( $match[0] );
791  $output[] = ( $match[1][0] != '"'
792  ? $match[1]
793  : stripcslashes( substr( $match[1], 1, -1 ) ) );
794  if ( $match[3] == '},' ) {
795  return $output;
796  }
797  } else {
798  $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
799  }
800  } while ( $limit > $offset );
801 
802  return $output;
803  }
804 
805  public function aggregateValue( $valuedata, $valuename = 'value' ) {
806  return $valuedata;
807  }
808 
809  public function getSoftwareLink() {
810  return '[{{int:version-db-postgres-url}} PostgreSQL]';
811  }
812 
820  public function getCurrentSchema() {
821  $res = $this->query(
822  "SELECT current_schema()",
823  __METHOD__,
824  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
825  );
826  $row = $this->fetchRow( $res );
827 
828  return $row[0];
829  }
830 
841  public function getSchemas() {
842  $res = $this->query(
843  "SELECT current_schemas(false)",
844  __METHOD__,
845  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
846  );
847  $row = $this->fetchRow( $res );
848  $schemas = [];
849 
850  /* PHP pgsql support does not support array type, "{a,b}" string is returned */
851 
852  return $this->pg_array_parse( $row[0], $schemas );
853  }
854 
864  public function getSearchPath() {
865  $res = $this->query(
866  "SHOW search_path",
867  __METHOD__,
868  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
869  );
870  $row = $this->fetchRow( $res );
871 
872  /* PostgreSQL returns SHOW values as strings */
873 
874  return explode( ",", $row[0] );
875  }
876 
884  private function setSearchPath( $search_path ) {
885  $this->query(
886  "SET search_path = " . implode( ", ", $search_path ),
887  __METHOD__,
888  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_TRX
889  );
890  }
891 
906  public function determineCoreSchema( $desiredSchema ) {
907  if ( $this->trxLevel() ) {
908  // We do not want the schema selection to change on ROLLBACK or INSERT SELECT.
909  // See https://www.postgresql.org/docs/8.3/sql-set.html
910  throw new DBUnexpectedError(
911  $this,
912  __METHOD__ . ": a transaction is currently active"
913  );
914  }
915 
916  if ( $this->schemaExists( $desiredSchema ) ) {
917  if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
918  $this->coreSchema = $desiredSchema;
919  $this->queryLogger->debug(
920  "Schema \"" . $desiredSchema . "\" already in the search path\n" );
921  } else {
922  // Prepend the desired schema to the search path (T17816)
923  $search_path = $this->getSearchPath();
924  array_unshift( $search_path, $this->addIdentifierQuotes( $desiredSchema ) );
925  $this->setSearchPath( $search_path );
926  $this->coreSchema = $desiredSchema;
927  $this->queryLogger->debug(
928  "Schema \"" . $desiredSchema . "\" added to the search path\n" );
929  }
930  } else {
931  $this->coreSchema = $this->getCurrentSchema();
932  $this->queryLogger->debug(
933  "Schema \"" . $desiredSchema . "\" not found, using current \"" .
934  $this->coreSchema . "\"\n" );
935  }
936  }
937 
944  public function getCoreSchema() {
945  return $this->coreSchema;
946  }
947 
954  public function getCoreSchemas() {
955  if ( $this->tempSchema ) {
956  return [ $this->tempSchema, $this->getCoreSchema() ];
957  }
958 
959  $res = $this->query(
960  "SELECT nspname FROM pg_catalog.pg_namespace n WHERE n.oid = pg_my_temp_schema()",
961  __METHOD__,
962  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
963  );
964  $row = $this->fetchObject( $res );
965  if ( $row ) {
966  $this->tempSchema = $row->nspname;
967  return [ $this->tempSchema, $this->getCoreSchema() ];
968  }
969 
970  return [ $this->getCoreSchema() ];
971  }
972 
973  public function getServerVersion() {
974  if ( !isset( $this->numericVersion ) ) {
975  $conn = $this->getBindingHandle();
976  $versionInfo = pg_version( $conn );
977  if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
978  // Old client, abort install
979  $this->numericVersion = '7.3 or earlier';
980  } elseif ( isset( $versionInfo['server'] ) ) {
981  // Normal client
982  $this->numericVersion = $versionInfo['server'];
983  } else {
984  // T18937: broken pgsql extension from PHP<5.3
985  $this->numericVersion = pg_parameter_status( $conn, 'server_version' );
986  }
987  }
988 
989  return $this->numericVersion;
990  }
991 
1000  private function relationExists( $table, $types, $schema = false ) {
1001  if ( !is_array( $types ) ) {
1002  $types = [ $types ];
1003  }
1004  if ( $schema === false ) {
1005  $schemas = $this->getCoreSchemas();
1006  } else {
1007  $schemas = [ $schema ];
1008  }
1009  $table = $this->realTableName( $table, 'raw' );
1010  $etable = $this->addQuotes( $table );
1011  foreach ( $schemas as $schema ) {
1012  $eschema = $this->addQuotes( $schema );
1013  $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
1014  . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
1015  . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
1016  $res = $this->query(
1017  $sql,
1018  __METHOD__,
1019  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1020  );
1021  if ( $res && $res->numRows() ) {
1022  return true;
1023  }
1024  }
1025 
1026  return false;
1027  }
1028 
1036  public function tableExists( $table, $fname = __METHOD__, $schema = false ) {
1037  return $this->relationExists( $table, [ 'r', 'v' ], $schema );
1038  }
1039 
1040  public function sequenceExists( $sequence, $schema = false ) {
1041  return $this->relationExists( $sequence, 'S', $schema );
1042  }
1043 
1044  public function triggerExists( $table, $trigger ) {
1045  $q = <<<SQL
1046  SELECT 1 FROM pg_class, pg_namespace, pg_trigger
1047  WHERE relnamespace=pg_namespace.oid AND relkind='r'
1048  AND tgrelid=pg_class.oid
1049  AND nspname=%s AND relname=%s AND tgname=%s
1050 SQL;
1051  foreach ( $this->getCoreSchemas() as $schema ) {
1052  $res = $this->query(
1053  sprintf(
1054  $q,
1055  $this->addQuotes( $schema ),
1056  $this->addQuotes( $table ),
1057  $this->addQuotes( $trigger )
1058  ),
1059  __METHOD__,
1060  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1061  );
1062  if ( $res && $res->numRows() ) {
1063  return true;
1064  }
1065  }
1066 
1067  return false;
1068  }
1069 
1070  public function ruleExists( $table, $rule ) {
1071  $exists = $this->selectField( 'pg_rules', 'rulename',
1072  [
1073  'rulename' => $rule,
1074  'tablename' => $table,
1075  'schemaname' => $this->getCoreSchemas()
1076  ],
1077  __METHOD__
1078  );
1079 
1080  return $exists === $rule;
1081  }
1082 
1083  public function constraintExists( $table, $constraint ) {
1084  foreach ( $this->getCoreSchemas() as $schema ) {
1085  $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
1086  "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
1087  $this->addQuotes( $schema ),
1088  $this->addQuotes( $table ),
1089  $this->addQuotes( $constraint )
1090  );
1091  $res = $this->query(
1092  $sql,
1093  __METHOD__,
1094  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1095  );
1096  if ( $res && $res->numRows() ) {
1097  return true;
1098  }
1099  }
1100  return false;
1101  }
1102 
1108  public function schemaExists( $schema ) {
1109  if ( !strlen( $schema ) ) {
1110  return false; // short-circuit
1111  }
1112 
1113  $res = $this->query(
1114  "SELECT 1 FROM pg_catalog.pg_namespace " .
1115  "WHERE nspname = " . $this->addQuotes( $schema ) . " LIMIT 1",
1116  __METHOD__,
1117  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1118  );
1119 
1120  return ( $this->numRows( $res ) > 0 );
1121  }
1122 
1128  public function roleExists( $roleName ) {
1129  $res = $this->query(
1130  "SELECT 1 FROM pg_catalog.pg_roles " .
1131  "WHERE rolname = " . $this->addQuotes( $roleName ) . " LIMIT 1",
1132  __METHOD__,
1133  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1134  );
1135 
1136  return ( $this->numRows( $res ) > 0 );
1137  }
1138 
1144  public function fieldInfo( $table, $field ) {
1145  return PostgresField::fromText( $this, $table, $field );
1146  }
1147 
1157  public function fieldType( $res, $index ) {
1158  wfDeprecated( __METHOD__, '1.37' );
1159  return pg_field_type( $res->getInternalResult(), $index );
1160  }
1161 
1162  public function encodeBlob( $b ) {
1163  return new PostgresBlob( pg_escape_bytea( $b ) );
1164  }
1165 
1166  public function decodeBlob( $b ) {
1167  if ( $b instanceof PostgresBlob ) {
1168  $b = $b->fetch();
1169  } elseif ( $b instanceof Blob ) {
1170  return $b->fetch();
1171  }
1172 
1173  return pg_unescape_bytea( $b );
1174  }
1175 
1176  public function strencode( $s ) {
1177  // Should not be called by us
1178  return pg_escape_string( $this->getBindingHandle(), (string)$s );
1179  }
1180 
1181  public function addQuotes( $s ) {
1182  $conn = $this->getBindingHandle();
1183 
1184  if ( $s === null ) {
1185  return 'NULL';
1186  } elseif ( is_bool( $s ) ) {
1187  return (string)intval( $s );
1188  } elseif ( is_int( $s ) ) {
1189  return (string)$s;
1190  } elseif ( $s instanceof Blob ) {
1191  if ( $s instanceof PostgresBlob ) {
1192  $s = $s->fetch();
1193  } else {
1194  $s = pg_escape_bytea( $conn, $s->fetch() );
1195  }
1196  return "'$s'";
1197  } elseif ( $s instanceof NextSequenceValue ) {
1198  return 'DEFAULT';
1199  }
1200 
1201  return "'" . pg_escape_string( $conn, (string)$s ) . "'";
1202  }
1203 
1204  protected function makeSelectOptions( array $options ) {
1205  $preLimitTail = $postLimitTail = '';
1206  $startOpts = $useIndex = $ignoreIndex = '';
1207 
1208  $noKeyOptions = [];
1209  foreach ( $options as $key => $option ) {
1210  if ( is_numeric( $key ) ) {
1211  $noKeyOptions[$option] = true;
1212  }
1213  }
1214 
1215  $preLimitTail .= $this->makeGroupByWithHaving( $options );
1216 
1217  $preLimitTail .= $this->makeOrderBy( $options );
1218 
1219  if ( isset( $options['FOR UPDATE'] ) ) {
1220  $postLimitTail .= ' FOR UPDATE OF ' .
1221  implode( ', ', array_map( [ $this, 'tableName' ], $options['FOR UPDATE'] ) );
1222  } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1223  $postLimitTail .= ' FOR UPDATE';
1224  }
1225 
1226  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1227  $startOpts .= 'DISTINCT';
1228  }
1229 
1230  return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1231  }
1232 
1233  public function buildConcat( $stringList ) {
1234  return implode( ' || ', $stringList );
1235  }
1236 
1237  public function buildGroupConcatField(
1238  $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
1239  ) {
1240  $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
1241 
1242  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1243  }
1244 
1245  public function buildStringCast( $field ) {
1246  return $field . '::text';
1247  }
1248 
1249  public function streamStatementEnd( &$sql, &$newLine ) {
1250  # Allow dollar quoting for function declarations
1251  if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
1252  if ( $this->delimiter ) {
1253  $this->delimiter = false;
1254  } else {
1255  $this->delimiter = ';';
1256  }
1257  }
1258 
1259  return parent::streamStatementEnd( $sql, $newLine );
1260  }
1261 
1262  public function doLockTables( array $read, array $write, $method ) {
1263  $tablesWrite = [];
1264  foreach ( $write as $table ) {
1265  $tablesWrite[] = $this->tableName( $table );
1266  }
1267  $tablesRead = [];
1268  foreach ( $read as $table ) {
1269  $tablesRead[] = $this->tableName( $table );
1270  }
1271 
1272  // Acquire locks for the duration of the current transaction...
1273  if ( $tablesWrite ) {
1274  $this->query(
1275  'LOCK TABLE ONLY ' . implode( ',', $tablesWrite ) . ' IN EXCLUSIVE MODE',
1276  $method,
1277  self::QUERY_CHANGE_ROWS
1278  );
1279  }
1280  if ( $tablesRead ) {
1281  $this->query(
1282  'LOCK TABLE ONLY ' . implode( ',', $tablesRead ) . ' IN SHARE MODE',
1283  $method,
1284  self::QUERY_CHANGE_ROWS
1285  );
1286  }
1287 
1288  return true;
1289  }
1290 
1291  public function doLockIsFree( string $lockName, string $method ) {
1292  // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1293  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1294 
1295  $res = $this->query(
1296  "SELECT (CASE(pg_try_advisory_lock($key))
1297  WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS unlocked",
1298  $method,
1299  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1300  );
1301  $row = $this->fetchObject( $res );
1302 
1303  return ( $row->unlocked === 't' );
1304  }
1305 
1306  public function doLock( string $lockName, string $method, int $timeout ) {
1307  // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1308  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1309 
1310  $acquired = null;
1311  $loop = new WaitConditionLoop(
1312  function () use ( $lockName, $key, $timeout, $method, &$acquired ) {
1313  $res = $this->query(
1314  "SELECT (CASE WHEN pg_try_advisory_lock($key) " .
1315  "THEN EXTRACT(epoch from clock_timestamp()) " .
1316  "ELSE NULL " .
1317  "END) AS acquired",
1318  $method,
1319  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_ROWS
1320  );
1321  $row = $this->fetchObject( $res );
1322 
1323  if ( $row->acquired !== null ) {
1324  $acquired = (float)$row->acquired;
1325 
1326  return WaitConditionLoop::CONDITION_REACHED;
1327  }
1328 
1329  return WaitConditionLoop::CONDITION_CONTINUE;
1330  },
1331  $timeout
1332  );
1333  $loop->invoke();
1334 
1335  return $acquired;
1336  }
1337 
1338  public function doUnlock( string $lockName, string $method ) {
1339  // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1340  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1341 
1342  $result = $this->query(
1343  "SELECT pg_advisory_unlock($key) AS released",
1344  $method,
1345  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_ROWS
1346  );
1347  $row = $this->fetchObject( $result );
1348 
1349  return ( $row->released === 't' );
1350  }
1351 
1352  public function serverIsReadOnly() {
1353  $res = $this->query(
1354  "SHOW default_transaction_read_only",
1355  __METHOD__,
1356  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1357  );
1358  $row = $this->fetchObject( $res );
1359 
1360  return $row ? ( strtolower( $row->default_transaction_read_only ) === 'on' ) : false;
1361  }
1362 
1363  protected static function getAttributes() {
1364  return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS => true ];
1365  }
1366 
1371  private function bigintFromLockName( $lockName ) {
1372  return \Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
1373  }
1374 }
1375 
1379 class_alias( DatabasePostgres::class, 'DatabasePostgres' );
Wikimedia\Rdbms\Database\getLastPHPError
getLastPHPError()
Definition: Database.php:950
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:52
Wikimedia\Rdbms\DatabasePostgres\$keywordTableMap
null string[] $keywordTableMap
Map of (reserved table name => alternate table name)
Definition: DatabasePostgres.php:40
Wikimedia\Rdbms\DatabasePostgres\doLockTables
doLockTables(array $read, array $write, $method)
Helper function for lockTables() that handles the actual table locking.
Definition: DatabasePostgres.php:1262
Wikimedia\Rdbms\DatabasePostgres\bigintFromLockName
bigintFromLockName( $lockName)
Definition: DatabasePostgres.php:1371
Wikimedia\Rdbms\Database\getBindingHandle
getBindingHandle()
Get the underlying binding connection handle.
Definition: Database.php:5856
Wikimedia\Rdbms\DatabasePostgres\$port
int $port
Definition: DatabasePostgres.php:34
Wikimedia\Rdbms\DatabasePostgres\tableName
tableName( $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.This does two important things: it quo...
Definition: DatabasePostgres.php:581
Wikimedia\Rdbms\DatabasePostgres\getSoftwareLink
getSoftwareLink()
Returns a wikitext style link to the DB's website (e.g.
Definition: DatabasePostgres.php:809
Wikimedia\Rdbms\DatabasePostgres\closeConnection
closeConnection()
Closes underlying database connection.
Definition: DatabasePostgres.php:189
Wikimedia\Rdbms\DatabasePostgres\relationExists
relationExists( $table, $types, $schema=false)
Query whether a given relation exists (in the given schema, or the default mw one if not given)
Definition: DatabasePostgres.php:1000
Wikimedia\Rdbms\DatabasePostgres\makeUpdateOptionsArray
makeUpdateOptionsArray( $options)
Make UPDATE options array for Database::makeUpdateOptions.
Definition: DatabasePostgres.php:514
Wikimedia\Rdbms\DatabasePostgres\getSchemas
getSchemas()
Return list of schemas which are accessible without schema name This is list does not contain magic k...
Definition: DatabasePostgres.php:841
Wikimedia\Rdbms\DatabasePostgres\remappedTableName
remappedTableName( $name)
Definition: DatabasePostgres.php:590
Wikimedia\Rdbms\DatabasePostgres\doUnlock
doUnlock(string $lockName, string $method)
Definition: DatabasePostgres.php:1338
Wikimedia\Rdbms\DatabasePostgres\addQuotes
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.stringStability: stableto override
Definition: DatabasePostgres.php:1181
Wikimedia\Rdbms\Database\tableNamesN
tableNamesN(... $tables)
Fetch a number of table names into an zero-indexed numerical array This is handy when you need to con...
Definition: Database.php:3136
Wikimedia\Rdbms\DatabasePostgres\fetchAffectedRowCount
fetchAffectedRowCount()
Definition: DatabasePostgres.php:274
Wikimedia\Rdbms\DatabasePostgres\getAttributes
static getAttributes()
Definition: DatabasePostgres.php:1363
Wikimedia\Rdbms\DatabasePostgres\getCoreSchemas
getCoreSchemas()
Return schema names for temporary tables and core application tables.
Definition: DatabasePostgres.php:954
n
while(( $__line=Maintenance::readconsole()) !==false) print n
Definition: eval.php:69
Wikimedia\Rdbms\DatabasePostgres\fieldInfo
fieldInfo( $table, $field)
Definition: DatabasePostgres.php:1144
Wikimedia\Rdbms\DatabasePostgres\makeSelectOptions
makeSelectOptions(array $options)
Returns an optional USE INDEX clause to go after the table, and a string to go at the end of the quer...
Definition: DatabasePostgres.php:1204
true
return true
Definition: router.php:90
Wikimedia\Rdbms\DatabasePostgres\listTables
listTables( $prefix='', $fname=__METHOD__)
Definition: DatabasePostgres.php:734
Wikimedia\Rdbms\Database\$delimiter
string $delimiter
Current SQL query delimiter.
Definition: Database.php:110
Wikimedia\Rdbms\Database\endAtomic
endAtomic( $fname=__METHOD__)
Ends an atomic section of SQL statements.
Definition: Database.php:4621
Wikimedia\Rdbms\Database\indexName
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
Definition: Database.php:3321
Wikimedia\Rdbms\DatabasePostgres\fieldType
fieldType( $res, $index)
pg_field_type() wrapper
Definition: DatabasePostgres.php:1157
Wikimedia\Rdbms\DatabaseDomain\getTablePrefix
getTablePrefix()
Definition: DatabaseDomain.php:193
Wikimedia\Rdbms\DatabasePostgres\open
open( $server, $user, $password, $db, $schema, $tablePrefix)
Open a new connection to the database (closing any existing one)
Definition: DatabasePostgres.php:76
Wikimedia\Rdbms\DatabasePostgres\$numericVersion
float string $numericVersion
Definition: DatabasePostgres.php:42
Wikimedia\Rdbms
Definition: ChronologyProtector.php:24
Wikimedia\Rdbms\Database\normalizeConditions
normalizeConditions( $conds, $fname)
Definition: Database.php:2248
Wikimedia\Rdbms\Database\$server
string null $server
Server that this instance is currently connected to.
Definition: Database.php:83
Wikimedia\Rdbms\Database\normalizeOptions
normalizeOptions( $options)
Definition: Database.php:2341
Wikimedia\Rdbms\DatabasePostgres\doInsertSelectNative
doInsertSelectNative( $destTable, $srcTable, array $varMap, $conds, $fname, array $insertOptions, array $selectOptions, $selectJoinConds)
INSERT SELECT wrapper $varMap must be an associative array of the form [ 'dest1' => 'source1',...
Definition: DatabasePostgres.php:540
Wikimedia\Rdbms\Database\extractSingleFieldFromList
extractSingleFieldFromList( $var)
Definition: Database.php:2443
DBO_SSL
const DBO_SSL
Definition: defines.php:17
Wikimedia\Rdbms\DatabasePostgres\makeInsertNonConflictingVerbAndOptions
makeInsertNonConflictingVerbAndOptions()
Definition: DatabasePostgres.php:470
Wikimedia\Rdbms\DatabaseDomain\getDatabase
getDatabase()
Definition: DatabaseDomain.php:179
Wikimedia\Rdbms\DatabasePostgres
Definition: DatabasePostgres.php:32
Wikimedia\Rdbms\DatabasePostgres\textFieldSize
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited".intStability: stableto override
Definition: DatabasePostgres.php:627
$res
$res
Definition: testCompression.php:57
Wikimedia\Rdbms\Database\cancelAtomic
cancelAtomic( $fname=__METHOD__, AtomicSectionIdentifier $sectionId=null)
Cancel an atomic section of SQL statements.
Definition: Database.php:4671
Wikimedia\Rdbms\Database\getFlag
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition: Database.php:864
Wikimedia\Rdbms\DatabasePostgres\indexUnique
indexUnique( $table, $index, $fname=__METHOD__)
Determines if a given index is unique.Calling function nameboolStability: stableto override
Definition: DatabasePostgres.php:402
Wikimedia\Rdbms\Database\doInsertSelectGeneric
doInsertSelectGeneric( $destTable, $srcTable, array $varMap, $conds, $fname, array $insertOptions, array $selectOptions, $selectJoinConds)
Implementation of insertSelect() based on select() and insert()
Definition: Database.php:3729
Wikimedia\Rdbms\DatabasePostgres\doLockIsFree
doLockIsFree(string $lockName, string $method)
Definition: DatabasePostgres.php:1291
Wikimedia\Rdbms\DatabasePostgres\insertId
insertId()
Get the inserted value of an auto-increment row.
Definition: DatabasePostgres.php:243
Wikimedia\Rdbms\DatabasePostgres\doLock
doLock(string $lockName, string $method, int $timeout)
Definition: DatabasePostgres.php:1306
Wikimedia\Rdbms\Database\$affectedRowCount
int null $affectedRowCount
Rows affected by the last query to query() or its CRUD wrappers.
Definition: Database.php:183
Wikimedia\Rdbms\DatabasePostgres\aggregateValue
aggregateValue( $valuedata, $valuename='value')
Return aggregated value alias.array|string Since 1.33 Stability: stableto override
Definition: DatabasePostgres.php:805
Wikimedia\Rdbms\DatabasePostgres\roleExists
roleExists( $roleName)
Returns true if a given role (i.e.
Definition: DatabasePostgres.php:1128
Wikimedia\Rdbms\DatabasePostgres\$coreSchema
string $coreSchema
Definition: DatabasePostgres.php:36
Wikimedia\Rdbms\Database\fetchObject
fetchObject(IResultWrapper $res)
Fetch the next row from the given result object, in object form.
Definition: Database.php:872
Wikimedia\Rdbms\Database\close
close( $fname=__METHOD__, $owner=null)
Close the database connection.
Definition: Database.php:990
Wikimedia\Rdbms\DatabasePostgres\encodeBlob
encodeBlob( $b)
Some DBMSs have a special format for inserting into blob fields, they don't allow simple quoted strin...
Definition: DatabasePostgres.php:1162
Wikimedia\Rdbms\DatabasePostgres\makeConnectionString
makeConnectionString( $vars)
Definition: DatabasePostgres.php:180
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1028
Wikimedia\Rdbms\DatabasePostgres\isTransactableQuery
isTransactableQuery( $sql)
Determine whether a SQL statement is sensitive to isolation level.
Definition: DatabasePostgres.php:193
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:997
Wikimedia\Rdbms\DatabasePostgres\schemaExists
schemaExists( $schema)
Query whether a given schema exists.
Definition: DatabasePostgres.php:1108
Wikimedia\Rdbms\DatabasePostgres\implicitOrderby
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
Definition: DatabasePostgres.php:72
Wikimedia\Rdbms\Database\startAtomic
startAtomic( $fname=__METHOD__, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Begin an atomic section of SQL statements.
Definition: Database.php:4559
Wikimedia\Rdbms\DatabasePostgres\limitResult
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.The SQL should be adjusted so that only the first $limit...
Definition: DatabasePostgres.php:645
Wikimedia\Rdbms\DatabasePostgres\duplicateTableStructure
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Creates a new table with structure copied from existing table.Note that unlike most database abstract...
Definition: DatabasePostgres.php:670
Wikimedia\Rdbms\PostgresField\fromText
static fromText(DatabasePostgres $db, $table, $field)
Definition: PostgresField.php:15
Wikimedia\Rdbms\Database\trxLevel
trxLevel()
Gets the current transaction level.
Definition: Database.php:599
Wikimedia\Rdbms\DatabasePostgres\doQuery
doQuery( $sql)
Definition: DatabasePostgres.php:202
Wikimedia\Rdbms\DatabasePostgres\buildStringCast
buildStringCast( $field)
Field or column to cast string 1.28Stability: stableto override
Definition: DatabasePostgres.php:1245
Wikimedia\Rdbms\Database\numRows
numRows( $res)
Get the number of rows in a query result.
Definition: Database.php:880
Wikimedia\Rdbms\DatabasePostgres\$lastResultHandle
resource null $lastResultHandle
Definition: DatabasePostgres.php:45
Wikimedia\Rdbms\Database\makeInsertLists
makeInsertLists(array $rows)
Make SQL lists of columns, row tuples for INSERT/VALUES expressions.
Definition: Database.php:2580
Wikimedia\Rdbms\DatabasePostgres\pg_array_parse
pg_array_parse( $text, &$output, $limit=false, $offset=1)
Posted by cc[plus]php[at]c2se[dot]com on 25-Mar-2009 09:12 to https://www.php.net/manual/en/ref....
Definition: DatabasePostgres.php:778
Wikimedia\Rdbms\DatabasePostgres\lastErrno
lastErrno()
Get the last error number.
Definition: DatabasePostgres.php:266
Wikimedia\Rdbms\DatabasePostgres\selectSQLText
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Take the same arguments as IDatabase::select() and return the SQL it would use.This can be useful for...
Definition: DatabasePostgres.php:416
Wikimedia\Rdbms\NextSequenceValue
Used by Database::nextSequenceValue() so Database::insert() can detect values coming from the depreca...
Definition: NextSequenceValue.php:11
Wikimedia\Rdbms\DatabasePostgres\strencode
strencode( $s)
Wrapper for addslashes()
Definition: DatabasePostgres.php:1176
Wikimedia\Rdbms\DBQueryError
@newable
Definition: DBQueryError.php:29
Wikimedia\Rdbms\DatabasePostgres\wasConnectionError
wasConnectionError( $errno)
Do not use this method outside of Database/DBError classes.
Definition: DatabasePostgres.php:659
Wikimedia\Rdbms\Database\installErrorHandler
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition: Database.php:927
Wikimedia\Rdbms\Database\restoreErrorHandler
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition: Database.php:938
Wikimedia\Rdbms\DatabasePostgres\serverIsReadOnly
serverIsReadOnly()
bool Whether the DB is marked as read-only server-side If an error occurs, {query} 1....
Definition: DatabasePostgres.php:1352
Wikimedia\Rdbms\DatabasePostgres\doTruncate
doTruncate(array $tables, $fname)
Definition: DatabasePostgres.php:722
$s
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
Definition: mergeMessageFileList.php:206
Wikimedia\Rdbms\Database\fetchRow
fetchRow(IResultWrapper $res)
Fetch the next row from the given result object, in associative array form.
Definition: Database.php:876
Wikimedia\Rdbms\DatabasePostgres\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
Definition: DatabasePostgres.php:754
Wikimedia\Rdbms\DatabasePostgres\relationSchemaQualifier
relationSchemaQualifier()
Definition: DatabasePostgres.php:144
Wikimedia\Rdbms\Database\query
query( $sql, $fname=__METHOD__, $flags=self::QUERY_NORMAL)
Run an SQL query and return the result.
Definition: Database.php:1316
Wikimedia\Rdbms\DatabasePostgres\decodeBlob
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects....
Definition: DatabasePostgres.php:1166
Wikimedia\Rdbms\DatabasePostgres\getCurrentSchema
getCurrentSchema()
Return current schema (executes SELECT current_schema()) Needs transaction.
Definition: DatabasePostgres.php:820
Wikimedia\Rdbms\Database\$user
string null $user
User that this instance is currently connected under the name of.
Definition: Database.php:85
Wikimedia\Rdbms\DBUnexpectedError
@newable
Definition: DBUnexpectedError.php:29
Wikimedia\Rdbms\DatabasePostgres\realTableName
realTableName( $name, $format='quoted')
Definition: DatabasePostgres.php:601
Wikimedia\Rdbms\Database\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
Definition: Database.php:1839
Wikimedia\Rdbms\DatabasePostgres\nextSequenceValue
nextSequenceValue( $seqName)
Deprecated method, calls should be removed.
Definition: DatabasePostgres.php:605
Wikimedia\Rdbms\DatabasePostgres\buildConcat
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.Raw SQL expression list; caller is responsible fo...
Definition: DatabasePostgres.php:1233
Wikimedia\Rdbms\DatabasePostgres\getType
getType()
Get the RDBMS type of the server (e.g.
Definition: DatabasePostgres.php:68
Wikimedia\Rdbms\Database\addIdentifierQuotes
addIdentifierQuotes( $s)
Escape a SQL identifier (e.g.table, column, database) for use in a SQL queryDepending on the database...
Definition: Database.php:3348
Wikimedia\Rdbms\DatabasePostgres\getServerVersion
getServerVersion()
A string describing the current software version, like from mysql_get_server_info()
Definition: DatabasePostgres.php:973
Wikimedia\Rdbms\DatabasePostgres\lastError
lastError()
Get a description of the last error.
Definition: DatabasePostgres.php:254
Wikimedia\Rdbms\Database\newExceptionAfterConnectError
newExceptionAfterConnectError( $error)
Definition: Database.php:1817
Wikimedia\Rdbms\DatabasePostgres\__construct
__construct(array $params)
Definition: DatabasePostgres.php:54
Wikimedia\Rdbms\DatabasePostgres\constraintExists
constraintExists( $table, $constraint)
Definition: DatabasePostgres.php:1083
Wikimedia\Rdbms\DatabasePostgres\wasLockTimeout
wasLockTimeout()
Determines if the last failure was due to a lock timeout.Note that during a lock wait timeout,...
Definition: DatabasePostgres.php:654
Wikimedia\Rdbms\DatabasePostgres\streamStatementEnd
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
Definition: DatabasePostgres.php:1249
Wikimedia\Rdbms\Database\makeGroupByWithHaving
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1969
Wikimedia\Rdbms\DatabaseDomain\getSchema
getSchema()
Definition: DatabaseDomain.php:186
Wikimedia\Rdbms\DatabasePostgres\sequenceExists
sequenceExists( $sequence, $schema=false)
Definition: DatabasePostgres.php:1040
Wikimedia\Rdbms\DatabasePostgres\tableExists
tableExists( $table, $fname=__METHOD__, $schema=false)
For backward compatibility, this function checks both tables and views.
Definition: DatabasePostgres.php:1036
Wikimedia\Rdbms\DatabasePostgres\buildGroupConcatField
buildGroupConcatField( $delimiter, $table, $field, $conds='', $options=[], $join_conds=[])
Definition: DatabasePostgres.php:1237
Wikimedia\Rdbms\Database\makeOrderBy
makeOrderBy( $options)
Returns an optional ORDER BY.
Definition: Database.php:1995
Wikimedia\Rdbms\Database\$flags
int $flags
Current bit field of class DBO_* constants.
Definition: Database.php:106
Wikimedia\Rdbms\DatabasePostgres\ruleExists
ruleExists( $table, $rule)
Definition: DatabasePostgres.php:1070
Wikimedia\Rdbms\Database\select
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
Definition: Database.php:2007
Wikimedia\Rdbms\DatabasePostgres\wasKnownStatementRollbackError
wasKnownStatementRollbackError()
Definition: DatabasePostgres.php:666
Wikimedia\Rdbms\DatabasePostgres\dumpError
dumpError()
Definition: DatabasePostgres.php:222
Wikimedia\Rdbms\Database\$password
string null $password
Password used to establish the current connection.
Definition: Database.php:87
Wikimedia\Rdbms\DatabasePostgres\determineCoreSchema
determineCoreSchema( $desiredSchema)
Determine default schema for the current application Adjust this session schema search path if desire...
Definition: DatabasePostgres.php:906
Wikimedia\Rdbms\DatabasePostgres\getSearchPath
getSearchPath()
Return search patch for schemas This is different from getSchemas() since it contain magic keywords (...
Definition: DatabasePostgres.php:864
Wikimedia\Rdbms\DatabasePostgres\getCoreSchema
getCoreSchema()
Return schema name for core application tables.
Definition: DatabasePostgres.php:944
Wikimedia\Rdbms\DatabaseDomain
Class to handle database/schema/prefix specifications for IDatabase.
Definition: DatabaseDomain.php:40
Wikimedia\Rdbms\DatabasePostgres\indexInfo
indexInfo( $table, $index, $fname=__METHOD__)
Get information about an index into an object.
Definition: DatabasePostgres.php:320
Wikimedia\Rdbms\DatabasePostgres\$tempSchema
string $tempSchema
Definition: DatabasePostgres.php:38
Wikimedia\Rdbms\DatabasePostgres\triggerExists
triggerExists( $table, $trigger)
Definition: DatabasePostgres.php:1044
Wikimedia\Rdbms\DatabasePostgres\currentSequenceValue
currentSequenceValue( $seqName)
Return the current value of a sequence.
Definition: DatabasePostgres.php:615
Wikimedia\Rdbms\DatabasePostgres\setSearchPath
setSearchPath( $search_path)
Update search_path, values should already be sanitized Values may contain magic keywords like "$user"...
Definition: DatabasePostgres.php:884
Wikimedia\Rdbms\DatabasePostgres\wasDeadlock
wasDeadlock()
Determines if the last failure was due to a deadlock.Note that during a deadlock, the prior transacti...
Definition: DatabasePostgres.php:649
Wikimedia\Rdbms\DatabasePostgres\doSelectDomain
doSelectDomain(DatabaseDomain $domain)
Definition: DatabasePostgres.php:157
Wikimedia\Rdbms\PostgresBlob
@newable
Definition: PostgresBlob.php:8
Wikimedia\Rdbms\DatabasePostgres\estimateRowCount
estimateRowCount( $table, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Estimate rows in dataset Returns estimated count, based on EXPLAIN output This is not necessarily an ...
Definition: DatabasePostgres.php:297
Wikimedia\Rdbms\Database\$conn
object resource null $conn
Database connection.
Definition: Database.php:77
Wikimedia\Rdbms\DatabasePostgres\databasesAreIndependent
databasesAreIndependent()
Returns true if DBs are assumed to be on potentially different servers.In systems like mysql/mariadb,...
Definition: DatabasePostgres.php:153
Wikimedia\Rdbms\PostgresResultWrapper
Definition: PostgresResultWrapper.php:7
Wikimedia\Rdbms\Database\getDBname
getDBname()
Get the current database name; null if there isn't one.
Definition: Database.php:3001
Wikimedia\Rdbms\DatabasePostgres\indexAttributes
indexAttributes( $index, $schema=false)
Definition: DatabasePostgres.php:338
Wikimedia\Rdbms\DatabasePostgres\doInsertNonConflicting
doInsertNonConflicting( $table, array $rows, $fname)
Definition: DatabasePostgres.php:474
Wikimedia\Rdbms\Blob
@newable
Definition: Blob.php:9