MediaWiki  master
DatabasePostgres.php
Go to the documentation of this file.
1 <?php
23 namespace Wikimedia\Rdbms;
24 
28 use Exception;
29 
33 class DatabasePostgres extends Database {
35  private $port;
37  private $coreSchema;
39  private $tempSchema;
41  private $keywordTableMap = [];
43  private $numericVersion;
44 
47 
53  public function __construct( array $params ) {
54  $this->port = intval( $params['port'] ?? null );
55  $this->keywordTableMap = $params['keywordTableMap'] ?? [];
56 
57  parent::__construct( $params );
58  }
59 
60  public function getType() {
61  return 'postgres';
62  }
63 
64  public function implicitOrderby() {
65  return false;
66  }
67 
68  public function hasConstraint( $name ) {
69  foreach ( $this->getCoreSchemas() as $schema ) {
70  $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
71  "WHERE c.connamespace = n.oid AND conname = " .
72  $this->addQuotes( $name ) . " AND n.nspname = " .
73  $this->addQuotes( $schema );
74  $res = $this->doQuery( $sql );
75  if ( $res && $this->numRows( $res ) ) {
76  return true;
77  }
78  }
79  return false;
80  }
81 
82  protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix ) {
83  if ( !function_exists( 'pg_connect' ) ) {
84  throw $this->newExceptionAfterConnectError(
85  "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
86  "option? (Note: if you recently installed PHP, you may need to restart your\n" .
87  "webserver and database)"
88  );
89  }
90 
91  $this->close();
92 
93  $this->server = $server;
94  $this->user = $user;
95  $this->password = $password;
96 
97  $connectVars = [
98  // A database must be specified in order to connect to Postgres. If $dbName is not
99  // specified, then use the standard "postgres" database that should exist by default.
100  'dbname' => strlen( $dbName ) ? $dbName : 'postgres',
101  'user' => $user,
102  'password' => $password
103  ];
104  if ( strlen( $server ) ) {
105  $connectVars['host'] = $server;
106  }
107  if ( $this->port > 0 ) {
108  $connectVars['port'] = $this->port;
109  }
110  if ( $this->getFlag( self::DBO_SSL ) ) {
111  $connectVars['sslmode'] = 'require';
112  }
113  $connectString = $this->makeConnectionString( $connectVars );
114 
115  $this->installErrorHandler();
116  try {
117  $this->conn = pg_connect( $connectString, PGSQL_CONNECT_FORCE_NEW ) ?: null;
118  } catch ( Exception $e ) {
119  $this->restoreErrorHandler();
120  throw $this->newExceptionAfterConnectError( $e->getMessage() );
121  }
122  $error = $this->restoreErrorHandler();
123 
124  if ( !$this->conn ) {
125  throw $this->newExceptionAfterConnectError( $error ?: $this->lastError() );
126  }
127 
128  try {
129  // Since no transaction is active at this point, any SET commands should apply
130  // for the entire session (e.g. will not be reverted on transaction rollback).
131  // See https://www.postgresql.org/docs/8.3/sql-set.html
132  $variables = [
133  'client_encoding' => 'UTF8',
134  'datestyle' => 'ISO, YMD',
135  'timezone' => 'GMT',
136  'standard_conforming_strings' => 'on',
137  'bytea_output' => 'escape',
138  'client_min_messages' => 'ERROR'
139  ];
140  foreach ( $variables as $var => $val ) {
141  $this->query(
142  'SET ' . $this->addIdentifierQuotes( $var ) . ' = ' . $this->addQuotes( $val ),
143  __METHOD__,
144  self::QUERY_IGNORE_DBO_TRX | self::QUERY_NO_RETRY
145  );
146  }
147  $this->determineCoreSchema( $schema );
148  $this->currentDomain = new DatabaseDomain( $dbName, $schema, $tablePrefix );
149  } catch ( Exception $e ) {
150  throw $this->newExceptionAfterConnectError( $e->getMessage() );
151  }
152  }
153 
154  protected function relationSchemaQualifier() {
155  if ( $this->coreSchema === $this->currentDomain->getSchema() ) {
156  // The schema to be used is now in the search path; no need for explicit qualification
157  return '';
158  }
159 
160  return parent::relationSchemaQualifier();
161  }
162 
163  public function databasesAreIndependent() {
164  return true;
165  }
166 
167  public function doSelectDomain( DatabaseDomain $domain ) {
168  if ( $this->getDBname() !== $domain->getDatabase() ) {
169  // Postgres doesn't support selectDB in the same way MySQL does.
170  // So if the DB name doesn't match the open connection, open a new one
171  $this->open(
172  $this->server,
173  $this->user,
174  $this->password,
175  $domain->getDatabase(),
176  $domain->getSchema(),
177  $domain->getTablePrefix()
178  );
179  } else {
180  $this->currentDomain = $domain;
181  }
182 
183  return true;
184  }
185 
190  private function makeConnectionString( $vars ) {
191  $s = '';
192  foreach ( $vars as $name => $value ) {
193  $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
194  }
195 
196  return $s;
197  }
198 
199  protected function closeConnection() {
200  return $this->conn ? pg_close( $this->conn ) : true;
201  }
202 
203  protected function isTransactableQuery( $sql ) {
204  return parent::isTransactableQuery( $sql ) &&
205  !preg_match( '/^SELECT\s+pg_(try_|)advisory_\w+\(/', $sql );
206  }
207 
212  public function doQuery( $sql ) {
213  $conn = $this->getBindingHandle();
214 
215  $sql = mb_convert_encoding( $sql, 'UTF-8' );
216  // Clear previously left over PQresult
217  while ( $res = pg_get_result( $conn ) ) {
218  pg_free_result( $res );
219  }
220  if ( pg_send_query( $conn, $sql ) === false ) {
221  throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
222  }
223  $this->lastResultHandle = pg_get_result( $conn );
224  if ( pg_result_error( $this->lastResultHandle ) ) {
225  return false;
226  }
227 
229  }
230 
231  protected function dumpError() {
232  $diags = [
233  PGSQL_DIAG_SEVERITY,
234  PGSQL_DIAG_SQLSTATE,
235  PGSQL_DIAG_MESSAGE_PRIMARY,
236  PGSQL_DIAG_MESSAGE_DETAIL,
237  PGSQL_DIAG_MESSAGE_HINT,
238  PGSQL_DIAG_STATEMENT_POSITION,
239  PGSQL_DIAG_INTERNAL_POSITION,
240  PGSQL_DIAG_INTERNAL_QUERY,
241  PGSQL_DIAG_CONTEXT,
242  PGSQL_DIAG_SOURCE_FILE,
243  PGSQL_DIAG_SOURCE_LINE,
244  PGSQL_DIAG_SOURCE_FUNCTION
245  ];
246  foreach ( $diags as $d ) {
247  $this->queryLogger->debug( sprintf( "PgSQL ERROR(%d): %s\n",
248  $d, pg_result_error_field( $this->lastResultHandle, $d ) ) );
249  }
250  }
251 
252  public function freeResult( $res ) {
253  AtEase::suppressWarnings();
254  $ok = pg_free_result( ResultWrapper::unwrap( $res ) );
255  AtEase::restoreWarnings();
256  if ( !$ok ) {
257  throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
258  }
259  }
260 
261  public function fetchObject( $res ) {
262  AtEase::suppressWarnings();
263  $row = pg_fetch_object( ResultWrapper::unwrap( $res ) );
264  AtEase::restoreWarnings();
265  # @todo FIXME: HACK HACK HACK HACK debug
266 
267  # @todo hashar: not sure if the following test really trigger if the object
268  # fetching failed.
269  $conn = $this->getBindingHandle();
270  if ( pg_last_error( $conn ) ) {
271  throw new DBUnexpectedError(
272  $this,
273  'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
274  );
275  }
276 
277  return $row;
278  }
279 
280  public function fetchRow( $res ) {
281  AtEase::suppressWarnings();
282  $row = pg_fetch_array( ResultWrapper::unwrap( $res ) );
283  AtEase::restoreWarnings();
284 
285  $conn = $this->getBindingHandle();
286  if ( pg_last_error( $conn ) ) {
287  throw new DBUnexpectedError(
288  $this,
289  'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
290  );
291  }
292 
293  return $row;
294  }
295 
296  public function numRows( $res ) {
297  if ( $res === false ) {
298  return 0;
299  }
300 
301  AtEase::suppressWarnings();
302  $n = pg_num_rows( ResultWrapper::unwrap( $res ) );
303  AtEase::restoreWarnings();
304 
305  $conn = $this->getBindingHandle();
306  if ( pg_last_error( $conn ) ) {
307  throw new DBUnexpectedError(
308  $this,
309  'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
310  );
311  }
312 
313  return $n;
314  }
315 
316  public function numFields( $res ) {
317  return pg_num_fields( ResultWrapper::unwrap( $res ) );
318  }
319 
320  public function fieldName( $res, $n ) {
321  return pg_field_name( ResultWrapper::unwrap( $res ), $n );
322  }
323 
324  public function insertId() {
325  $res = $this->query( "SELECT lastval()" );
326  $row = $this->fetchRow( $res );
327  return is_null( $row[0] ) ? null : (int)$row[0];
328  }
329 
330  public function dataSeek( $res, $row ) {
331  return pg_result_seek( ResultWrapper::unwrap( $res ), $row );
332  }
333 
334  public function lastError() {
335  if ( $this->conn ) {
336  if ( $this->lastResultHandle ) {
337  return pg_result_error( $this->lastResultHandle );
338  } else {
339  return pg_last_error();
340  }
341  }
342 
343  return $this->getLastPHPError() ?: 'No database connection';
344  }
345 
346  public function lastErrno() {
347  if ( $this->lastResultHandle ) {
348  return pg_result_error_field( $this->lastResultHandle, PGSQL_DIAG_SQLSTATE );
349  } else {
350  return false;
351  }
352  }
353 
354  protected function fetchAffectedRowCount() {
355  if ( !$this->lastResultHandle ) {
356  return 0;
357  }
358 
359  return pg_affected_rows( $this->lastResultHandle );
360  }
361 
377  public function estimateRowCount( $table, $var = '*', $conds = '',
378  $fname = __METHOD__, $options = [], $join_conds = []
379  ) {
380  $conds = $this->normalizeConditions( $conds, $fname );
381  $column = $this->extractSingleFieldFromList( $var );
382  if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
383  $conds[] = "$column IS NOT NULL";
384  }
385 
386  $options['EXPLAIN'] = true;
387  $res = $this->select( $table, $var, $conds, $fname, $options, $join_conds );
388  $rows = -1;
389  if ( $res ) {
390  $row = $this->fetchRow( $res );
391  $count = [];
392  if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
393  $rows = (int)$count[1];
394  }
395  }
396 
397  return $rows;
398  }
399 
400  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
401  $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
402  $res = $this->query( $sql, $fname );
403  if ( !$res ) {
404  return null;
405  }
406  foreach ( $res as $row ) {
407  if ( $row->indexname == $this->indexName( $index ) ) {
408  return $row;
409  }
410  }
411 
412  return false;
413  }
414 
415  public function indexAttributes( $index, $schema = false ) {
416  if ( $schema === false ) {
417  $schemas = $this->getCoreSchemas();
418  } else {
419  $schemas = [ $schema ];
420  }
421 
422  $eindex = $this->addQuotes( $index );
423 
424  foreach ( $schemas as $schema ) {
425  $eschema = $this->addQuotes( $schema );
426  /*
427  * A subquery would be not needed if we didn't care about the order
428  * of attributes, but we do
429  */
430  $sql = <<<__INDEXATTR__
431 
432  SELECT opcname,
433  attname,
434  i.indoption[s.g] as option,
435  pg_am.amname
436  FROM
437  (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
438  FROM
439  pg_index isub
440  JOIN pg_class cis
441  ON cis.oid=isub.indexrelid
442  JOIN pg_namespace ns
443  ON cis.relnamespace = ns.oid
444  WHERE cis.relname=$eindex AND ns.nspname=$eschema) AS s,
445  pg_attribute,
446  pg_opclass opcls,
447  pg_am,
448  pg_class ci
449  JOIN pg_index i
450  ON ci.oid=i.indexrelid
451  JOIN pg_class ct
452  ON ct.oid = i.indrelid
453  JOIN pg_namespace n
454  ON ci.relnamespace = n.oid
455  WHERE
456  ci.relname=$eindex AND n.nspname=$eschema
457  AND attrelid = ct.oid
458  AND i.indkey[s.g] = attnum
459  AND i.indclass[s.g] = opcls.oid
460  AND pg_am.oid = opcls.opcmethod
461 __INDEXATTR__;
462  $res = $this->query( $sql, __METHOD__ );
463  $a = [];
464  if ( $res ) {
465  foreach ( $res as $row ) {
466  $a[] = [
467  $row->attname,
468  $row->opcname,
469  $row->amname,
470  $row->option ];
471  }
472  return $a;
473  }
474  }
475  return null;
476  }
477 
478  public function indexUnique( $table, $index, $fname = __METHOD__ ) {
479  $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
480  " AND indexdef LIKE 'CREATE UNIQUE%(" .
481  $this->strencode( $this->indexName( $index ) ) .
482  ")'";
483  $res = $this->query( $sql, $fname );
484  if ( !$res ) {
485  return null;
486  }
487 
488  return $res->numRows() > 0;
489  }
490 
491  public function selectSQLText(
492  $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
493  ) {
494  if ( is_string( $options ) ) {
495  $options = [ $options ];
496  }
497 
498  // Change the FOR UPDATE option as necessary based on the join conditions. Then pass
499  // to the parent function to get the actual SQL text.
500  // In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
501  // can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to
502  // do so causes a DB error. This wrapper checks which tables can be locked and adjusts it
503  // accordingly.
504  // MySQL uses "ORDER BY NULL" as an optimization hint, but that is illegal in PostgreSQL.
505  if ( is_array( $options ) ) {
506  $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
507  if ( $forUpdateKey !== false && $join_conds ) {
508  unset( $options[$forUpdateKey] );
509  $options['FOR UPDATE'] = [];
510 
511  $toCheck = $table;
512  reset( $toCheck );
513  while ( $toCheck ) {
514  $alias = key( $toCheck );
515  $name = $toCheck[$alias];
516  unset( $toCheck[$alias] );
517 
518  $hasAlias = !is_numeric( $alias );
519  if ( !$hasAlias && is_string( $name ) ) {
520  $alias = $name;
521  }
522 
523  if ( !isset( $join_conds[$alias] ) ||
524  !preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_conds[$alias][0] )
525  ) {
526  if ( is_array( $name ) ) {
527  // It's a parenthesized group, process all the tables inside the group.
528  $toCheck = array_merge( $toCheck, $name );
529  } else {
530  // Quote alias names so $this->tableName() won't mangle them
531  $options['FOR UPDATE'][] = $hasAlias ? $this->addIdentifierQuotes( $alias ) : $alias;
532  }
533  }
534  }
535  }
536 
537  if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
538  unset( $options['ORDER BY'] );
539  }
540  }
541 
542  return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
543  }
544 
546  public function insert( $table, $args, $fname = __METHOD__, $options = [] ) {
547  if ( !count( $args ) ) {
548  return true;
549  }
550 
551  $table = $this->tableName( $table );
552  if ( !isset( $this->numericVersion ) ) {
553  $this->getServerVersion();
554  }
555 
556  if ( !is_array( $options ) ) {
557  $options = [ $options ];
558  }
559 
560  if ( isset( $args[0] ) && is_array( $args[0] ) ) {
561  $rows = $args;
562  $keys = array_keys( $args[0] );
563  } else {
564  $rows = [ $args ];
565  $keys = array_keys( $args );
566  }
567 
568  $ignore = in_array( 'IGNORE', $options );
569 
570  $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
571 
572  if ( $this->numericVersion >= 9.5 || !$ignore ) {
573  // No IGNORE or our PG has "ON CONFLICT DO NOTHING"
574  $first = true;
575  foreach ( $rows as $row ) {
576  if ( $first ) {
577  $first = false;
578  } else {
579  $sql .= ',';
580  }
581  $sql .= '(' . $this->makeList( $row ) . ')';
582  }
583  if ( $ignore ) {
584  $sql .= ' ON CONFLICT DO NOTHING';
585  }
586  $this->query( $sql, $fname );
587  } else {
588  // Emulate IGNORE by doing each row individually, with savepoints
589  // to roll back as necessary.
590  $numrowsinserted = 0;
591 
592  $tok = $this->startAtomic( "$fname (outer)", self::ATOMIC_CANCELABLE );
593  try {
594  foreach ( $rows as $row ) {
595  $tempsql = $sql;
596  $tempsql .= '(' . $this->makeList( $row ) . ')';
597 
598  $this->startAtomic( "$fname (inner)", self::ATOMIC_CANCELABLE );
599  try {
600  $this->query( $tempsql, $fname );
601  $this->endAtomic( "$fname (inner)" );
602  $numrowsinserted++;
603  } catch ( DBQueryError $e ) {
604  $this->cancelAtomic( "$fname (inner)" );
605  // Our IGNORE is supposed to ignore duplicate key errors, but not others.
606  // (even though MySQL's version apparently ignores all errors)
607  if ( $e->errno !== '23505' ) {
608  throw $e;
609  }
610  }
611  }
612  } catch ( Exception $e ) {
613  $this->cancelAtomic( "$fname (outer)", $tok );
614  throw $e;
615  }
616  $this->endAtomic( "$fname (outer)" );
617 
618  // Set the affected row count for the whole operation
619  $this->affectedRowCount = $numrowsinserted;
620  }
621 
622  return true;
623  }
624 
625  protected function makeUpdateOptionsArray( $options ) {
626  if ( !is_array( $options ) ) {
627  $options = [ $options ];
628  }
629 
630  // PostgreSQL doesn't support anything like "ignore" for
631  // UPDATE.
632  $options = array_diff( $options, [ 'IGNORE' ] );
633 
634  return parent::makeUpdateOptionsArray( $options );
635  }
636 
655  protected function nativeInsertSelect(
656  $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
657  $insertOptions = [], $selectOptions = [], $selectJoinConds = []
658  ) {
659  if ( !is_array( $insertOptions ) ) {
660  $insertOptions = [ $insertOptions ];
661  }
662 
663  if ( in_array( 'IGNORE', $insertOptions ) ) {
664  if ( $this->getServerVersion() >= 9.5 ) {
665  // Use ON CONFLICT DO NOTHING if we have it for IGNORE
666  $destTable = $this->tableName( $destTable );
667 
668  $selectSql = $this->selectSQLText(
669  $srcTable,
670  array_values( $varMap ),
671  $conds,
672  $fname,
673  $selectOptions,
674  $selectJoinConds
675  );
676 
677  $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' .
678  $selectSql . ' ON CONFLICT DO NOTHING';
679 
680  $this->query( $sql, $fname );
681  } else {
682  // IGNORE and we don't have ON CONFLICT DO NOTHING, so just use the non-native version
683  $this->nonNativeInsertSelect(
684  $destTable, $srcTable, $varMap, $conds, $fname,
685  $insertOptions, $selectOptions, $selectJoinConds
686  );
687  }
688  } else {
689  parent::nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname,
690  $insertOptions, $selectOptions, $selectJoinConds );
691  }
692  }
693 
694  public function tableName( $name, $format = 'quoted' ) {
695  // Replace reserved words with better ones
696  $name = $this->remappedTableName( $name );
697 
698  return parent::tableName( $name, $format );
699  }
700 
705  public function remappedTableName( $name ) {
706  return $this->keywordTableMap[$name] ?? $name;
707  }
708 
714  public function realTableName( $name, $format = 'quoted' ) {
715  return parent::tableName( $name, $format );
716  }
717 
718  public function nextSequenceValue( $seqName ) {
719  return new NextSequenceValue;
720  }
721 
728  public function currentSequenceValue( $seqName ) {
729  $safeseq = str_replace( "'", "''", $seqName );
730  $res = $this->query( "SELECT currval('$safeseq')" );
731  $row = $this->fetchRow( $res );
732  $currval = $row[0];
733 
734  return $currval;
735  }
736 
737  public function textFieldSize( $table, $field ) {
738  $table = $this->tableName( $table );
739  $sql = "SELECT t.typname as ftype,a.atttypmod as size
740  FROM pg_class c, pg_attribute a, pg_type t
741  WHERE relname='$table' AND a.attrelid=c.oid AND
742  a.atttypid=t.oid and a.attname='$field'";
743  $res = $this->query( $sql );
744  $row = $this->fetchObject( $res );
745  if ( $row->ftype == 'varchar' ) {
746  $size = $row->size - 4;
747  } else {
748  $size = $row->size;
749  }
750 
751  return $size;
752  }
753 
754  public function limitResult( $sql, $limit, $offset = false ) {
755  return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
756  }
757 
758  public function wasDeadlock() {
759  // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
760  return $this->lastErrno() === '40P01';
761  }
762 
763  public function wasLockTimeout() {
764  // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
765  return $this->lastErrno() === '55P03';
766  }
767 
768  public function wasConnectionError( $errno ) {
769  // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
770  static $codes = [ '08000', '08003', '08006', '08001', '08004', '57P01', '57P03', '53300' ];
771 
772  return in_array( $errno, $codes, true );
773  }
774 
775  protected function wasKnownStatementRollbackError() {
776  return false; // transaction has to be rolled-back from error state
777  }
778 
779  public function duplicateTableStructure(
780  $oldName, $newName, $temporary = false, $fname = __METHOD__
781  ) {
782  $newNameE = $this->addIdentifierQuotes( $newName );
783  $oldNameE = $this->addIdentifierQuotes( $oldName );
784 
785  $temporary = $temporary ? 'TEMPORARY' : '';
786 
787  $ret = $this->query(
788  "CREATE $temporary TABLE $newNameE " .
789  "(LIKE $oldNameE INCLUDING DEFAULTS INCLUDING INDEXES)",
790  $fname,
791  $this::QUERY_PSEUDO_PERMANENT
792  );
793  if ( !$ret ) {
794  return $ret;
795  }
796 
797  $res = $this->query( 'SELECT attname FROM pg_class c'
798  . ' JOIN pg_namespace n ON (n.oid = c.relnamespace)'
799  . ' JOIN pg_attribute a ON (a.attrelid = c.oid)'
800  . ' JOIN pg_attrdef d ON (c.oid=d.adrelid and a.attnum=d.adnum)'
801  . ' WHERE relkind = \'r\''
802  . ' AND nspname = ' . $this->addQuotes( $this->getCoreSchema() )
803  . ' AND relname = ' . $this->addQuotes( $oldName )
804  . ' AND pg_get_expr(adbin, adrelid) LIKE \'nextval(%\'',
805  $fname
806  );
807  $row = $this->fetchObject( $res );
808  if ( $row ) {
809  $field = $row->attname;
810  $newSeq = "{$newName}_{$field}_seq";
811  $fieldE = $this->addIdentifierQuotes( $field );
812  $newSeqE = $this->addIdentifierQuotes( $newSeq );
813  $newSeqQ = $this->addQuotes( $newSeq );
814  $this->query(
815  "CREATE $temporary SEQUENCE $newSeqE OWNED BY $newNameE.$fieldE",
816  $fname
817  );
818  $this->query(
819  "ALTER TABLE $newNameE ALTER COLUMN $fieldE SET DEFAULT nextval({$newSeqQ}::regclass)",
820  $fname
821  );
822  }
823 
824  return $ret;
825  }
826 
827  public function resetSequenceForTable( $table, $fname = __METHOD__ ) {
828  $table = $this->tableName( $table, 'raw' );
829  foreach ( $this->getCoreSchemas() as $schema ) {
830  $res = $this->query(
831  'SELECT c.oid FROM pg_class c JOIN pg_namespace n ON (n.oid = c.relnamespace)'
832  . ' WHERE relkind = \'r\''
833  . ' AND nspname = ' . $this->addQuotes( $schema )
834  . ' AND relname = ' . $this->addQuotes( $table ),
835  $fname
836  );
837  if ( !$res || !$this->numRows( $res ) ) {
838  continue;
839  }
840 
841  $oid = $this->fetchObject( $res )->oid;
842  $res = $this->query( 'SELECT pg_get_expr(adbin, adrelid) AS adsrc FROM pg_attribute a'
843  . ' JOIN pg_attrdef d ON (a.attrelid=d.adrelid and a.attnum=d.adnum)'
844  . " WHERE a.attrelid = $oid"
845  . ' AND pg_get_expr(adbin, adrelid) LIKE \'nextval(%\'',
846  $fname
847  );
848  $row = $this->fetchObject( $res );
849  if ( $row ) {
850  $this->query(
851  'SELECT ' . preg_replace( '/^nextval\((.+)\)$/', 'setval($1,1,false)', $row->adsrc ),
852  $fname
853  );
854  return true;
855  }
856  return false;
857  }
858 
859  return false;
860  }
861 
868  public function listTables( $prefix = '', $fname = __METHOD__ ) {
869  $eschemas = implode( ',', array_map( [ $this, 'addQuotes' ], $this->getCoreSchemas() ) );
870  $result = $this->query(
871  "SELECT DISTINCT tablename FROM pg_tables WHERE schemaname IN ($eschemas)", $fname );
872  $endArray = [];
873 
874  foreach ( $result as $table ) {
875  $vars = get_object_vars( $table );
876  $table = array_pop( $vars );
877  if ( $prefix == '' || strpos( $table, $prefix ) === 0 ) {
878  $endArray[] = $table;
879  }
880  }
881 
882  return $endArray;
883  }
884 
885  public function timestamp( $ts = 0 ) {
886  $ct = new ConvertibleTimestamp( $ts );
887 
888  return $ct->getTimestamp( TS_POSTGRES );
889  }
890 
909  private function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
910  if ( $limit === false ) {
911  $limit = strlen( $text ) - 1;
912  $output = [];
913  }
914  if ( $text == '{}' ) {
915  return $output;
916  }
917  do {
918  if ( $text[$offset] != '{' ) {
919  preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
920  $text, $match, 0, $offset );
921  $offset += strlen( $match[0] );
922  $output[] = ( $match[1][0] != '"'
923  ? $match[1]
924  : stripcslashes( substr( $match[1], 1, -1 ) ) );
925  if ( $match[3] == '},' ) {
926  return $output;
927  }
928  } else {
929  $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
930  }
931  } while ( $limit > $offset );
932 
933  return $output;
934  }
935 
936  public function aggregateValue( $valuedata, $valuename = 'value' ) {
937  return $valuedata;
938  }
939 
940  public function getSoftwareLink() {
941  return '[{{int:version-db-postgres-url}} PostgreSQL]';
942  }
943 
951  public function getCurrentSchema() {
952  $res = $this->query( "SELECT current_schema()", __METHOD__, self::QUERY_IGNORE_DBO_TRX );
953  $row = $this->fetchRow( $res );
954 
955  return $row[0];
956  }
957 
968  public function getSchemas() {
969  $res = $this->query(
970  "SELECT current_schemas(false)",
971  __METHOD__,
972  self::QUERY_IGNORE_DBO_TRX
973  );
974  $row = $this->fetchRow( $res );
975  $schemas = [];
976 
977  /* PHP pgsql support does not support array type, "{a,b}" string is returned */
978 
979  return $this->pg_array_parse( $row[0], $schemas );
980  }
981 
991  public function getSearchPath() {
992  $res = $this->query( "SHOW search_path", __METHOD__, self::QUERY_IGNORE_DBO_TRX );
993  $row = $this->fetchRow( $res );
994 
995  /* PostgreSQL returns SHOW values as strings */
996 
997  return explode( ",", $row[0] );
998  }
999 
1007  private function setSearchPath( $search_path ) {
1008  $this->query(
1009  "SET search_path = " . implode( ", ", $search_path ),
1010  __METHOD__,
1011  self::QUERY_IGNORE_DBO_TRX
1012  );
1013  }
1014 
1029  public function determineCoreSchema( $desiredSchema ) {
1030  if ( $this->trxLevel() ) {
1031  // We do not want the schema selection to change on ROLLBACK or INSERT SELECT.
1032  // See https://www.postgresql.org/docs/8.3/sql-set.html
1033  throw new DBUnexpectedError(
1034  $this,
1035  __METHOD__ . ": a transaction is currently active"
1036  );
1037  }
1038 
1039  if ( $this->schemaExists( $desiredSchema ) ) {
1040  if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
1041  $this->coreSchema = $desiredSchema;
1042  $this->queryLogger->debug(
1043  "Schema \"" . $desiredSchema . "\" already in the search path\n" );
1044  } else {
1045  // Prepend the desired schema to the search path (T17816)
1046  $search_path = $this->getSearchPath();
1047  array_unshift( $search_path, $this->addIdentifierQuotes( $desiredSchema ) );
1048  $this->setSearchPath( $search_path );
1049  $this->coreSchema = $desiredSchema;
1050  $this->queryLogger->debug(
1051  "Schema \"" . $desiredSchema . "\" added to the search path\n" );
1052  }
1053  } else {
1054  $this->coreSchema = $this->getCurrentSchema();
1055  $this->queryLogger->debug(
1056  "Schema \"" . $desiredSchema . "\" not found, using current \"" .
1057  $this->coreSchema . "\"\n" );
1058  }
1059  }
1060 
1067  public function getCoreSchema() {
1068  return $this->coreSchema;
1069  }
1070 
1077  public function getCoreSchemas() {
1078  if ( $this->tempSchema ) {
1079  return [ $this->tempSchema, $this->getCoreSchema() ];
1080  }
1081 
1082  $res = $this->query(
1083  "SELECT nspname FROM pg_catalog.pg_namespace n WHERE n.oid = pg_my_temp_schema()", __METHOD__
1084  );
1085  $row = $this->fetchObject( $res );
1086  if ( $row ) {
1087  $this->tempSchema = $row->nspname;
1088  return [ $this->tempSchema, $this->getCoreSchema() ];
1089  }
1090 
1091  return [ $this->getCoreSchema() ];
1092  }
1093 
1094  public function getServerVersion() {
1095  if ( !isset( $this->numericVersion ) ) {
1096  $conn = $this->getBindingHandle();
1097  $versionInfo = pg_version( $conn );
1098  if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
1099  // Old client, abort install
1100  $this->numericVersion = '7.3 or earlier';
1101  } elseif ( isset( $versionInfo['server'] ) ) {
1102  // Normal client
1103  $this->numericVersion = $versionInfo['server'];
1104  } else {
1105  // T18937: broken pgsql extension from PHP<5.3
1106  $this->numericVersion = pg_parameter_status( $conn, 'server_version' );
1107  }
1108  }
1109 
1110  return $this->numericVersion;
1111  }
1112 
1121  private function relationExists( $table, $types, $schema = false ) {
1122  if ( !is_array( $types ) ) {
1123  $types = [ $types ];
1124  }
1125  if ( $schema === false ) {
1126  $schemas = $this->getCoreSchemas();
1127  } else {
1128  $schemas = [ $schema ];
1129  }
1130  $table = $this->realTableName( $table, 'raw' );
1131  $etable = $this->addQuotes( $table );
1132  foreach ( $schemas as $schema ) {
1133  $eschema = $this->addQuotes( $schema );
1134  $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
1135  . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
1136  . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
1137  $res = $this->query( $sql );
1138  if ( $res && $res->numRows() ) {
1139  return true;
1140  }
1141  }
1142 
1143  return false;
1144  }
1145 
1153  public function tableExists( $table, $fname = __METHOD__, $schema = false ) {
1154  return $this->relationExists( $table, [ 'r', 'v' ], $schema );
1155  }
1156 
1157  public function sequenceExists( $sequence, $schema = false ) {
1158  return $this->relationExists( $sequence, 'S', $schema );
1159  }
1160 
1161  public function triggerExists( $table, $trigger ) {
1162  $q = <<<SQL
1163  SELECT 1 FROM pg_class, pg_namespace, pg_trigger
1164  WHERE relnamespace=pg_namespace.oid AND relkind='r'
1165  AND tgrelid=pg_class.oid
1166  AND nspname=%s AND relname=%s AND tgname=%s
1167 SQL;
1168  foreach ( $this->getCoreSchemas() as $schema ) {
1169  $res = $this->query(
1170  sprintf(
1171  $q,
1172  $this->addQuotes( $schema ),
1173  $this->addQuotes( $table ),
1174  $this->addQuotes( $trigger )
1175  )
1176  );
1177  if ( $res && $res->numRows() ) {
1178  return true;
1179  }
1180  }
1181 
1182  return false;
1183  }
1184 
1185  public function ruleExists( $table, $rule ) {
1186  $exists = $this->selectField( 'pg_rules', 'rulename',
1187  [
1188  'rulename' => $rule,
1189  'tablename' => $table,
1190  'schemaname' => $this->getCoreSchemas()
1191  ]
1192  );
1193 
1194  return $exists === $rule;
1195  }
1196 
1197  public function constraintExists( $table, $constraint ) {
1198  foreach ( $this->getCoreSchemas() as $schema ) {
1199  $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
1200  "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
1201  $this->addQuotes( $schema ),
1202  $this->addQuotes( $table ),
1203  $this->addQuotes( $constraint )
1204  );
1205  $res = $this->query( $sql );
1206  if ( $res && $res->numRows() ) {
1207  return true;
1208  }
1209  }
1210  return false;
1211  }
1212 
1218  public function schemaExists( $schema ) {
1219  if ( !strlen( $schema ) ) {
1220  return false; // short-circuit
1221  }
1222 
1223  $res = $this->query(
1224  "SELECT 1 FROM pg_catalog.pg_namespace " .
1225  "WHERE nspname = " . $this->addQuotes( $schema ) . " LIMIT 1",
1226  __METHOD__,
1227  self::QUERY_IGNORE_DBO_TRX
1228  );
1229 
1230  return ( $this->numRows( $res ) > 0 );
1231  }
1232 
1238  public function roleExists( $roleName ) {
1239  $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
1240  [ 'rolname' => $roleName ], __METHOD__ );
1241 
1242  return (bool)$exists;
1243  }
1244 
1250  public function fieldInfo( $table, $field ) {
1251  return PostgresField::fromText( $this, $table, $field );
1252  }
1253 
1260  public function fieldType( $res, $index ) {
1261  return pg_field_type( ResultWrapper::unwrap( $res ), $index );
1262  }
1263 
1264  public function encodeBlob( $b ) {
1265  return new PostgresBlob( pg_escape_bytea( $b ) );
1266  }
1267 
1268  public function decodeBlob( $b ) {
1269  if ( $b instanceof PostgresBlob ) {
1270  $b = $b->fetch();
1271  } elseif ( $b instanceof Blob ) {
1272  return $b->fetch();
1273  }
1274 
1275  return pg_unescape_bytea( $b );
1276  }
1277 
1278  public function strencode( $s ) {
1279  // Should not be called by us
1280  return pg_escape_string( $this->getBindingHandle(), (string)$s );
1281  }
1282 
1283  public function addQuotes( $s ) {
1284  $conn = $this->getBindingHandle();
1285 
1286  if ( is_null( $s ) ) {
1287  return 'NULL';
1288  } elseif ( is_bool( $s ) ) {
1289  return (string)intval( $s );
1290  } elseif ( is_int( $s ) ) {
1291  return (string)$s;
1292  } elseif ( $s instanceof Blob ) {
1293  if ( $s instanceof PostgresBlob ) {
1294  $s = $s->fetch();
1295  } else {
1296  $s = pg_escape_bytea( $conn, $s->fetch() );
1297  }
1298  return "'$s'";
1299  } elseif ( $s instanceof NextSequenceValue ) {
1300  return 'DEFAULT';
1301  }
1302 
1303  return "'" . pg_escape_string( $conn, (string)$s ) . "'";
1304  }
1305 
1306  protected function makeSelectOptions( array $options ) {
1307  $preLimitTail = $postLimitTail = '';
1308  $startOpts = $useIndex = $ignoreIndex = '';
1309 
1310  $noKeyOptions = [];
1311  foreach ( $options as $key => $option ) {
1312  if ( is_numeric( $key ) ) {
1313  $noKeyOptions[$option] = true;
1314  }
1315  }
1316 
1317  $preLimitTail .= $this->makeGroupByWithHaving( $options );
1318 
1319  $preLimitTail .= $this->makeOrderBy( $options );
1320 
1321  if ( isset( $options['FOR UPDATE'] ) ) {
1322  $postLimitTail .= ' FOR UPDATE OF ' .
1323  implode( ', ', array_map( [ $this, 'tableName' ], $options['FOR UPDATE'] ) );
1324  } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1325  $postLimitTail .= ' FOR UPDATE';
1326  }
1327 
1328  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1329  $startOpts .= 'DISTINCT';
1330  }
1331 
1332  return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1333  }
1334 
1335  public function buildConcat( $stringList ) {
1336  return implode( ' || ', $stringList );
1337  }
1338 
1339  public function buildGroupConcatField(
1340  $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
1341  ) {
1342  $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
1343 
1344  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1345  }
1346 
1347  public function buildStringCast( $field ) {
1348  return $field . '::text';
1349  }
1350 
1351  public function streamStatementEnd( &$sql, &$newLine ) {
1352  # Allow dollar quoting for function declarations
1353  if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
1354  if ( $this->delimiter ) {
1355  $this->delimiter = false;
1356  } else {
1357  $this->delimiter = ';';
1358  }
1359  }
1360 
1361  return parent::streamStatementEnd( $sql, $newLine );
1362  }
1363 
1364  public function doLockTables( array $read, array $write, $method ) {
1365  $tablesWrite = [];
1366  foreach ( $write as $table ) {
1367  $tablesWrite[] = $this->tableName( $table );
1368  }
1369  $tablesRead = [];
1370  foreach ( $read as $table ) {
1371  $tablesRead[] = $this->tableName( $table );
1372  }
1373 
1374  // Acquire locks for the duration of the current transaction...
1375  if ( $tablesWrite ) {
1376  $this->query(
1377  'LOCK TABLE ONLY ' . implode( ',', $tablesWrite ) . ' IN EXCLUSIVE MODE',
1378  $method
1379  );
1380  }
1381  if ( $tablesRead ) {
1382  $this->query(
1383  'LOCK TABLE ONLY ' . implode( ',', $tablesRead ) . ' IN SHARE MODE',
1384  $method
1385  );
1386  }
1387 
1388  return true;
1389  }
1390 
1391  public function lockIsFree( $lockName, $method ) {
1392  if ( !parent::lockIsFree( $lockName, $method ) ) {
1393  return false; // already held
1394  }
1395  // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1396  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1397  $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
1398  WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
1399  $row = $this->fetchObject( $result );
1400 
1401  return ( $row->lockstatus === 't' );
1402  }
1403 
1404  public function lock( $lockName, $method, $timeout = 5 ) {
1405  // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1406  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1407  $loop = new WaitConditionLoop(
1408  function () use ( $lockName, $key, $timeout, $method ) {
1409  $res = $this->query( "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
1410  $row = $this->fetchObject( $res );
1411  if ( $row->lockstatus === 't' ) {
1412  parent::lock( $lockName, $method, $timeout ); // record
1413  return true;
1414  }
1415 
1416  return WaitConditionLoop::CONDITION_CONTINUE;
1417  },
1418  $timeout
1419  );
1420 
1421  return ( $loop->invoke() === $loop::CONDITION_REACHED );
1422  }
1423 
1424  public function unlock( $lockName, $method ) {
1425  // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1426  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1427  $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
1428  $row = $this->fetchObject( $result );
1429 
1430  if ( $row->lockstatus === 't' ) {
1431  parent::unlock( $lockName, $method ); // record
1432  return true;
1433  }
1434 
1435  $this->queryLogger->debug( __METHOD__ . " failed to release lock\n" );
1436 
1437  return false;
1438  }
1439 
1440  public function serverIsReadOnly() {
1441  $res = $this->query( "SHOW default_transaction_read_only", __METHOD__ );
1442  $row = $this->fetchObject( $res );
1443 
1444  return $row ? ( strtolower( $row->default_transaction_read_only ) === 'on' ) : false;
1445  }
1446 
1447  protected static function getAttributes() {
1448  return [ self::ATTR_SCHEMAS_AS_TABLE_GROUPS => true ];
1449  }
1450 
1455  private function bigintFromLockName( $lockName ) {
1456  return \Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
1457  }
1458 }
1459 
1463 class_alias( DatabasePostgres::class, 'DatabasePostgres' );
normalizeConditions( $conds, $fname)
Definition: Database.php:2026
close( $fname=__METHOD__, $owner=null)
Close the database connection.
Definition: Database.php:892
open( $server, $user, $password, $dbName, $schema, $tablePrefix)
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects...
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition: Database.php:802
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Creates a new table with structure copied from existing table.
setSearchPath( $search_path)
Update search_path, values should already be sanitized Values may contain magic keywords like "$user"...
resetSequenceForTable( $table, $fname=__METHOD__)
getType()
Get the type of the DBMS (e.g.
fieldName( $res, $n)
Get a field name in a result object.
nextSequenceValue( $seqName)
Deprecated method, calls should be removed.
sequenceExists( $sequence, $schema=false)
indexAttributes( $index, $schema=false)
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.
roleExists( $roleName)
Returns true if a given role (i.e.
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 ...
getSoftwareLink()
Returns a wikitext style link to the DB&#39;s website (e.g.
numRows( $res)
Get the number of rows in a query result.
doLockTables(array $read, array $write, $method)
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:1652
nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[], $selectJoinConds=[])
INSERT SELECT wrapper $varMap must be an associative array of the form [ &#39;dest1&#39; => &#39;source1&#39;...
relationExists( $table, $types, $schema=false)
Query whether a given relation exists (in the given schema, or the default mw one if not given) ...
nonNativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[], $selectJoinConds=[])
Implementation of insertSelect() based on select() and insert()
Definition: Database.php:3118
trxLevel()
Gets the current transaction level.
Definition: Database.php:550
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Definition: Database.php:2222
startAtomic( $fname=__METHOD__, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Begin an atomic section of SQL statements.
Definition: Database.php:3834
endAtomic( $fname=__METHOD__)
Ends an atomic section of SQL statements.
Definition: Database.php:3864
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query and return the result.
Definition: Database.php:1155
string $server
Server that this instance is currently connected to.
Definition: Database.php:75
lastErrno()
Get the last error number.
getCoreSchemas()
Return schema names for temporary tables and core application tables.
currentSequenceValue( $seqName)
Return the current value of a sequence.
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we&#39;ve reached a statement end.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
indexInfo( $table, $index, $fname=__METHOD__)
newExceptionAfterConnectError( $error)
Definition: Database.php:1634
freeResult( $res)
Free a result object returned by query() or select()
getCoreSchema()
Return schema name for core application tables.
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition: Database.php:830
getDBname()
Get the current DB name.
Definition: Database.php:2426
indexUnique( $table, $index, $fname=__METHOD__)
getSchemas()
Return list of schemas which are accessible without schema name This is list does not contain magic k...
if( $line===false) $args
Definition: mcc.php:124
string [] $keywordTableMap
Map of (reserved table name => alternate table name)
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
Definition: Database.php:2751
unlock( $lockName, $method)
Release a lock.
insert( $table, $args, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.$a may be either:A single associative array...
doSelectDomain(DatabaseDomain $domain)
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
string $delimiter
Current SQL query delimiter.
Definition: Database.php:100
dataSeek( $res, $row)
Change the position of the cursor in a result object.
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.pgsql.php.
numFields( $res)
Get the number of fields in a result object.
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.
buildGroupConcatField( $delimiter, $table, $field, $conds='', $options=[], $join_conds=[])
fetchObject( $res)
Fetch the next row from the given result object, in object form.
aggregateValue( $valuedata, $valuename='value')
Return aggregated value alias.
static fromText(DatabasePostgres $db, $table, $field)
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited".
cancelAtomic( $fname=__METHOD__, AtomicSectionIdentifier $sectionId=null)
Cancel an atomic section of SQL statements.
Definition: Database.php:3898
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Take the same arguments as IDatabase::select() and return the SQL it would use.
encodeBlob( $b)
Some DBMSs have a special format for inserting into blob fields, they don&#39;t allow simple quoted strin...
Used by Database::nextSequenceValue() so Database::insert() can detect values coming from the depreca...
Class to handle database/schema/prefix specifications for IDatabase.
fieldType( $res, $index)
pg_field_type() wrapper
getServerVersion()
A string describing the current software version, like from mysql_get_server_info() ...
Relational database abstraction object.
Definition: Database.php:49
lock( $lockName, $method, $timeout=5)
Acquire a named lock.
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
Definition: Database.php:1823
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition: Database.php:841
object resource null $conn
Database connection.
Definition: Database.php:69
databasesAreIndependent()
Returns true if DBs are assumed to be on potentially different servers.
string $user
User that this instance is currently connected under the name of.
Definition: Database.php:77
addIdentifierQuotes( $s)
Escape a SQL identifier (e.g.
Definition: Database.php:2770
makeOrderBy( $options)
Returns an optional ORDER BY.
Definition: Database.php:1811
wasLockTimeout()
Determines if the last failure was due to a lock timeout.
lockIsFree( $lockName, $method)
Check to see if a named lock is not locked by any thread (non-blocking)
fetchRow( $res)
Fetch the next row from the given result object, in associative array form.
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1785
tableName( $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.
getSearchPath()
Return search patch for schemas This is different from getSchemas() since it contain magic keywords (...
lastError()
Get a description of the last error.
const DBO_SSL
Definition: defines.php:17
schemaExists( $schema)
Query whether a given schema exists.
wasDeadlock()
Determines if the last failure was due to a deadlock.
getBindingHandle()
Get the underlying binding connection handle.
Definition: Database.php:4873
determineCoreSchema( $desiredSchema)
Determine default schema for the current application Adjust this session schema search path if desire...
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.
insertId()
Get the inserted value of an auto-increment row.
return true
Definition: router.php:92
listTables( $prefix='', $fname=__METHOD__)
realTableName( $name, $format='quoted')
static & unwrap(&$res)
Get the underlying RDBMS driver-specific result resource.
string $password
Password used to establish the current connection.
Definition: Database.php:79
while(( $__line=Maintenance::readconsole()) !==false) print n
Definition: eval.php:64
constraintExists( $table, $constraint)
getCurrentSchema()
Return current schema (executes SELECT current_schema()) Needs transaction.
tableExists( $table, $fname=__METHOD__, $schema=false)
For backward compatibility, this function checks both tables and views.