MediaWiki  1.30.0
DatabasePostgres.php
Go to the documentation of this file.
1 <?php
23 namespace Wikimedia\Rdbms;
24 
25 use Wikimedia\Timestamp\ConvertibleTimestamp;
26 use Wikimedia\WaitConditionLoop;
28 use Exception;
29 
33 class DatabasePostgres extends Database {
35  protected $port;
36 
38  protected $mLastResult = null;
40  protected $mAffectedRows = null;
41 
43  private $numericVersion = null;
45  private $connectString;
47  private $mCoreSchema;
49  private $keywordTableMap = [];
50 
56  public function __construct( array $params ) {
57  $this->port = isset( $params['port'] ) ? $params['port'] : false;
58  $this->keywordTableMap = isset( $params['keywordTableMap'] )
59  ? $params['keywordTableMap']
60  : [];
61 
62  parent::__construct( $params );
63  }
64 
65  public function getType() {
66  return 'postgres';
67  }
68 
69  public function implicitGroupby() {
70  return false;
71  }
72 
73  public function implicitOrderby() {
74  return false;
75  }
76 
77  public function hasConstraint( $name ) {
78  $conn = $this->getBindingHandle();
79 
80  $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
81  "WHERE c.connamespace = n.oid AND conname = '" .
82  pg_escape_string( $conn, $name ) . "' AND n.nspname = '" .
83  pg_escape_string( $conn, $this->getCoreSchema() ) . "'";
84  $res = $this->doQuery( $sql );
85 
86  return $this->numRows( $res );
87  }
88 
89  public function open( $server, $user, $password, $dbName ) {
90  # Test for Postgres support, to avoid suppressed fatal error
91  if ( !function_exists( 'pg_connect' ) ) {
92  throw new DBConnectionError(
93  $this,
94  "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
95  "option? (Note: if you recently installed PHP, you may need to restart your\n" .
96  "webserver and database)\n"
97  );
98  }
99 
100  $this->mServer = $server;
101  $this->mUser = $user;
102  $this->mPassword = $password;
103  $this->mDBname = $dbName;
104 
105  $connectVars = [
106  // pg_connect() user $user as the default database. Since a database is *required*,
107  // at least pick a "don't care" database that is more likely to exist. This case
108  // arrises when LoadBalancer::getConnection( $i, [], '' ) is used.
109  'dbname' => strlen( $dbName ) ? $dbName : 'postgres',
110  'user' => $user,
111  'password' => $password
112  ];
113  if ( $server != false && $server != '' ) {
114  $connectVars['host'] = $server;
115  }
116  if ( (int)$this->port > 0 ) {
117  $connectVars['port'] = (int)$this->port;
118  }
119  if ( $this->mFlags & self::DBO_SSL ) {
120  $connectVars['sslmode'] = 1;
121  }
122 
123  $this->connectString = $this->makeConnectionString( $connectVars );
124  $this->close();
125  $this->installErrorHandler();
126 
127  try {
128  // Use new connections to let LoadBalancer/LBFactory handle reuse
129  $this->mConn = pg_connect( $this->connectString, PGSQL_CONNECT_FORCE_NEW );
130  } catch ( Exception $ex ) {
131  $this->restoreErrorHandler();
132  throw $ex;
133  }
134 
135  $phpError = $this->restoreErrorHandler();
136 
137  if ( !$this->mConn ) {
138  $this->queryLogger->debug(
139  "DB connection error\n" .
140  "Server: $server, Database: $dbName, User: $user, Password: " .
141  substr( $password, 0, 3 ) . "...\n"
142  );
143  $this->queryLogger->debug( $this->lastError() . "\n" );
144  throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
145  }
146 
147  $this->mOpened = true;
148 
149  # If called from the command-line (e.g. importDump), only show errors
150  if ( $this->cliMode ) {
151  $this->doQuery( "SET client_min_messages = 'ERROR'" );
152  }
153 
154  $this->query( "SET client_encoding='UTF8'", __METHOD__ );
155  $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
156  $this->query( "SET timezone = 'GMT'", __METHOD__ );
157  $this->query( "SET standard_conforming_strings = on", __METHOD__ );
158  if ( $this->getServerVersion() >= 9.0 ) {
159  $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
160  }
161 
162  $this->determineCoreSchema( $this->mSchema );
163  // The schema to be used is now in the search path; no need for explicit qualification
164  $this->mSchema = '';
165 
166  return $this->mConn;
167  }
168 
169  public function databasesAreIndependent() {
170  return true;
171  }
172 
180  public function selectDB( $db ) {
181  if ( $this->mDBname !== $db ) {
182  return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
183  } else {
184  return true;
185  }
186  }
187 
192  private function makeConnectionString( $vars ) {
193  $s = '';
194  foreach ( $vars as $name => $value ) {
195  $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
196  }
197 
198  return $s;
199  }
200 
201  protected function closeConnection() {
202  return $this->mConn ? pg_close( $this->mConn ) : true;
203  }
204 
205  public function doQuery( $sql ) {
206  $conn = $this->getBindingHandle();
207 
208  $sql = mb_convert_encoding( $sql, 'UTF-8' );
209  // Clear previously left over PQresult
210  while ( $res = pg_get_result( $conn ) ) {
211  pg_free_result( $res );
212  }
213  if ( pg_send_query( $conn, $sql ) === false ) {
214  throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
215  }
216  $this->mLastResult = pg_get_result( $conn );
217  $this->mAffectedRows = null;
218  if ( pg_result_error( $this->mLastResult ) ) {
219  return false;
220  }
221 
222  return $this->mLastResult;
223  }
224 
225  protected function dumpError() {
226  $diags = [
227  PGSQL_DIAG_SEVERITY,
228  PGSQL_DIAG_SQLSTATE,
229  PGSQL_DIAG_MESSAGE_PRIMARY,
230  PGSQL_DIAG_MESSAGE_DETAIL,
231  PGSQL_DIAG_MESSAGE_HINT,
232  PGSQL_DIAG_STATEMENT_POSITION,
233  PGSQL_DIAG_INTERNAL_POSITION,
234  PGSQL_DIAG_INTERNAL_QUERY,
235  PGSQL_DIAG_CONTEXT,
236  PGSQL_DIAG_SOURCE_FILE,
237  PGSQL_DIAG_SOURCE_LINE,
238  PGSQL_DIAG_SOURCE_FUNCTION
239  ];
240  foreach ( $diags as $d ) {
241  $this->queryLogger->debug( sprintf( "PgSQL ERROR(%d): %s\n",
242  $d, pg_result_error_field( $this->mLastResult, $d ) ) );
243  }
244  }
245 
246  public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
247  if ( $tempIgnore ) {
248  /* Check for constraint violation */
249  if ( $errno === '23505' ) {
250  parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
251 
252  return;
253  }
254  }
255  /* Transaction stays in the ERROR state until rolled back */
256  if ( $this->mTrxLevel ) {
257  // Throw away the transaction state, then raise the error as normal.
258  // Note that if this connection is managed by LBFactory, it's already expected
259  // that the other transactions LBFactory manages will be rolled back.
260  $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
261  }
262  parent::reportQueryError( $error, $errno, $sql, $fname, false );
263  }
264 
265  public function freeResult( $res ) {
266  if ( $res instanceof ResultWrapper ) {
267  $res = $res->result;
268  }
269  MediaWiki\suppressWarnings();
270  $ok = pg_free_result( $res );
271  MediaWiki\restoreWarnings();
272  if ( !$ok ) {
273  throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
274  }
275  }
276 
277  public function fetchObject( $res ) {
278  if ( $res instanceof ResultWrapper ) {
279  $res = $res->result;
280  }
281  MediaWiki\suppressWarnings();
282  $row = pg_fetch_object( $res );
283  MediaWiki\restoreWarnings();
284  # @todo FIXME: HACK HACK HACK HACK debug
285 
286  # @todo hashar: not sure if the following test really trigger if the object
287  # fetching failed.
288  $conn = $this->getBindingHandle();
289  if ( pg_last_error( $conn ) ) {
290  throw new DBUnexpectedError(
291  $this,
292  'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
293  );
294  }
295 
296  return $row;
297  }
298 
299  public function fetchRow( $res ) {
300  if ( $res instanceof ResultWrapper ) {
301  $res = $res->result;
302  }
303  MediaWiki\suppressWarnings();
304  $row = pg_fetch_array( $res );
305  MediaWiki\restoreWarnings();
306 
307  $conn = $this->getBindingHandle();
308  if ( pg_last_error( $conn ) ) {
309  throw new DBUnexpectedError(
310  $this,
311  'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
312  );
313  }
314 
315  return $row;
316  }
317 
318  public function numRows( $res ) {
319  if ( $res instanceof ResultWrapper ) {
320  $res = $res->result;
321  }
322  MediaWiki\suppressWarnings();
323  $n = pg_num_rows( $res );
324  MediaWiki\restoreWarnings();
325 
326  $conn = $this->getBindingHandle();
327  if ( pg_last_error( $conn ) ) {
328  throw new DBUnexpectedError(
329  $this,
330  'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
331  );
332  }
333 
334  return $n;
335  }
336 
337  public function numFields( $res ) {
338  if ( $res instanceof ResultWrapper ) {
339  $res = $res->result;
340  }
341 
342  return pg_num_fields( $res );
343  }
344 
345  public function fieldName( $res, $n ) {
346  if ( $res instanceof ResultWrapper ) {
347  $res = $res->result;
348  }
349 
350  return pg_field_name( $res, $n );
351  }
352 
353  public function insertId() {
354  $res = $this->query( "SELECT lastval()" );
355  $row = $this->fetchRow( $res );
356  return is_null( $row[0] ) ? null : (int)$row[0];
357  }
358 
359  public function dataSeek( $res, $row ) {
360  if ( $res instanceof ResultWrapper ) {
361  $res = $res->result;
362  }
363 
364  return pg_result_seek( $res, $row );
365  }
366 
367  public function lastError() {
368  if ( $this->mConn ) {
369  if ( $this->mLastResult ) {
370  return pg_result_error( $this->mLastResult );
371  } else {
372  return pg_last_error();
373  }
374  }
375 
376  return $this->getLastPHPError() ?: 'No database connection';
377  }
378 
379  public function lastErrno() {
380  if ( $this->mLastResult ) {
381  return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
382  } else {
383  return false;
384  }
385  }
386 
387  public function affectedRows() {
388  if ( !is_null( $this->mAffectedRows ) ) {
389  // Forced result for simulated queries
390  return $this->mAffectedRows;
391  }
392  if ( empty( $this->mLastResult ) ) {
393  return 0;
394  }
395 
396  return pg_affected_rows( $this->mLastResult );
397  }
398 
413  public function estimateRowCount( $table, $vars = '*', $conds = '',
414  $fname = __METHOD__, $options = []
415  ) {
416  $options['EXPLAIN'] = true;
417  $res = $this->select( $table, $vars, $conds, $fname, $options );
418  $rows = -1;
419  if ( $res ) {
420  $row = $this->fetchRow( $res );
421  $count = [];
422  if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
423  $rows = (int)$count[1];
424  }
425  }
426 
427  return $rows;
428  }
429 
430  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
431  $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
432  $res = $this->query( $sql, $fname );
433  if ( !$res ) {
434  return null;
435  }
436  foreach ( $res as $row ) {
437  if ( $row->indexname == $this->indexName( $index ) ) {
438  return $row;
439  }
440  }
441 
442  return false;
443  }
444 
445  public function indexAttributes( $index, $schema = false ) {
446  if ( $schema === false ) {
447  $schema = $this->getCoreSchema();
448  }
449  /*
450  * A subquery would be not needed if we didn't care about the order
451  * of attributes, but we do
452  */
453  $sql = <<<__INDEXATTR__
454 
455  SELECT opcname,
456  attname,
457  i.indoption[s.g] as option,
458  pg_am.amname
459  FROM
460  (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
461  FROM
462  pg_index isub
463  JOIN pg_class cis
464  ON cis.oid=isub.indexrelid
465  JOIN pg_namespace ns
466  ON cis.relnamespace = ns.oid
467  WHERE cis.relname='$index' AND ns.nspname='$schema') AS s,
468  pg_attribute,
469  pg_opclass opcls,
470  pg_am,
471  pg_class ci
472  JOIN pg_index i
473  ON ci.oid=i.indexrelid
474  JOIN pg_class ct
475  ON ct.oid = i.indrelid
476  JOIN pg_namespace n
477  ON ci.relnamespace = n.oid
478  WHERE
479  ci.relname='$index' AND n.nspname='$schema'
480  AND attrelid = ct.oid
481  AND i.indkey[s.g] = attnum
482  AND i.indclass[s.g] = opcls.oid
483  AND pg_am.oid = opcls.opcmethod
484 __INDEXATTR__;
485  $res = $this->query( $sql, __METHOD__ );
486  $a = [];
487  if ( $res ) {
488  foreach ( $res as $row ) {
489  $a[] = [
490  $row->attname,
491  $row->opcname,
492  $row->amname,
493  $row->option ];
494  }
495  } else {
496  return null;
497  }
498 
499  return $a;
500  }
501 
502  public function indexUnique( $table, $index, $fname = __METHOD__ ) {
503  $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
504  " AND indexdef LIKE 'CREATE UNIQUE%(" .
505  $this->strencode( $this->indexName( $index ) ) .
506  ")'";
507  $res = $this->query( $sql, $fname );
508  if ( !$res ) {
509  return null;
510  }
511 
512  return $res->numRows() > 0;
513  }
514 
515  public function selectSQLText(
516  $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
517  ) {
518  if ( is_string( $options ) ) {
519  $options = [ $options ];
520  }
521 
522  // Change the FOR UPDATE option as necessary based on the join conditions. Then pass
523  // to the parent function to get the actual SQL text.
524  // In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
525  // can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to
526  // do so causes a DB error. This wrapper checks which tables can be locked and adjusts it
527  // accordingly.
528  // MySQL uses "ORDER BY NULL" as an optimization hint, but that is illegal in PostgreSQL.
529  if ( is_array( $options ) ) {
530  $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
531  if ( $forUpdateKey !== false && $join_conds ) {
532  unset( $options[$forUpdateKey] );
533  $options['FOR UPDATE'] = [];
534 
535  // All tables not in $join_conds are good
536  foreach ( $table as $alias => $name ) {
537  if ( is_numeric( $alias ) ) {
538  $alias = $name;
539  }
540  if ( !isset( $join_conds[$alias] ) ) {
541  $options['FOR UPDATE'][] = $alias;
542  }
543  }
544 
545  foreach ( $join_conds as $table_cond => $join_cond ) {
546  if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) {
547  $options['FOR UPDATE'][] = $table_cond;
548  }
549  }
550 
551  // Quote alias names so $this->tableName() won't mangle them
552  $options['FOR UPDATE'] = array_map( function ( $name ) use ( $table ) {
553  return isset( $table[$name] ) ? $this->addIdentifierQuotes( $name ) : $name;
554  }, $options['FOR UPDATE'] );
555  }
556 
557  if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
558  unset( $options['ORDER BY'] );
559  }
560  }
561 
562  return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
563  }
564 
577  public function insert( $table, $args, $fname = __METHOD__, $options = [] ) {
578  if ( !count( $args ) ) {
579  return true;
580  }
581 
582  $table = $this->tableName( $table );
583  if ( !isset( $this->numericVersion ) ) {
584  $this->getServerVersion();
585  }
586 
587  if ( !is_array( $options ) ) {
588  $options = [ $options ];
589  }
590 
591  if ( isset( $args[0] ) && is_array( $args[0] ) ) {
592  $multi = true;
593  $keys = array_keys( $args[0] );
594  } else {
595  $multi = false;
596  $keys = array_keys( $args );
597  }
598 
599  // If IGNORE is set, we use savepoints to emulate mysql's behavior
600  $savepoint = $olde = null;
601  $numrowsinserted = 0;
602  if ( in_array( 'IGNORE', $options ) ) {
603  $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
604  $olde = error_reporting( 0 );
605  // For future use, we may want to track the number of actual inserts
606  // Right now, insert (all writes) simply return true/false
607  }
608 
609  $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
610 
611  if ( $multi ) {
612  if ( $this->numericVersion >= 8.2 && !$savepoint ) {
613  $first = true;
614  foreach ( $args as $row ) {
615  if ( $first ) {
616  $first = false;
617  } else {
618  $sql .= ',';
619  }
620  $sql .= '(' . $this->makeList( $row ) . ')';
621  }
622  $res = (bool)$this->query( $sql, $fname, $savepoint );
623  } else {
624  $res = true;
625  $origsql = $sql;
626  foreach ( $args as $row ) {
627  $tempsql = $origsql;
628  $tempsql .= '(' . $this->makeList( $row ) . ')';
629 
630  if ( $savepoint ) {
631  $savepoint->savepoint();
632  }
633 
634  $tempres = (bool)$this->query( $tempsql, $fname, $savepoint );
635 
636  if ( $savepoint ) {
637  $bar = pg_result_error( $this->mLastResult );
638  if ( $bar != false ) {
639  $savepoint->rollback();
640  } else {
641  $savepoint->release();
642  $numrowsinserted++;
643  }
644  }
645 
646  // If any of them fail, we fail overall for this function call
647  // Note that this will be ignored if IGNORE is set
648  if ( !$tempres ) {
649  $res = false;
650  }
651  }
652  }
653  } else {
654  // Not multi, just a lone insert
655  if ( $savepoint ) {
656  $savepoint->savepoint();
657  }
658 
659  $sql .= '(' . $this->makeList( $args ) . ')';
660  $res = (bool)$this->query( $sql, $fname, $savepoint );
661  if ( $savepoint ) {
662  $bar = pg_result_error( $this->mLastResult );
663  if ( $bar != false ) {
664  $savepoint->rollback();
665  } else {
666  $savepoint->release();
667  $numrowsinserted++;
668  }
669  }
670  }
671  if ( $savepoint ) {
672  error_reporting( $olde );
673  $savepoint->commit();
674 
675  // Set the affected row count for the whole operation
676  $this->mAffectedRows = $numrowsinserted;
677 
678  // IGNORE always returns true
679  return true;
680  }
681 
682  return $res;
683  }
684 
704  public function nativeInsertSelect(
705  $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
706  $insertOptions = [], $selectOptions = [], $selectJoinConds = []
707  ) {
708  if ( !is_array( $insertOptions ) ) {
709  $insertOptions = [ $insertOptions ];
710  }
711 
712  /*
713  * If IGNORE is set, we use savepoints to emulate mysql's behavior
714  * Ignore LOW PRIORITY option, since it is MySQL-specific
715  */
716  $savepoint = $olde = null;
717  $numrowsinserted = 0;
718  if ( in_array( 'IGNORE', $insertOptions ) ) {
719  $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
720  $olde = error_reporting( 0 );
721  $savepoint->savepoint();
722  }
723 
724  $res = parent::nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname,
725  $insertOptions, $selectOptions, $selectJoinConds );
726 
727  if ( $savepoint ) {
728  $bar = pg_result_error( $this->mLastResult );
729  if ( $bar != false ) {
730  $savepoint->rollback();
731  } else {
732  $savepoint->release();
733  $numrowsinserted++;
734  }
735  error_reporting( $olde );
736  $savepoint->commit();
737 
738  // Set the affected row count for the whole operation
739  $this->mAffectedRows = $numrowsinserted;
740 
741  // IGNORE always returns true
742  return true;
743  }
744 
745  return $res;
746  }
747 
748  public function tableName( $name, $format = 'quoted' ) {
749  // Replace reserved words with better ones
750  $name = $this->remappedTableName( $name );
751 
752  return parent::tableName( $name, $format );
753  }
754 
759  public function remappedTableName( $name ) {
760  return isset( $this->keywordTableMap[$name] ) ? $this->keywordTableMap[$name] : $name;
761  }
762 
768  public function realTableName( $name, $format = 'quoted' ) {
769  return parent::tableName( $name, $format );
770  }
771 
772  public function nextSequenceValue( $seqName ) {
773  return new NextSequenceValue;
774  }
775 
782  public function currentSequenceValue( $seqName ) {
783  $safeseq = str_replace( "'", "''", $seqName );
784  $res = $this->query( "SELECT currval('$safeseq')" );
785  $row = $this->fetchRow( $res );
786  $currval = $row[0];
787 
788  return $currval;
789  }
790 
791  public function textFieldSize( $table, $field ) {
792  $table = $this->tableName( $table );
793  $sql = "SELECT t.typname as ftype,a.atttypmod as size
794  FROM pg_class c, pg_attribute a, pg_type t
795  WHERE relname='$table' AND a.attrelid=c.oid AND
796  a.atttypid=t.oid and a.attname='$field'";
797  $res = $this->query( $sql );
798  $row = $this->fetchObject( $res );
799  if ( $row->ftype == 'varchar' ) {
800  $size = $row->size - 4;
801  } else {
802  $size = $row->size;
803  }
804 
805  return $size;
806  }
807 
808  public function limitResult( $sql, $limit, $offset = false ) {
809  return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
810  }
811 
812  public function wasDeadlock() {
813  return $this->lastErrno() == '40P01';
814  }
815 
816  public function duplicateTableStructure(
817  $oldName, $newName, $temporary = false, $fname = __METHOD__
818  ) {
819  $newName = $this->addIdentifierQuotes( $newName );
820  $oldName = $this->addIdentifierQuotes( $oldName );
821 
822  return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " .
823  "(LIKE $oldName INCLUDING DEFAULTS INCLUDING INDEXES)", $fname );
824  }
825 
826  public function listTables( $prefix = null, $fname = __METHOD__ ) {
827  $eschema = $this->addQuotes( $this->getCoreSchema() );
828  $result = $this->query(
829  "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
830  $endArray = [];
831 
832  foreach ( $result as $table ) {
833  $vars = get_object_vars( $table );
834  $table = array_pop( $vars );
835  if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
836  $endArray[] = $table;
837  }
838  }
839 
840  return $endArray;
841  }
842 
843  public function timestamp( $ts = 0 ) {
844  $ct = new ConvertibleTimestamp( $ts );
845 
846  return $ct->getTimestamp( TS_POSTGRES );
847  }
848 
867  private function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
868  if ( false === $limit ) {
869  $limit = strlen( $text ) - 1;
870  $output = [];
871  }
872  if ( '{}' == $text ) {
873  return $output;
874  }
875  do {
876  if ( '{' != $text[$offset] ) {
877  preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
878  $text, $match, 0, $offset );
879  $offset += strlen( $match[0] );
880  $output[] = ( '"' != $match[1][0]
881  ? $match[1]
882  : stripcslashes( substr( $match[1], 1, -1 ) ) );
883  if ( '},' == $match[3] ) {
884  return $output;
885  }
886  } else {
887  $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
888  }
889  } while ( $limit > $offset );
890 
891  return $output;
892  }
893 
894  public function aggregateValue( $valuedata, $valuename = 'value' ) {
895  return $valuedata;
896  }
897 
898  public function getSoftwareLink() {
899  return '[{{int:version-db-postgres-url}} PostgreSQL]';
900  }
901 
909  public function getCurrentSchema() {
910  $res = $this->query( "SELECT current_schema()", __METHOD__ );
911  $row = $this->fetchRow( $res );
912 
913  return $row[0];
914  }
915 
926  public function getSchemas() {
927  $res = $this->query( "SELECT current_schemas(false)", __METHOD__ );
928  $row = $this->fetchRow( $res );
929  $schemas = [];
930 
931  /* PHP pgsql support does not support array type, "{a,b}" string is returned */
932 
933  return $this->pg_array_parse( $row[0], $schemas );
934  }
935 
945  public function getSearchPath() {
946  $res = $this->query( "SHOW search_path", __METHOD__ );
947  $row = $this->fetchRow( $res );
948 
949  /* PostgreSQL returns SHOW values as strings */
950 
951  return explode( ",", $row[0] );
952  }
953 
961  private function setSearchPath( $search_path ) {
962  $this->query( "SET search_path = " . implode( ", ", $search_path ) );
963  }
964 
979  public function determineCoreSchema( $desiredSchema ) {
980  $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
981  if ( $this->schemaExists( $desiredSchema ) ) {
982  if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
983  $this->mCoreSchema = $desiredSchema;
984  $this->queryLogger->debug(
985  "Schema \"" . $desiredSchema . "\" already in the search path\n" );
986  } else {
992  $search_path = $this->getSearchPath();
993  array_unshift( $search_path,
994  $this->addIdentifierQuotes( $desiredSchema ) );
995  $this->setSearchPath( $search_path );
996  $this->mCoreSchema = $desiredSchema;
997  $this->queryLogger->debug(
998  "Schema \"" . $desiredSchema . "\" added to the search path\n" );
999  }
1000  } else {
1001  $this->mCoreSchema = $this->getCurrentSchema();
1002  $this->queryLogger->debug(
1003  "Schema \"" . $desiredSchema . "\" not found, using current \"" .
1004  $this->mCoreSchema . "\"\n" );
1005  }
1006  /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
1007  $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
1008  }
1009 
1016  public function getCoreSchema() {
1017  return $this->mCoreSchema;
1018  }
1019 
1020  public function getServerVersion() {
1021  if ( !isset( $this->numericVersion ) ) {
1022  $conn = $this->getBindingHandle();
1023  $versionInfo = pg_version( $conn );
1024  if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
1025  // Old client, abort install
1026  $this->numericVersion = '7.3 or earlier';
1027  } elseif ( isset( $versionInfo['server'] ) ) {
1028  // Normal client
1029  $this->numericVersion = $versionInfo['server'];
1030  } else {
1031  // T18937: broken pgsql extension from PHP<5.3
1032  $this->numericVersion = pg_parameter_status( $conn, 'server_version' );
1033  }
1034  }
1035 
1036  return $this->numericVersion;
1037  }
1038 
1047  private function relationExists( $table, $types, $schema = false ) {
1048  if ( !is_array( $types ) ) {
1049  $types = [ $types ];
1050  }
1051  if ( $schema === false ) {
1052  $schema = $this->getCoreSchema();
1053  }
1054  $table = $this->realTableName( $table, 'raw' );
1055  $etable = $this->addQuotes( $table );
1056  $eschema = $this->addQuotes( $schema );
1057  $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
1058  . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
1059  . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
1060  $res = $this->query( $sql );
1061  $count = $res ? $res->numRows() : 0;
1062 
1063  return (bool)$count;
1064  }
1065 
1073  public function tableExists( $table, $fname = __METHOD__, $schema = false ) {
1074  return $this->relationExists( $table, [ 'r', 'v' ], $schema );
1075  }
1076 
1077  public function sequenceExists( $sequence, $schema = false ) {
1078  return $this->relationExists( $sequence, 'S', $schema );
1079  }
1080 
1081  public function triggerExists( $table, $trigger ) {
1082  $q = <<<SQL
1083  SELECT 1 FROM pg_class, pg_namespace, pg_trigger
1084  WHERE relnamespace=pg_namespace.oid AND relkind='r'
1085  AND tgrelid=pg_class.oid
1086  AND nspname=%s AND relname=%s AND tgname=%s
1087 SQL;
1088  $res = $this->query(
1089  sprintf(
1090  $q,
1091  $this->addQuotes( $this->getCoreSchema() ),
1092  $this->addQuotes( $table ),
1093  $this->addQuotes( $trigger )
1094  )
1095  );
1096  if ( !$res ) {
1097  return null;
1098  }
1099  $rows = $res->numRows();
1100 
1101  return $rows;
1102  }
1103 
1104  public function ruleExists( $table, $rule ) {
1105  $exists = $this->selectField( 'pg_rules', 'rulename',
1106  [
1107  'rulename' => $rule,
1108  'tablename' => $table,
1109  'schemaname' => $this->getCoreSchema()
1110  ]
1111  );
1112 
1113  return $exists === $rule;
1114  }
1115 
1116  public function constraintExists( $table, $constraint ) {
1117  $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
1118  "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
1119  $this->addQuotes( $this->getCoreSchema() ),
1120  $this->addQuotes( $table ),
1121  $this->addQuotes( $constraint )
1122  );
1123  $res = $this->query( $sql );
1124  if ( !$res ) {
1125  return null;
1126  }
1127  $rows = $res->numRows();
1128 
1129  return $rows;
1130  }
1131 
1137  public function schemaExists( $schema ) {
1138  if ( !strlen( $schema ) ) {
1139  return false; // short-circuit
1140  }
1141 
1142  $exists = $this->selectField(
1143  '"pg_catalog"."pg_namespace"', 1, [ 'nspname' => $schema ], __METHOD__ );
1144 
1145  return (bool)$exists;
1146  }
1147 
1153  public function roleExists( $roleName ) {
1154  $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
1155  [ 'rolname' => $roleName ], __METHOD__ );
1156 
1157  return (bool)$exists;
1158  }
1159 
1165  public function fieldInfo( $table, $field ) {
1166  return PostgresField::fromText( $this, $table, $field );
1167  }
1168 
1175  public function fieldType( $res, $index ) {
1176  if ( $res instanceof ResultWrapper ) {
1177  $res = $res->result;
1178  }
1179 
1180  return pg_field_type( $res, $index );
1181  }
1182 
1183  public function encodeBlob( $b ) {
1184  return new PostgresBlob( pg_escape_bytea( $b ) );
1185  }
1186 
1187  public function decodeBlob( $b ) {
1188  if ( $b instanceof PostgresBlob ) {
1189  $b = $b->fetch();
1190  } elseif ( $b instanceof Blob ) {
1191  return $b->fetch();
1192  }
1193 
1194  return pg_unescape_bytea( $b );
1195  }
1196 
1197  public function strencode( $s ) {
1198  // Should not be called by us
1199  return pg_escape_string( $this->getBindingHandle(), (string)$s );
1200  }
1201 
1202  public function addQuotes( $s ) {
1203  $conn = $this->getBindingHandle();
1204 
1205  if ( is_null( $s ) ) {
1206  return 'NULL';
1207  } elseif ( is_bool( $s ) ) {
1208  return intval( $s );
1209  } elseif ( $s instanceof Blob ) {
1210  if ( $s instanceof PostgresBlob ) {
1211  $s = $s->fetch();
1212  } else {
1213  $s = pg_escape_bytea( $conn, $s->fetch() );
1214  }
1215  return "'$s'";
1216  } elseif ( $s instanceof NextSequenceValue ) {
1217  return 'DEFAULT';
1218  }
1219 
1220  return "'" . pg_escape_string( $conn, (string)$s ) . "'";
1221  }
1222 
1230  protected function replaceVars( $ins ) {
1231  $ins = parent::replaceVars( $ins );
1232 
1233  if ( $this->numericVersion >= 8.3 ) {
1234  // Thanks for not providing backwards-compatibility, 8.3
1235  $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
1236  }
1237 
1238  if ( $this->numericVersion <= 8.1 ) { // Our minimum version
1239  $ins = str_replace( 'USING gin', 'USING gist', $ins );
1240  }
1241 
1242  return $ins;
1243  }
1244 
1245  public function makeSelectOptions( $options ) {
1246  $preLimitTail = $postLimitTail = '';
1247  $startOpts = $useIndex = $ignoreIndex = '';
1248 
1249  $noKeyOptions = [];
1250  foreach ( $options as $key => $option ) {
1251  if ( is_numeric( $key ) ) {
1252  $noKeyOptions[$option] = true;
1253  }
1254  }
1255 
1256  $preLimitTail .= $this->makeGroupByWithHaving( $options );
1257 
1258  $preLimitTail .= $this->makeOrderBy( $options );
1259 
1260  if ( isset( $options['FOR UPDATE'] ) ) {
1261  $postLimitTail .= ' FOR UPDATE OF ' .
1262  implode( ', ', array_map( [ $this, 'tableName' ], $options['FOR UPDATE'] ) );
1263  } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1264  $postLimitTail .= ' FOR UPDATE';
1265  }
1266 
1267  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1268  $startOpts .= 'DISTINCT';
1269  }
1270 
1271  return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1272  }
1273 
1274  public function getDBname() {
1275  return $this->mDBname;
1276  }
1277 
1278  public function getServer() {
1279  return $this->mServer;
1280  }
1281 
1282  public function buildConcat( $stringList ) {
1283  return implode( ' || ', $stringList );
1284  }
1285 
1286  public function buildGroupConcatField(
1287  $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
1288  ) {
1289  $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
1290 
1291  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1292  }
1293 
1294  public function buildStringCast( $field ) {
1295  return $field . '::text';
1296  }
1297 
1298  public function streamStatementEnd( &$sql, &$newLine ) {
1299  # Allow dollar quoting for function declarations
1300  if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
1301  if ( $this->delimiter ) {
1302  $this->delimiter = false;
1303  } else {
1304  $this->delimiter = ';';
1305  }
1306  }
1307 
1308  return parent::streamStatementEnd( $sql, $newLine );
1309  }
1310 
1311  public function doLockTables( array $read, array $write, $method ) {
1312  $tablesWrite = [];
1313  foreach ( $write as $table ) {
1314  $tablesWrite[] = $this->tableName( $table );
1315  }
1316  $tablesRead = [];
1317  foreach ( $read as $table ) {
1318  $tablesRead[] = $this->tableName( $table );
1319  }
1320 
1321  // Acquire locks for the duration of the current transaction...
1322  if ( $tablesWrite ) {
1323  $this->query(
1324  'LOCK TABLE ONLY ' . implode( ',', $tablesWrite ) . ' IN EXCLUSIVE MODE',
1325  $method
1326  );
1327  }
1328  if ( $tablesRead ) {
1329  $this->query(
1330  'LOCK TABLE ONLY ' . implode( ',', $tablesRead ) . ' IN SHARE MODE',
1331  $method
1332  );
1333  }
1334 
1335  return true;
1336  }
1337 
1338  public function lockIsFree( $lockName, $method ) {
1339  // http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1340  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1341  $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
1342  WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
1343  $row = $this->fetchObject( $result );
1344 
1345  return ( $row->lockstatus === 't' );
1346  }
1347 
1348  public function lock( $lockName, $method, $timeout = 5 ) {
1349  // http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1350  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1351  $loop = new WaitConditionLoop(
1352  function () use ( $lockName, $key, $timeout, $method ) {
1353  $res = $this->query( "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
1354  $row = $this->fetchObject( $res );
1355  if ( $row->lockstatus === 't' ) {
1356  parent::lock( $lockName, $method, $timeout ); // record
1357  return true;
1358  }
1359 
1360  return WaitConditionLoop::CONDITION_CONTINUE;
1361  },
1362  $timeout
1363  );
1364 
1365  return ( $loop->invoke() === $loop::CONDITION_REACHED );
1366  }
1367 
1368  public function unlock( $lockName, $method ) {
1369  // http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1370  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1371  $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
1372  $row = $this->fetchObject( $result );
1373 
1374  if ( $row->lockstatus === 't' ) {
1375  parent::unlock( $lockName, $method ); // record
1376  return true;
1377  }
1378 
1379  $this->queryLogger->debug( __METHOD__ . " failed to release lock\n" );
1380 
1381  return false;
1382  }
1383 
1384  public function serverIsReadOnly() {
1385  $res = $this->query( "SHOW default_transaction_read_only", __METHOD__ );
1386  $row = $this->fetchObject( $res );
1387 
1388  return $row ? ( strtolower( $row->default_transaction_read_only ) === 'on' ) : false;
1389  }
1390 
1395  private function bigintFromLockName( $lockName ) {
1396  return \Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
1397  }
1398 }
1399 
1400 class_alias( DatabasePostgres::class, 'DatabasePostgres' );
Wikimedia\Rdbms\DatabasePostgres\freeResult
freeResult( $res)
Free a result object returned by query() or select().
Definition: DatabasePostgres.php:265
Wikimedia\Rdbms\Database\getLastPHPError
getLastPHPError()
Definition: Database.php:714
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:45
Wikimedia\Rdbms\DatabasePostgres\doLockTables
doLockTables(array $read, array $write, $method)
Helper function for lockTables() that handles the actual table locking.
Definition: DatabasePostgres.php:1311
Wikimedia\Rdbms\DatabasePostgres\numFields
numFields( $res)
Get the number of fields in a result object.
Definition: DatabasePostgres.php:337
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:244
Wikimedia\Rdbms\DatabasePostgres\bigintFromLockName
bigintFromLockName( $lockName)
Definition: DatabasePostgres.php:1395
Wikimedia\Rdbms\Database\getBindingHandle
getBindingHandle()
Get the underlying binding handle, mConn.
Definition: Database.php:3619
Wikimedia\Rdbms\DatabasePostgres\tableName
tableName( $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.
Definition: DatabasePostgres.php:748
Wikimedia\Rdbms\DatabasePostgres\getSoftwareLink
getSoftwareLink()
Returns a wikitext link to the DB's website, e.g., return "[https://www.mysql.com/ MySQL]"; Should at...
Definition: DatabasePostgres.php:898
Wikimedia\Rdbms\DatabasePostgres\closeConnection
closeConnection()
Closes underlying database connection.
Definition: DatabasePostgres.php:201
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:1047
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:926
Wikimedia\Rdbms\DatabasePostgres\implicitGroupby
implicitGroupby()
Returns true if this database does an implicit sort when doing GROUP BY.
Definition: DatabasePostgres.php:69
Wikimedia\Rdbms\Database\commit
commit( $fname=__METHOD__, $flush='')
Commits a transaction previously started using begin().
Definition: Database.php:2932
Wikimedia\Rdbms\Database\$mConn
resource null $mConn
Database connection.
Definition: Database.php:92
Wikimedia\Rdbms\DatabasePostgres\$mLastResult
resource $mLastResult
Definition: DatabasePostgres.php:38
Wikimedia\Rdbms\DatabasePostgres\remappedTableName
remappedTableName( $name)
Definition: DatabasePostgres.php:759
Wikimedia\Rdbms\DatabasePostgres\addQuotes
addQuotes( $s)
Adds quotes and backslashes.
Definition: DatabasePostgres.php:1202
Wikimedia\Rdbms\DatabasePostgres\$connectString
string $connectString
Connect string to open a PostgreSQL connection.
Definition: DatabasePostgres.php:45
Wikimedia\Rdbms\DatabasePostgres\fieldInfo
fieldInfo( $table, $field)
Definition: DatabasePostgres.php:1165
captcha-old.count
count
Definition: captcha-old.py:249
Wikimedia\Rdbms\DatabasePostgres\unlock
unlock( $lockName, $method)
Release a lock.
Definition: DatabasePostgres.php:1368
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1963
Wikimedia\Rdbms\Database\$delimiter
string $delimiter
Definition: Database.php:126
Wikimedia\Rdbms\Database\indexName
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
Definition: Database.php:2060
Wikimedia\Rdbms\DatabasePostgres\fieldType
fieldType( $res, $index)
pg_field_type() wrapper
Definition: DatabasePostgres.php:1175
FROM
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to and or sell copies of the and to permit persons to whom the Software is furnished to do subject to the following WITHOUT WARRANTY OF ANY EXPRESS OR INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DAMAGES OR OTHER WHETHER IN AN ACTION OF TORT OR ARISING FROM
Definition: MIT-LICENSE.txt:10
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
Wikimedia\Rdbms\DatabasePostgres\$numericVersion
float string $numericVersion
Definition: DatabasePostgres.php:43
Wikimedia\Rdbms
Definition: ChronologyProtector.php:24
$fname
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition: Setup.php:36
$params
$params
Definition: styleTest.css.php:40
$s
$s
Definition: mergeMessageFileList.php:188
DBO_SSL
const DBO_SSL
Definition: defines.php:17
Wikimedia\Rdbms\DatabasePostgres
Definition: DatabasePostgres.php:33
$res
$res
Definition: database.txt:21
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
Wikimedia\Rdbms\ResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: ResultWrapper.php:24
Wikimedia\Rdbms\DatabasePostgres\makeSelectOptions
makeSelectOptions( $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:1245
Wikimedia\Rdbms\DatabasePostgres\textFieldSize
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited".
Definition: DatabasePostgres.php:791
Wikimedia\Rdbms\DatabasePostgres\indexUnique
indexUnique( $table, $index, $fname=__METHOD__)
Definition: DatabasePostgres.php:502
$output
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set but before ordinary actions take place $output
Definition: hooks.txt:2198
Wikimedia\Rdbms\DatabasePostgres\insertId
insertId()
Get the inserted value of an auto-increment row.
Definition: DatabasePostgres.php:353
Wikimedia\Rdbms\DatabasePostgres\fetchObject
fetchObject( $res)
Fetch the next row from the given result object, in object form.
Definition: DatabasePostgres.php:277
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
Wikimedia\Rdbms\DatabasePostgres\aggregateValue
aggregateValue( $valuedata, $valuename='value')
Return aggregated value alias.
Definition: DatabasePostgres.php:894
Wikimedia\Rdbms\DatabasePostgres\roleExists
roleExists( $roleName)
Returns true if a given role (i.e.
Definition: DatabasePostgres.php:1153
Wikimedia\Rdbms\DatabasePostgres\nativeInsertSelect
nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[], $selectJoinConds=[])
INSERT SELECT wrapper $varMap must be an associative array of the form [ 'dest1' => 'source1',...
Definition: DatabasePostgres.php:704
Wikimedia\Rdbms\DatabasePostgres\insert
insert( $table, $args, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
Definition: DatabasePostgres.php:577
Wikimedia\Rdbms\Database\begin
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
Definition: Database.php:2871
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:1183
Wikimedia\Rdbms\DatabasePostgres\makeConnectionString
makeConnectionString( $vars)
Definition: DatabasePostgres.php:192
Wikimedia\Rdbms\DatabasePostgres\lockIsFree
lockIsFree( $lockName, $method)
Check to see if a named lock is available (non-blocking)
Definition: DatabasePostgres.php:1338
Wikimedia\Rdbms\Database\rollback
rollback( $fname=__METHOD__, $flush='')
Rollback a transaction previously started using begin().
Definition: Database.php:2998
Wikimedia\Rdbms\DatabasePostgres\schemaExists
schemaExists( $schema)
Query whether a given schema exists.
Definition: DatabasePostgres.php:1137
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:73
Wikimedia\Rdbms\DatabasePostgres\limitResult
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.
Definition: DatabasePostgres.php:808
tableName
We use the convention $dbr for read and $dbw for write to help you keep track of whether the database object is a the world will explode Or to be a subsequent write query which succeeded on the master may fail when replicated to the slave due to a unique key collision Replication on the slave will stop and it may take hours to repair the database and get it back online Setting read_only in my cnf on the slave will avoid this but given the dire we prefer to have as many checks as possible We provide a but the wrapper functions like please read the documentation for tableName() and addQuotes(). You will need both of them. ------------------------------------------------------------------------ Basic query optimisation ------------------------------------------------------------------------ MediaWiki developers who need to write DB queries should have some understanding of databases and the performance issues associated with them. Patches containing unacceptably slow features will not be accepted. Unindexed queries are generally not welcome in MediaWiki
Wikimedia\Rdbms\DatabasePostgres\duplicateTableStructure
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Creates a new table with structure copied from existing table.
Definition: DatabasePostgres.php:816
Wikimedia\Rdbms\PostgresField\fromText
static fromText(DatabasePostgres $db, $table, $field)
Definition: PostgresField.php:15
Wikimedia\Rdbms\DatabasePostgres\doQuery
doQuery( $sql)
The DBMS-dependent part of query()
Definition: DatabasePostgres.php:205
Wikimedia\Rdbms\DatabasePostgres\getDBname
getDBname()
Get the current DB name.
Definition: DatabasePostgres.php:1274
Wikimedia\Rdbms\DatabasePostgres\buildStringCast
buildStringCast( $field)
Definition: DatabasePostgres.php:1294
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://secure.php.net/manual/en/ref....
Definition: DatabasePostgres.php:867
Wikimedia\Rdbms\DatabasePostgres\lastErrno
lastErrno()
Get the last error number.
Definition: DatabasePostgres.php:379
MediaWiki
This document describes the state of Postgres support in MediaWiki
Definition: postgres.txt:4
$vars
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2198
Wikimedia\Rdbms\DatabasePostgres\selectSQLText
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
The equivalent of IDatabase::select() except that the constructed SQL is returned,...
Definition: DatabasePostgres.php:515
Wikimedia\Rdbms\DatabasePostgres\numRows
numRows( $res)
Get the number of rows in a result object.
Definition: DatabasePostgres.php:318
port
storage can be distributed across multiple and multiple web servers can use the same cache cluster *********************W A R N I N G ***********************Memcached has no security or authentication Please ensure that your server is appropriately and that the port(s) used for memcached servers are not publicly accessible. Otherwise
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:1197
Wikimedia\Rdbms\SavepointPostgres
Manage savepoints within a transaction.
Definition: SavepointPostgres.php:31
Wikimedia\Rdbms\Database\installErrorHandler
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition: Database.php:691
Wikimedia\Rdbms\Database\restoreErrorHandler
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition: Database.php:702
Wikimedia\Rdbms\Database\$mServer
string $mServer
Definition: Database.php:68
Wikimedia\Rdbms\DatabasePostgres\$mAffectedRows
int $mAffectedRows
The number of rows affected as an integer.
Definition: DatabasePostgres.php:40
Wikimedia\Rdbms\DatabasePostgres\serverIsReadOnly
serverIsReadOnly()
Definition: DatabasePostgres.php:1384
$value
$value
Definition: styleTest.css.php:45
Wikimedia\Rdbms\Database\$mDBname
string $mDBname
Definition: Database.php:74
Wikimedia\Rdbms\DatabasePostgres\affectedRows
affectedRows()
Get the number of rows affected by the last write query.
Definition: DatabasePostgres.php:387
Wikimedia\Rdbms\DatabasePostgres\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: DatabasePostgres.php:843
Wikimedia\Rdbms\DatabasePostgres\getServer
getServer()
Get the server hostname or IP address.
Definition: DatabasePostgres.php:1278
Wikimedia\Rdbms\DatabasePostgres\decodeBlob
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects.
Definition: DatabasePostgres.php:1187
Wikimedia\Rdbms\Database\query
query( $sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition: Database.php:888
Wikimedia\Rdbms\DatabasePostgres\getCurrentSchema
getCurrentSchema()
Return current schema (executes SELECT current_schema()) Needs transaction.
Definition: DatabasePostgres.php:909
Wikimedia\Rdbms\DBUnexpectedError
Definition: DBUnexpectedError.php:27
Wikimedia\Rdbms\DatabasePostgres\realTableName
realTableName( $name, $format='quoted')
Definition: DatabasePostgres.php:768
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:1156
Wikimedia\Rdbms\DatabasePostgres\nextSequenceValue
nextSequenceValue( $seqName)
Deprecated method, calls should be removed.
Definition: DatabasePostgres.php:772
Wikimedia\Rdbms\DatabasePostgres\buildConcat
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.
Definition: DatabasePostgres.php:1282
Wikimedia\Rdbms\Database\makeList
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Definition: Database.php:1636
Wikimedia\Rdbms\DatabasePostgres\getType
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
Definition: DatabasePostgres.php:65
Wikimedia\Rdbms\Database\addIdentifierQuotes
addIdentifierQuotes( $s)
Quotes an identifier using backticks or "double quotes" depending on the database type.
Definition: Database.php:2090
Wikimedia\Rdbms\DatabasePostgres\getServerVersion
getServerVersion()
A string describing the current software version, like from mysql_get_server_info().
Definition: DatabasePostgres.php:1020
$args
if( $line===false) $args
Definition: cdb.php:63
Wikimedia\Rdbms\Database\close
close()
Closes a database connection.
Definition: Database.php:753
Wikimedia\Rdbms\DatabasePostgres\lastError
lastError()
Get a description of the last error.
Definition: DatabasePostgres.php:367
$rows
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2581
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1965
Wikimedia\Rdbms\DatabasePostgres\__construct
__construct(array $params)
Definition: DatabasePostgres.php:56
Wikimedia\Rdbms\DatabasePostgres\estimateRowCount
estimateRowCount( $table, $vars=' *', $conds='', $fname=__METHOD__, $options=[])
Estimate rows in dataset Returns estimated count, based on EXPLAIN output This is not necessarily an ...
Definition: DatabasePostgres.php:413
Wikimedia\Rdbms\DatabasePostgres\constraintExists
constraintExists( $table, $constraint)
Definition: DatabasePostgres.php:1116
Wikimedia\Rdbms\DatabasePostgres\$mCoreSchema
string $mCoreSchema
Definition: DatabasePostgres.php:47
Wikimedia\Rdbms\DatabasePostgres\streamStatementEnd
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
Definition: DatabasePostgres.php:1298
Wikimedia\Rdbms\Database\makeGroupByWithHaving
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1301
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
Wikimedia\Rdbms\DatabasePostgres\open
open( $server, $user, $password, $dbName)
Open a connection to the database.
Definition: DatabasePostgres.php:89
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
Wikimedia\Rdbms\DatabasePostgres\sequenceExists
sequenceExists( $sequence, $schema=false)
Definition: DatabasePostgres.php:1077
$keys
$keys
Definition: testCompression.php:65
Wikimedia\Rdbms\DatabasePostgres\tableExists
tableExists( $table, $fname=__METHOD__, $schema=false)
For backward compatibility, this function checks both tables and views.
Definition: DatabasePostgres.php:1073
Wikimedia\Rdbms\DatabasePostgres\$keywordTableMap
string[] $keywordTableMap
Map of (reserved table name => alternate table name)
Definition: DatabasePostgres.php:49
Wikimedia\Rdbms\DatabasePostgres\buildGroupConcatField
buildGroupConcatField( $delimiter, $table, $field, $conds='', $options=[], $join_conds=[])
Definition: DatabasePostgres.php:1286
Wikimedia\Rdbms\Database\makeOrderBy
makeOrderBy( $options)
Returns an optional ORDER BY.
Definition: Database.php:1327
Wikimedia\Rdbms\DatabasePostgres\dataSeek
dataSeek( $res, $row)
Change the position of the cursor in a result object.
Definition: DatabasePostgres.php:359
Wikimedia\Rdbms\DatabasePostgres\ruleExists
ruleExists( $table, $rule)
Definition: DatabasePostgres.php:1104
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:1339
Wikimedia\Rdbms\DatabasePostgres\fetchRow
fetchRow( $res)
Fetch the next row from the given result object, in associative array form.
Definition: DatabasePostgres.php:299
Wikimedia\Rdbms\DatabasePostgres\fieldName
fieldName( $res, $n)
Get a field name in a result object.
Definition: DatabasePostgres.php:345
Wikimedia\Rdbms\DatabasePostgres\selectDB
selectDB( $db)
Postgres doesn't support selectDB in the same way MySQL does.
Definition: DatabasePostgres.php:180
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
Wikimedia\Rdbms\DatabasePostgres\dumpError
dumpError()
Definition: DatabasePostgres.php:225
Wikimedia\Rdbms\DatabasePostgres\determineCoreSchema
determineCoreSchema( $desiredSchema)
Determine default schema for the current application Adjust this session schema search path if desire...
Definition: DatabasePostgres.php:979
Wikimedia\Rdbms\DatabasePostgres\getSearchPath
getSearchPath()
Return search patch for schemas This is different from getSchemas() since it contain magic keywords (...
Definition: DatabasePostgres.php:945
Wikimedia\Rdbms\DatabasePostgres\getCoreSchema
getCoreSchema()
Return schema name for core application tables.
Definition: DatabasePostgres.php:1016
Wikimedia\Rdbms\DatabasePostgres\reportQueryError
reportQueryError( $error, $errno, $sql, $fname, $tempIgnore=false)
Report a query error.
Definition: DatabasePostgres.php:246
Wikimedia\Rdbms\DatabasePostgres\indexInfo
indexInfo( $table, $index, $fname=__METHOD__)
Get information about an index into an object.
Definition: DatabasePostgres.php:430
Wikimedia\Rdbms\DatabasePostgres\triggerExists
triggerExists( $table, $trigger)
Definition: DatabasePostgres.php:1081
Wikimedia\Rdbms\DatabasePostgres\currentSequenceValue
currentSequenceValue( $seqName)
Return the current value of a sequence.
Definition: DatabasePostgres.php:782
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:961
Wikimedia\Rdbms\DatabasePostgres\replaceVars
replaceVars( $ins)
Postgres specific version of replaceVars.
Definition: DatabasePostgres.php:1230
Wikimedia\Rdbms\DatabasePostgres\wasDeadlock
wasDeadlock()
Determines if the last failure was due to a deadlock.
Definition: DatabasePostgres.php:812
Wikimedia\Rdbms\PostgresBlob
Definition: PostgresBlob.php:5
Wikimedia\Rdbms\DatabasePostgres\lock
lock( $lockName, $method, $timeout=5)
Acquire a named lock.
Definition: DatabasePostgres.php:1348
Wikimedia\Rdbms\DatabasePostgres\hasConstraint
hasConstraint( $name)
Definition: DatabasePostgres.php:77
Wikimedia\Rdbms\DatabasePostgres\listTables
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
Definition: DatabasePostgres.php:826
array
the array() calling protocol came about after MediaWiki 1.4rc1.
Wikimedia\Rdbms\DatabasePostgres\databasesAreIndependent
databasesAreIndependent()
Returns true if DBs are assumed to be on potentially different servers.
Definition: DatabasePostgres.php:169
n
print Searching for spam in $maxID pages n
Definition: cleanup.php:99
Wikimedia\Rdbms\DatabasePostgres\$port
int bool $port
Definition: DatabasePostgres.php:35
Wikimedia\Rdbms\DatabasePostgres\indexAttributes
indexAttributes( $index, $schema=false)
Definition: DatabasePostgres.php:445
Wikimedia\Rdbms\Blob
Definition: Blob.php:5