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_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 
586  public function remappedTableName( $name ) {
587  wfDeprecated( __METHOD__, '1.37' );
588 
589  return $this->keywordTableMap[$name] ?? $name;
590  }
591 
597  public function realTableName( $name, $format = 'quoted' ) {
598  return parent::tableName( $name, $format );
599  }
600 
601  public function nextSequenceValue( $seqName ) {
602  return new NextSequenceValue;
603  }
604 
611  public function currentSequenceValue( $seqName ) {
612  $res = $this->query(
613  "SELECT currval('" . str_replace( "'", "''", $seqName ) . "')",
614  __METHOD__,
615  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
616  );
617  $row = $this->fetchRow( $res );
618  $currval = $row[0];
619 
620  return $currval;
621  }
622 
623  public function textFieldSize( $table, $field ) {
624  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
625  $encTable = $this->tableName( $table );
626  $sql = "SELECT t.typname as ftype,a.atttypmod as size
627  FROM pg_class c, pg_attribute a, pg_type t
628  WHERE relname='$encTable' AND a.attrelid=c.oid AND
629  a.atttypid=t.oid and a.attname='$field'";
630  $res = $this->query( $sql, __METHOD__, $flags );
631  $row = $res->fetchObject();
632  if ( $row->ftype == 'varchar' ) {
633  $size = $row->size - 4;
634  } else {
635  $size = $row->size;
636  }
637 
638  return $size;
639  }
640 
641  public function limitResult( $sql, $limit, $offset = false ) {
642  return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
643  }
644 
645  public function wasDeadlock() {
646  // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
647  return $this->lastErrno() === '40P01';
648  }
649 
650  public function wasLockTimeout() {
651  // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
652  return $this->lastErrno() === '55P03';
653  }
654 
655  protected function isConnectionError( $errno ) {
656  // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
657  static $codes = [ '08000', '08003', '08006', '08001', '08004', '57P01', '57P03', '53300' ];
658 
659  return in_array( $errno, $codes, true );
660  }
661 
662  protected function wasKnownStatementRollbackError() {
663  return false; // transaction has to be rolled-back from error state
664  }
665 
666  public function duplicateTableStructure(
667  $oldName, $newName, $temporary = false, $fname = __METHOD__
668  ) {
669  $newNameE = $this->addIdentifierQuotes( $newName );
670  $oldNameE = $this->addIdentifierQuotes( $oldName );
671 
672  $temporary = $temporary ? 'TEMPORARY' : '';
673 
674  $ret = $this->query(
675  "CREATE $temporary TABLE $newNameE " .
676  "(LIKE $oldNameE INCLUDING DEFAULTS INCLUDING INDEXES)",
677  $fname,
678  self::QUERY_PSEUDO_PERMANENT | self::QUERY_CHANGE_SCHEMA
679  );
680  if ( !$ret ) {
681  return $ret;
682  }
683 
684  $res = $this->query(
685  'SELECT attname FROM pg_class c'
686  . ' JOIN pg_namespace n ON (n.oid = c.relnamespace)'
687  . ' JOIN pg_attribute a ON (a.attrelid = c.oid)'
688  . ' JOIN pg_attrdef d ON (c.oid=d.adrelid and a.attnum=d.adnum)'
689  . ' WHERE relkind = \'r\''
690  . ' AND nspname = ' . $this->addQuotes( $this->getCoreSchema() )
691  . ' AND relname = ' . $this->addQuotes( $oldName )
692  . ' AND pg_get_expr(adbin, adrelid) LIKE \'nextval(%\'',
693  $fname,
694  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
695  );
696  $row = $res->fetchObject();
697  if ( $row ) {
698  $field = $row->attname;
699  $newSeq = "{$newName}_{$field}_seq";
700  $fieldE = $this->addIdentifierQuotes( $field );
701  $newSeqE = $this->addIdentifierQuotes( $newSeq );
702  $newSeqQ = $this->addQuotes( $newSeq );
703  $this->query(
704  "CREATE $temporary SEQUENCE $newSeqE OWNED BY $newNameE.$fieldE",
705  $fname,
706  self::QUERY_CHANGE_SCHEMA
707  );
708  $this->query(
709  "ALTER TABLE $newNameE ALTER COLUMN $fieldE SET DEFAULT nextval({$newSeqQ}::regclass)",
710  $fname,
711  self::QUERY_CHANGE_SCHEMA
712  );
713  }
714 
715  return $ret;
716  }
717 
718  protected function doTruncate( array $tables, $fname ) {
719  $encTables = $this->tableNamesN( ...$tables );
720  $sql = "TRUNCATE TABLE " . implode( ',', $encTables ) . " RESTART IDENTITY";
721  $this->query( $sql, $fname, self::QUERY_CHANGE_SCHEMA );
722  }
723 
730  public function listTables( $prefix = '', $fname = __METHOD__ ) {
731  $eschemas = implode( ',', array_map( [ $this, 'addQuotes' ], $this->getCoreSchemas() ) );
732  $result = $this->query(
733  "SELECT DISTINCT tablename FROM pg_tables WHERE schemaname IN ($eschemas)",
734  $fname,
735  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
736  );
737  $endArray = [];
738 
739  foreach ( $result as $table ) {
740  $vars = get_object_vars( $table );
741  $table = array_pop( $vars );
742  if ( $prefix == '' || strpos( $table, $prefix ) === 0 ) {
743  $endArray[] = $table;
744  }
745  }
746 
747  return $endArray;
748  }
749 
750  public function timestamp( $ts = 0 ) {
751  $ct = new ConvertibleTimestamp( $ts );
752 
753  return $ct->getTimestamp( TS_POSTGRES );
754  }
755 
774  private function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
775  if ( $limit === false ) {
776  $limit = strlen( $text ) - 1;
777  $output = [];
778  }
779  if ( $text == '{}' ) {
780  return $output;
781  }
782  do {
783  if ( $text[$offset] != '{' ) {
784  preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
785  $text, $match, 0, $offset );
786  $offset += strlen( $match[0] );
787  $output[] = ( $match[1][0] != '"'
788  ? $match[1]
789  : stripcslashes( substr( $match[1], 1, -1 ) ) );
790  if ( $match[3] == '},' ) {
791  return $output;
792  }
793  } else {
794  $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
795  }
796  } while ( $limit > $offset );
797 
798  return $output;
799  }
800 
801  public function getSoftwareLink() {
802  return '[{{int:version-db-postgres-url}} PostgreSQL]';
803  }
804 
812  public function getCurrentSchema() {
813  $res = $this->query(
814  "SELECT current_schema()",
815  __METHOD__,
816  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
817  );
818  $row = $this->fetchRow( $res );
819 
820  return $row[0];
821  }
822 
833  public function getSchemas() {
834  $res = $this->query(
835  "SELECT current_schemas(false)",
836  __METHOD__,
837  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
838  );
839  $row = $this->fetchRow( $res );
840  $schemas = [];
841 
842  /* PHP pgsql support does not support array type, "{a,b}" string is returned */
843 
844  return $this->pg_array_parse( $row[0], $schemas );
845  }
846 
856  public function getSearchPath() {
857  $res = $this->query(
858  "SHOW search_path",
859  __METHOD__,
860  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
861  );
862  $row = $this->fetchRow( $res );
863 
864  /* PostgreSQL returns SHOW values as strings */
865 
866  return explode( ",", $row[0] );
867  }
868 
876  private function setSearchPath( $search_path ) {
877  $this->query(
878  "SET search_path = " . implode( ", ", $search_path ),
879  __METHOD__,
880  self::QUERY_CHANGE_TRX
881  );
882  }
883 
898  public function determineCoreSchema( $desiredSchema ) {
899  if ( $this->trxLevel() ) {
900  // We do not want the schema selection to change on ROLLBACK or INSERT SELECT.
901  // See https://www.postgresql.org/docs/8.3/sql-set.html
902  throw new DBUnexpectedError(
903  $this,
904  __METHOD__ . ": a transaction is currently active"
905  );
906  }
907 
908  if ( $this->schemaExists( $desiredSchema ) ) {
909  if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
910  $this->coreSchema = $desiredSchema;
911  $this->queryLogger->debug(
912  "Schema \"" . $desiredSchema . "\" already in the search path\n" );
913  } else {
914  // Prepend the desired schema to the search path (T17816)
915  $search_path = $this->getSearchPath();
916  array_unshift( $search_path, $this->addIdentifierQuotes( $desiredSchema ) );
917  $this->setSearchPath( $search_path );
918  $this->coreSchema = $desiredSchema;
919  $this->queryLogger->debug(
920  "Schema \"" . $desiredSchema . "\" added to the search path\n" );
921  }
922  } else {
923  $this->coreSchema = $this->getCurrentSchema();
924  $this->queryLogger->debug(
925  "Schema \"" . $desiredSchema . "\" not found, using current \"" .
926  $this->coreSchema . "\"\n" );
927  }
928  }
929 
936  public function getCoreSchema() {
937  return $this->coreSchema;
938  }
939 
946  public function getCoreSchemas() {
947  if ( $this->tempSchema ) {
948  return [ $this->tempSchema, $this->getCoreSchema() ];
949  }
950 
951  $res = $this->query(
952  "SELECT nspname FROM pg_catalog.pg_namespace n WHERE n.oid = pg_my_temp_schema()",
953  __METHOD__,
954  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
955  );
956  $row = $res->fetchObject();
957  if ( $row ) {
958  $this->tempSchema = $row->nspname;
959  return [ $this->tempSchema, $this->getCoreSchema() ];
960  }
961 
962  return [ $this->getCoreSchema() ];
963  }
964 
965  public function getServerVersion() {
966  if ( !isset( $this->numericVersion ) ) {
967  $conn = $this->getBindingHandle();
968  $versionInfo = pg_version( $conn );
969  if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
970  // Old client, abort install
971  $this->numericVersion = '7.3 or earlier';
972  } elseif ( isset( $versionInfo['server'] ) ) {
973  // Normal client
974  $this->numericVersion = $versionInfo['server'];
975  } else {
976  // T18937: broken pgsql extension from PHP<5.3
977  $this->numericVersion = pg_parameter_status( $conn, 'server_version' );
978  }
979  }
980 
981  return $this->numericVersion;
982  }
983 
992  private function relationExists( $table, $types, $schema = false ) {
993  if ( !is_array( $types ) ) {
994  $types = [ $types ];
995  }
996  if ( $schema === false ) {
997  $schemas = $this->getCoreSchemas();
998  } else {
999  $schemas = [ $schema ];
1000  }
1001  $table = $this->realTableName( $table, 'raw' );
1002  $etable = $this->addQuotes( $table );
1003  foreach ( $schemas as $schema ) {
1004  $eschema = $this->addQuotes( $schema );
1005  $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
1006  . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
1007  . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
1008  $res = $this->query(
1009  $sql,
1010  __METHOD__,
1011  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1012  );
1013  if ( $res && $res->numRows() ) {
1014  return true;
1015  }
1016  }
1017 
1018  return false;
1019  }
1020 
1028  public function tableExists( $table, $fname = __METHOD__, $schema = false ) {
1029  return $this->relationExists( $table, [ 'r', 'v' ], $schema );
1030  }
1031 
1032  public function sequenceExists( $sequence, $schema = false ) {
1033  return $this->relationExists( $sequence, 'S', $schema );
1034  }
1035 
1036  public function triggerExists( $table, $trigger ) {
1037  $q = <<<SQL
1038  SELECT 1 FROM pg_class, pg_namespace, pg_trigger
1039  WHERE relnamespace=pg_namespace.oid AND relkind='r'
1040  AND tgrelid=pg_class.oid
1041  AND nspname=%s AND relname=%s AND tgname=%s
1042 SQL;
1043  foreach ( $this->getCoreSchemas() as $schema ) {
1044  $res = $this->query(
1045  sprintf(
1046  $q,
1047  $this->addQuotes( $schema ),
1048  $this->addQuotes( $table ),
1049  $this->addQuotes( $trigger )
1050  ),
1051  __METHOD__,
1052  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1053  );
1054  if ( $res && $res->numRows() ) {
1055  return true;
1056  }
1057  }
1058 
1059  return false;
1060  }
1061 
1062  public function ruleExists( $table, $rule ) {
1063  $exists = $this->selectField( 'pg_rules', 'rulename',
1064  [
1065  'rulename' => $rule,
1066  'tablename' => $table,
1067  'schemaname' => $this->getCoreSchemas()
1068  ],
1069  __METHOD__
1070  );
1071 
1072  return $exists === $rule;
1073  }
1074 
1075  public function constraintExists( $table, $constraint ) {
1076  foreach ( $this->getCoreSchemas() as $schema ) {
1077  $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
1078  "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
1079  $this->addQuotes( $schema ),
1080  $this->addQuotes( $table ),
1081  $this->addQuotes( $constraint )
1082  );
1083  $res = $this->query(
1084  $sql,
1085  __METHOD__,
1086  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1087  );
1088  if ( $res && $res->numRows() ) {
1089  return true;
1090  }
1091  }
1092  return false;
1093  }
1094 
1100  public function schemaExists( $schema ) {
1101  if ( !strlen( $schema ) ) {
1102  return false; // short-circuit
1103  }
1104 
1105  $res = $this->query(
1106  "SELECT 1 FROM pg_catalog.pg_namespace " .
1107  "WHERE nspname = " . $this->addQuotes( $schema ) . " LIMIT 1",
1108  __METHOD__,
1109  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1110  );
1111 
1112  return ( $this->numRows( $res ) > 0 );
1113  }
1114 
1120  public function roleExists( $roleName ) {
1121  $res = $this->query(
1122  "SELECT 1 FROM pg_catalog.pg_roles " .
1123  "WHERE rolname = " . $this->addQuotes( $roleName ) . " LIMIT 1",
1124  __METHOD__,
1125  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1126  );
1127 
1128  return ( $this->numRows( $res ) > 0 );
1129  }
1130 
1136  public function fieldInfo( $table, $field ) {
1137  return PostgresField::fromText( $this, $table, $field );
1138  }
1139 
1140  public function encodeBlob( $b ) {
1141  return new PostgresBlob( pg_escape_bytea( $b ) );
1142  }
1143 
1144  public function decodeBlob( $b ) {
1145  if ( $b instanceof PostgresBlob ) {
1146  $b = $b->fetch();
1147  } elseif ( $b instanceof Blob ) {
1148  return $b->fetch();
1149  }
1150 
1151  return pg_unescape_bytea( $b );
1152  }
1153 
1154  public function strencode( $s ) {
1155  // Should not be called by us
1156  return pg_escape_string( $this->getBindingHandle(), (string)$s );
1157  }
1158 
1159  public function addQuotes( $s ) {
1160  $conn = $this->getBindingHandle();
1161 
1162  if ( $s === null ) {
1163  return 'NULL';
1164  } elseif ( is_bool( $s ) ) {
1165  return (string)intval( $s );
1166  } elseif ( is_int( $s ) ) {
1167  return (string)$s;
1168  } elseif ( $s instanceof Blob ) {
1169  if ( $s instanceof PostgresBlob ) {
1170  $s = $s->fetch();
1171  } else {
1172  $s = pg_escape_bytea( $conn, $s->fetch() );
1173  }
1174  return "'$s'";
1175  } elseif ( $s instanceof NextSequenceValue ) {
1176  return 'DEFAULT';
1177  }
1178 
1179  return "'" . pg_escape_string( $conn, (string)$s ) . "'";
1180  }
1181 
1182  protected function makeSelectOptions( array $options ) {
1183  $preLimitTail = $postLimitTail = '';
1184  $startOpts = $useIndex = $ignoreIndex = '';
1185 
1186  $noKeyOptions = [];
1187  foreach ( $options as $key => $option ) {
1188  if ( is_numeric( $key ) ) {
1189  $noKeyOptions[$option] = true;
1190  }
1191  }
1192 
1193  $preLimitTail .= $this->makeGroupByWithHaving( $options );
1194 
1195  $preLimitTail .= $this->makeOrderBy( $options );
1196 
1197  if ( isset( $options['FOR UPDATE'] ) ) {
1198  $postLimitTail .= ' FOR UPDATE OF ' .
1199  implode( ', ', array_map( [ $this, 'tableName' ], $options['FOR UPDATE'] ) );
1200  } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1201  $postLimitTail .= ' FOR UPDATE';
1202  }
1203 
1204  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1205  $startOpts .= 'DISTINCT';
1206  }
1207 
1208  return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1209  }
1210 
1211  public function buildConcat( $stringList ) {
1212  return implode( ' || ', $stringList );
1213  }
1214 
1215  public function buildGroupConcatField(
1216  $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
1217  ) {
1218  $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
1219 
1220  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1221  }
1222 
1223  public function buildStringCast( $field ) {
1224  return $field . '::text';
1225  }
1226 
1227  public function streamStatementEnd( &$sql, &$newLine ) {
1228  # Allow dollar quoting for function declarations
1229  if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
1230  if ( $this->delimiter ) {
1231  $this->delimiter = false;
1232  } else {
1233  $this->delimiter = ';';
1234  }
1235  }
1236 
1237  return parent::streamStatementEnd( $sql, $newLine );
1238  }
1239 
1240  public function doLockTables( array $read, array $write, $method ) {
1241  $tablesWrite = [];
1242  foreach ( $write as $table ) {
1243  $tablesWrite[] = $this->tableName( $table );
1244  }
1245  $tablesRead = [];
1246  foreach ( $read as $table ) {
1247  $tablesRead[] = $this->tableName( $table );
1248  }
1249 
1250  // Acquire locks for the duration of the current transaction...
1251  if ( $tablesWrite ) {
1252  $this->query(
1253  'LOCK TABLE ONLY ' . implode( ',', $tablesWrite ) . ' IN EXCLUSIVE MODE',
1254  $method,
1255  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_ROWS
1256  );
1257  }
1258  if ( $tablesRead ) {
1259  $this->query(
1260  'LOCK TABLE ONLY ' . implode( ',', $tablesRead ) . ' IN SHARE MODE',
1261  $method,
1262  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_ROWS
1263  );
1264  }
1265 
1266  return true;
1267  }
1268 
1269  public function doLockIsFree( string $lockName, string $method ) {
1270  // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1271  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1272 
1273  $res = $this->query(
1274  "SELECT (CASE(pg_try_advisory_lock($key))
1275  WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS unlocked",
1276  $method,
1277  self::QUERY_CHANGE_LOCKS
1278  );
1279  $row = $res->fetchObject();
1280 
1281  return ( $row->unlocked === 't' );
1282  }
1283 
1284  public function doLock( string $lockName, string $method, int $timeout ) {
1285  // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1286  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1287 
1288  $acquired = null;
1289  $loop = new WaitConditionLoop(
1290  function () use ( $lockName, $key, $timeout, $method, &$acquired ) {
1291  $res = $this->query(
1292  "SELECT (CASE WHEN pg_try_advisory_lock($key) " .
1293  "THEN EXTRACT(epoch from clock_timestamp()) " .
1294  "ELSE NULL " .
1295  "END) AS acquired",
1296  $method,
1297  self::QUERY_CHANGE_LOCKS
1298  );
1299  $row = $res->fetchObject();
1300 
1301  if ( $row->acquired !== null ) {
1302  $acquired = (float)$row->acquired;
1303 
1304  return WaitConditionLoop::CONDITION_REACHED;
1305  }
1306 
1307  return WaitConditionLoop::CONDITION_CONTINUE;
1308  },
1309  $timeout
1310  );
1311  $loop->invoke();
1312 
1313  return $acquired;
1314  }
1315 
1316  public function doUnlock( string $lockName, string $method ) {
1317  // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1318  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1319 
1320  $result = $this->query(
1321  "SELECT pg_advisory_unlock($key) AS released",
1322  $method,
1323  self::QUERY_CHANGE_LOCKS
1324  );
1325  $row = $result->fetchObject();
1326 
1327  return ( $row->released === 't' );
1328  }
1329 
1330  public function serverIsReadOnly() {
1331  $res = $this->query(
1332  "SHOW default_transaction_read_only",
1333  __METHOD__,
1334  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1335  );
1336  $row = $res->fetchObject();
1337 
1338  return $row ? ( strtolower( $row->default_transaction_read_only ) === 'on' ) : false;
1339  }
1340 
1341  protected static function getAttributes() {
1342  return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS => true ];
1343  }
1344 
1349  private function bigintFromLockName( $lockName ) {
1350  return \Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
1351  }
1352 }
1353 
1357 class_alias( DatabasePostgres::class, 'DatabasePostgres' );
Wikimedia\Rdbms\Database\getLastPHPError
getLastPHPError()
Definition: Database.php:925
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:1240
Wikimedia\Rdbms\DatabasePostgres\bigintFromLockName
bigintFromLockName( $lockName)
Definition: DatabasePostgres.php:1349
Wikimedia\Rdbms\Database\getBindingHandle
getBindingHandle()
Get the underlying binding connection handle.
Definition: Database.php:5925
Wikimedia\Rdbms\DatabasePostgres\$port
int $port
Definition: DatabasePostgres.php:34
Wikimedia\Rdbms\DatabasePostgres\getSoftwareLink
getSoftwareLink()
Returns a wikitext style link to the DB's website (e.g.
Definition: DatabasePostgres.php:801
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:992
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:833
Wikimedia\Rdbms\DatabasePostgres\remappedTableName
remappedTableName( $name)
Definition: DatabasePostgres.php:586
Wikimedia\Rdbms\DatabasePostgres\doUnlock
doUnlock(string $lockName, string $method)
Definition: DatabasePostgres.php:1316
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:1159
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:3206
Wikimedia\Rdbms\DatabasePostgres\fetchAffectedRowCount
fetchAffectedRowCount()
Definition: DatabasePostgres.php:274
Wikimedia\Rdbms\DatabasePostgres\getAttributes
static getAttributes()
Definition: DatabasePostgres.php:1341
Wikimedia\Rdbms\DatabasePostgres\getCoreSchemas
getCoreSchemas()
Return schema names for temporary tables and core application tables.
Definition: DatabasePostgres.php:946
n
while(( $__line=Maintenance::readconsole()) !==false) print n
Definition: eval.php:69
Wikimedia\Rdbms\DatabasePostgres\fieldInfo
fieldInfo( $table, $field)
Definition: DatabasePostgres.php:1136
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:1182
true
return true
Definition: router.php:90
Wikimedia\Rdbms\DatabasePostgres\listTables
listTables( $prefix='', $fname=__METHOD__)
Definition: DatabasePostgres.php:730
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:4696
Wikimedia\Rdbms\Database\indexName
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
Definition: Database.php:3418
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:2244
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:2337
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:2439
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\isConnectionError
isConnectionError( $errno)
Do not use this method outside of Database/DBError classes.
Definition: DatabasePostgres.php:655
Wikimedia\Rdbms\DatabasePostgres\textFieldSize
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited".intStability: stableto override
Definition: DatabasePostgres.php:623
$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:4746
Wikimedia\Rdbms\Database\getFlag
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition: Database.php:851
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:3829
Wikimedia\Rdbms\DatabasePostgres\doLockIsFree
doLockIsFree(string $lockName, string $method)
Definition: DatabasePostgres.php:1269
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:1284
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\roleExists
roleExists( $roleName)
Returns true if a given role (i.e.
Definition: DatabasePostgres.php:1120
Wikimedia\Rdbms\DatabasePostgres\$coreSchema
string $coreSchema
Definition: DatabasePostgres.php:36
Wikimedia\Rdbms\Database\close
close( $fname=__METHOD__, $owner=null)
Close the database connection.
Definition: Database.php:965
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:1140
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:1100
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:4634
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:641
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:666
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:594
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:1223
Wikimedia\Rdbms\Database\numRows
numRows( $res)
Get the number of rows in a query result.
Definition: Database.php:867
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:2576
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:774
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:1154
Wikimedia\Rdbms\DBQueryError
Definition: DBQueryError.php:29
Wikimedia\Rdbms\Database\installErrorHandler
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition: Database.php:902
Wikimedia\Rdbms\Database\restoreErrorHandler
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition: Database.php:913
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:1330
Wikimedia\Rdbms\DatabasePostgres\doTruncate
doTruncate(array $tables, $fname)
Definition: DatabasePostgres.php:718
$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:863
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:750
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:1292
Wikimedia\Rdbms\Database\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: Database.php:3087
Wikimedia\Rdbms\DatabasePostgres\decodeBlob
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects....
Definition: DatabasePostgres.php:1144
Wikimedia\Rdbms\DatabasePostgres\getCurrentSchema
getCurrentSchema()
Return current schema (executes SELECT current_schema()) Needs transaction.
Definition: DatabasePostgres.php:812
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
Definition: DBUnexpectedError.php:29
Wikimedia\Rdbms\DatabasePostgres\realTableName
realTableName( $name, $format='quoted')
Definition: DatabasePostgres.php:597
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:1835
Wikimedia\Rdbms\DatabasePostgres\nextSequenceValue
nextSequenceValue( $seqName)
Deprecated method, calls should be removed.
Definition: DatabasePostgres.php:601
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:1211
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:3445
Wikimedia\Rdbms\DatabasePostgres\getServerVersion
getServerVersion()
A string describing the current software version, like from mysql_get_server_info()
Definition: DatabasePostgres.php:965
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:1813
Wikimedia\Rdbms\DatabasePostgres\__construct
__construct(array $params)
Definition: DatabasePostgres.php:54
Wikimedia\Rdbms\DatabasePostgres\constraintExists
constraintExists( $table, $constraint)
Definition: DatabasePostgres.php:1075
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:650
Wikimedia\Rdbms\DatabasePostgres\streamStatementEnd
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
Definition: DatabasePostgres.php:1227
Wikimedia\Rdbms\Database\makeGroupByWithHaving
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1965
Wikimedia\Rdbms\DatabaseDomain\getSchema
getSchema()
Definition: DatabaseDomain.php:186
Wikimedia\Rdbms\DatabasePostgres\sequenceExists
sequenceExists( $sequence, $schema=false)
Definition: DatabasePostgres.php:1032
Wikimedia\Rdbms\DatabasePostgres\tableExists
tableExists( $table, $fname=__METHOD__, $schema=false)
For backward compatibility, this function checks both tables and views.
Definition: DatabasePostgres.php:1028
Wikimedia\Rdbms\DatabasePostgres\buildGroupConcatField
buildGroupConcatField( $delimiter, $table, $field, $conds='', $options=[], $join_conds=[])
Definition: DatabasePostgres.php:1215
Wikimedia\Rdbms\Database\makeOrderBy
makeOrderBy( $options)
Returns an optional ORDER BY.
Definition: Database.php:1991
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:1062
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:2003
Wikimedia\Rdbms\DatabasePostgres\wasKnownStatementRollbackError
wasKnownStatementRollbackError()
Definition: DatabasePostgres.php:662
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:898
Wikimedia\Rdbms\DatabasePostgres\getSearchPath
getSearchPath()
Return search patch for schemas This is different from getSchemas() since it contain magic keywords (...
Definition: DatabasePostgres.php:856
Wikimedia\Rdbms\DatabasePostgres\getCoreSchema
getCoreSchema()
Return schema name for core application tables.
Definition: DatabasePostgres.php:936
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:1036
Wikimedia\Rdbms\DatabasePostgres\currentSequenceValue
currentSequenceValue( $seqName)
Return the current value of a sequence.
Definition: DatabasePostgres.php:611
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:876
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:645
Wikimedia\Rdbms\DatabasePostgres\doSelectDomain
doSelectDomain(DatabaseDomain $domain)
Definition: DatabasePostgres.php:157
Wikimedia\Rdbms\PostgresBlob
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:3071
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
Definition: Blob.php:9