MediaWiki  1.29.2
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 $mInsertId = null;
45  private $numericVersion = null;
47  private $connectString;
49  private $mCoreSchema;
51  private $keywordTableMap = [];
52 
58  public function __construct( array $params ) {
59  $this->port = isset( $params['port'] ) ? $params['port'] : false;
60  $this->keywordTableMap = isset( $params['keywordTableMap'] )
61  ? $params['keywordTableMap']
62  : [];
63 
64  parent::__construct( $params );
65  }
66 
67  public function getType() {
68  return 'postgres';
69  }
70 
71  public function implicitGroupby() {
72  return false;
73  }
74 
75  public function implicitOrderby() {
76  return false;
77  }
78 
79  public function hasConstraint( $name ) {
80  $conn = $this->getBindingHandle();
81 
82  $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
83  "WHERE c.connamespace = n.oid AND conname = '" .
84  pg_escape_string( $conn, $name ) . "' AND n.nspname = '" .
85  pg_escape_string( $conn, $this->getCoreSchema() ) . "'";
86  $res = $this->doQuery( $sql );
87 
88  return $this->numRows( $res );
89  }
90 
91  public function open( $server, $user, $password, $dbName ) {
92  # Test for Postgres support, to avoid suppressed fatal error
93  if ( !function_exists( 'pg_connect' ) ) {
94  throw new DBConnectionError(
95  $this,
96  "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
97  "option? (Note: if you recently installed PHP, you may need to restart your\n" .
98  "webserver and database)\n"
99  );
100  }
101 
102  $this->mServer = $server;
103  $this->mUser = $user;
104  $this->mPassword = $password;
105  $this->mDBname = $dbName;
106 
107  $connectVars = [
108  'dbname' => $dbName,
109  'user' => $user,
110  'password' => $password
111  ];
112  if ( $server != false && $server != '' ) {
113  $connectVars['host'] = $server;
114  }
115  if ( (int)$this->port > 0 ) {
116  $connectVars['port'] = (int)$this->port;
117  }
118  if ( $this->mFlags & self::DBO_SSL ) {
119  $connectVars['sslmode'] = 1;
120  }
121 
122  $this->connectString = $this->makeConnectionString( $connectVars );
123  $this->close();
124  $this->installErrorHandler();
125 
126  try {
127  // Use new connections to let LoadBalancer/LBFactory handle reuse
128  $this->mConn = pg_connect( $this->connectString, PGSQL_CONNECT_FORCE_NEW );
129  } catch ( Exception $ex ) {
130  $this->restoreErrorHandler();
131  throw $ex;
132  }
133 
134  $phpError = $this->restoreErrorHandler();
135 
136  if ( !$this->mConn ) {
137  $this->queryLogger->debug(
138  "DB connection error\n" .
139  "Server: $server, Database: $dbName, User: $user, Password: " .
140  substr( $password, 0, 3 ) . "...\n"
141  );
142  $this->queryLogger->debug( $this->lastError() . "\n" );
143  throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
144  }
145 
146  $this->mOpened = true;
147 
148  # If called from the command-line (e.g. importDump), only show errors
149  if ( $this->cliMode ) {
150  $this->doQuery( "SET client_min_messages = 'ERROR'" );
151  }
152 
153  $this->query( "SET client_encoding='UTF8'", __METHOD__ );
154  $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
155  $this->query( "SET timezone = 'GMT'", __METHOD__ );
156  $this->query( "SET standard_conforming_strings = on", __METHOD__ );
157  if ( $this->getServerVersion() >= 9.0 ) {
158  $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
159  }
160 
161  $this->determineCoreSchema( $this->mSchema );
162  // The schema to be used is now in the search path; no need for explicit qualification
163  $this->mSchema = '';
164 
165  return $this->mConn;
166  }
167 
174  public function selectDB( $db ) {
175  if ( $this->mDBname !== $db ) {
176  return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
177  } else {
178  return true;
179  }
180  }
181 
186  private function makeConnectionString( $vars ) {
187  $s = '';
188  foreach ( $vars as $name => $value ) {
189  $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
190  }
191 
192  return $s;
193  }
194 
195  protected function closeConnection() {
196  return $this->mConn ? pg_close( $this->mConn ) : true;
197  }
198 
199  public function doQuery( $sql ) {
200  $conn = $this->getBindingHandle();
201 
202  $sql = mb_convert_encoding( $sql, 'UTF-8' );
203  // Clear previously left over PQresult
204  while ( $res = pg_get_result( $conn ) ) {
205  pg_free_result( $res );
206  }
207  if ( pg_send_query( $conn, $sql ) === false ) {
208  throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
209  }
210  $this->mLastResult = pg_get_result( $conn );
211  $this->mAffectedRows = null;
212  if ( pg_result_error( $this->mLastResult ) ) {
213  return false;
214  }
215 
216  return $this->mLastResult;
217  }
218 
219  protected function dumpError() {
220  $diags = [
221  PGSQL_DIAG_SEVERITY,
222  PGSQL_DIAG_SQLSTATE,
223  PGSQL_DIAG_MESSAGE_PRIMARY,
224  PGSQL_DIAG_MESSAGE_DETAIL,
225  PGSQL_DIAG_MESSAGE_HINT,
226  PGSQL_DIAG_STATEMENT_POSITION,
227  PGSQL_DIAG_INTERNAL_POSITION,
228  PGSQL_DIAG_INTERNAL_QUERY,
229  PGSQL_DIAG_CONTEXT,
230  PGSQL_DIAG_SOURCE_FILE,
231  PGSQL_DIAG_SOURCE_LINE,
232  PGSQL_DIAG_SOURCE_FUNCTION
233  ];
234  foreach ( $diags as $d ) {
235  $this->queryLogger->debug( sprintf( "PgSQL ERROR(%d): %s\n",
236  $d, pg_result_error_field( $this->mLastResult, $d ) ) );
237  }
238  }
239 
240  public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
241  if ( $tempIgnore ) {
242  /* Check for constraint violation */
243  if ( $errno === '23505' ) {
244  parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
245 
246  return;
247  }
248  }
249  /* Transaction stays in the ERROR state until rolled back */
250  if ( $this->mTrxLevel ) {
251  // Throw away the transaction state, then raise the error as normal.
252  // Note that if this connection is managed by LBFactory, it's already expected
253  // that the other transactions LBFactory manages will be rolled back.
254  $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
255  }
256  parent::reportQueryError( $error, $errno, $sql, $fname, false );
257  }
258 
259  public function freeResult( $res ) {
260  if ( $res instanceof ResultWrapper ) {
261  $res = $res->result;
262  }
263  MediaWiki\suppressWarnings();
264  $ok = pg_free_result( $res );
265  MediaWiki\restoreWarnings();
266  if ( !$ok ) {
267  throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
268  }
269  }
270 
271  public function fetchObject( $res ) {
272  if ( $res instanceof ResultWrapper ) {
273  $res = $res->result;
274  }
275  MediaWiki\suppressWarnings();
276  $row = pg_fetch_object( $res );
277  MediaWiki\restoreWarnings();
278  # @todo FIXME: HACK HACK HACK HACK debug
279 
280  # @todo hashar: not sure if the following test really trigger if the object
281  # fetching failed.
282  $conn = $this->getBindingHandle();
283  if ( pg_last_error( $conn ) ) {
284  throw new DBUnexpectedError(
285  $this,
286  'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
287  );
288  }
289 
290  return $row;
291  }
292 
293  public function fetchRow( $res ) {
294  if ( $res instanceof ResultWrapper ) {
295  $res = $res->result;
296  }
297  MediaWiki\suppressWarnings();
298  $row = pg_fetch_array( $res );
299  MediaWiki\restoreWarnings();
300 
301  $conn = $this->getBindingHandle();
302  if ( pg_last_error( $conn ) ) {
303  throw new DBUnexpectedError(
304  $this,
305  'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
306  );
307  }
308 
309  return $row;
310  }
311 
312  public function numRows( $res ) {
313  if ( $res instanceof ResultWrapper ) {
314  $res = $res->result;
315  }
316  MediaWiki\suppressWarnings();
317  $n = pg_num_rows( $res );
318  MediaWiki\restoreWarnings();
319 
320  $conn = $this->getBindingHandle();
321  if ( pg_last_error( $conn ) ) {
322  throw new DBUnexpectedError(
323  $this,
324  'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
325  );
326  }
327 
328  return $n;
329  }
330 
331  public function numFields( $res ) {
332  if ( $res instanceof ResultWrapper ) {
333  $res = $res->result;
334  }
335 
336  return pg_num_fields( $res );
337  }
338 
339  public function fieldName( $res, $n ) {
340  if ( $res instanceof ResultWrapper ) {
341  $res = $res->result;
342  }
343 
344  return pg_field_name( $res, $n );
345  }
346 
353  public function insertId() {
354  return $this->mInsertId;
355  }
356 
357  public function dataSeek( $res, $row ) {
358  if ( $res instanceof ResultWrapper ) {
359  $res = $res->result;
360  }
361 
362  return pg_result_seek( $res, $row );
363  }
364 
365  public function lastError() {
366  if ( $this->mConn ) {
367  if ( $this->mLastResult ) {
368  return pg_result_error( $this->mLastResult );
369  } else {
370  return pg_last_error();
371  }
372  }
373 
374  return $this->getLastPHPError() ?: 'No database connection';
375  }
376 
377  public function lastErrno() {
378  if ( $this->mLastResult ) {
379  return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
380  } else {
381  return false;
382  }
383  }
384 
385  public function affectedRows() {
386  if ( !is_null( $this->mAffectedRows ) ) {
387  // Forced result for simulated queries
388  return $this->mAffectedRows;
389  }
390  if ( empty( $this->mLastResult ) ) {
391  return 0;
392  }
393 
394  return pg_affected_rows( $this->mLastResult );
395  }
396 
411  public function estimateRowCount( $table, $vars = '*', $conds = '',
412  $fname = __METHOD__, $options = []
413  ) {
414  $options['EXPLAIN'] = true;
415  $res = $this->select( $table, $vars, $conds, $fname, $options );
416  $rows = -1;
417  if ( $res ) {
418  $row = $this->fetchRow( $res );
419  $count = [];
420  if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
421  $rows = (int)$count[1];
422  }
423  }
424 
425  return $rows;
426  }
427 
428  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
429  $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
430  $res = $this->query( $sql, $fname );
431  if ( !$res ) {
432  return null;
433  }
434  foreach ( $res as $row ) {
435  if ( $row->indexname == $this->indexName( $index ) ) {
436  return $row;
437  }
438  }
439 
440  return false;
441  }
442 
443  public function indexAttributes( $index, $schema = false ) {
444  if ( $schema === false ) {
445  $schema = $this->getCoreSchema();
446  }
447  /*
448  * A subquery would be not needed if we didn't care about the order
449  * of attributes, but we do
450  */
451  $sql = <<<__INDEXATTR__
452 
453  SELECT opcname,
454  attname,
455  i.indoption[s.g] as option,
456  pg_am.amname
457  FROM
458  (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
459  FROM
460  pg_index isub
461  JOIN pg_class cis
462  ON cis.oid=isub.indexrelid
463  JOIN pg_namespace ns
464  ON cis.relnamespace = ns.oid
465  WHERE cis.relname='$index' AND ns.nspname='$schema') AS s,
466  pg_attribute,
467  pg_opclass opcls,
468  pg_am,
469  pg_class ci
470  JOIN pg_index i
471  ON ci.oid=i.indexrelid
472  JOIN pg_class ct
473  ON ct.oid = i.indrelid
474  JOIN pg_namespace n
475  ON ci.relnamespace = n.oid
476  WHERE
477  ci.relname='$index' AND n.nspname='$schema'
478  AND attrelid = ct.oid
479  AND i.indkey[s.g] = attnum
480  AND i.indclass[s.g] = opcls.oid
481  AND pg_am.oid = opcls.opcmethod
482 __INDEXATTR__;
483  $res = $this->query( $sql, __METHOD__ );
484  $a = [];
485  if ( $res ) {
486  foreach ( $res as $row ) {
487  $a[] = [
488  $row->attname,
489  $row->opcname,
490  $row->amname,
491  $row->option ];
492  }
493  } else {
494  return null;
495  }
496 
497  return $a;
498  }
499 
500  public function indexUnique( $table, $index, $fname = __METHOD__ ) {
501  $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
502  " AND indexdef LIKE 'CREATE UNIQUE%(" .
503  $this->strencode( $this->indexName( $index ) ) .
504  ")'";
505  $res = $this->query( $sql, $fname );
506  if ( !$res ) {
507  return null;
508  }
509 
510  return $res->numRows() > 0;
511  }
512 
513  public function selectSQLText(
514  $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
515  ) {
516  // Change the FOR UPDATE option as necessary based on the join conditions. Then pass
517  // to the parent function to get the actual SQL text.
518  // In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
519  // can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to
520  // do so causes a DB error. This wrapper checks which tables can be locked and adjusts it
521  // accordingly.
522  // MySQL uses "ORDER BY NULL" as an optimization hint, but that is illegal in PostgreSQL.
523  if ( is_array( $options ) ) {
524  $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
525  if ( $forUpdateKey !== false && $join_conds ) {
526  unset( $options[$forUpdateKey] );
527 
528  foreach ( $join_conds as $table_cond => $join_cond ) {
529  if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) {
530  $options['FOR UPDATE'][] = $table_cond;
531  }
532  }
533  }
534 
535  if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
536  unset( $options['ORDER BY'] );
537  }
538  }
539 
540  return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
541  }
542 
555  public function insert( $table, $args, $fname = __METHOD__, $options = [] ) {
556  if ( !count( $args ) ) {
557  return true;
558  }
559 
560  $table = $this->tableName( $table );
561  if ( !isset( $this->numericVersion ) ) {
562  $this->getServerVersion();
563  }
564 
565  if ( !is_array( $options ) ) {
566  $options = [ $options ];
567  }
568 
569  if ( isset( $args[0] ) && is_array( $args[0] ) ) {
570  $multi = true;
571  $keys = array_keys( $args[0] );
572  } else {
573  $multi = false;
574  $keys = array_keys( $args );
575  }
576 
577  // If IGNORE is set, we use savepoints to emulate mysql's behavior
578  $savepoint = $olde = null;
579  $numrowsinserted = 0;
580  if ( in_array( 'IGNORE', $options ) ) {
581  $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
582  $olde = error_reporting( 0 );
583  // For future use, we may want to track the number of actual inserts
584  // Right now, insert (all writes) simply return true/false
585  }
586 
587  $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
588 
589  if ( $multi ) {
590  if ( $this->numericVersion >= 8.2 && !$savepoint ) {
591  $first = true;
592  foreach ( $args as $row ) {
593  if ( $first ) {
594  $first = false;
595  } else {
596  $sql .= ',';
597  }
598  $sql .= '(' . $this->makeList( $row ) . ')';
599  }
600  $res = (bool)$this->query( $sql, $fname, $savepoint );
601  } else {
602  $res = true;
603  $origsql = $sql;
604  foreach ( $args as $row ) {
605  $tempsql = $origsql;
606  $tempsql .= '(' . $this->makeList( $row ) . ')';
607 
608  if ( $savepoint ) {
609  $savepoint->savepoint();
610  }
611 
612  $tempres = (bool)$this->query( $tempsql, $fname, $savepoint );
613 
614  if ( $savepoint ) {
615  $bar = pg_result_error( $this->mLastResult );
616  if ( $bar != false ) {
617  $savepoint->rollback();
618  } else {
619  $savepoint->release();
620  $numrowsinserted++;
621  }
622  }
623 
624  // If any of them fail, we fail overall for this function call
625  // Note that this will be ignored if IGNORE is set
626  if ( !$tempres ) {
627  $res = false;
628  }
629  }
630  }
631  } else {
632  // Not multi, just a lone insert
633  if ( $savepoint ) {
634  $savepoint->savepoint();
635  }
636 
637  $sql .= '(' . $this->makeList( $args ) . ')';
638  $res = (bool)$this->query( $sql, $fname, $savepoint );
639  if ( $savepoint ) {
640  $bar = pg_result_error( $this->mLastResult );
641  if ( $bar != false ) {
642  $savepoint->rollback();
643  } else {
644  $savepoint->release();
645  $numrowsinserted++;
646  }
647  }
648  }
649  if ( $savepoint ) {
650  error_reporting( $olde );
651  $savepoint->commit();
652 
653  // Set the affected row count for the whole operation
654  $this->mAffectedRows = $numrowsinserted;
655 
656  // IGNORE always returns true
657  return true;
658  }
659 
660  return $res;
661  }
662 
681  public function nativeInsertSelect(
682  $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
683  $insertOptions = [], $selectOptions = []
684  ) {
685  $destTable = $this->tableName( $destTable );
686 
687  if ( !is_array( $insertOptions ) ) {
688  $insertOptions = [ $insertOptions ];
689  }
690 
691  /*
692  * If IGNORE is set, we use savepoints to emulate mysql's behavior
693  * Ignore LOW PRIORITY option, since it is MySQL-specific
694  */
695  $savepoint = $olde = null;
696  $numrowsinserted = 0;
697  if ( in_array( 'IGNORE', $insertOptions ) ) {
698  $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
699  $olde = error_reporting( 0 );
700  $savepoint->savepoint();
701  }
702 
703  if ( !is_array( $selectOptions ) ) {
704  $selectOptions = [ $selectOptions ];
705  }
706  list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) =
707  $this->makeSelectOptions( $selectOptions );
708  if ( is_array( $srcTable ) ) {
709  $srcTable = implode( ',', array_map( [ $this, 'tableName' ], $srcTable ) );
710  } else {
711  $srcTable = $this->tableName( $srcTable );
712  }
713 
714  $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
715  " SELECT $startOpts " . implode( ',', $varMap ) .
716  " FROM $srcTable $useIndex $ignoreIndex ";
717 
718  if ( $conds != '*' ) {
719  $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
720  }
721 
722  $sql .= " $tailOpts";
723 
724  $res = (bool)$this->query( $sql, $fname, $savepoint );
725  if ( $savepoint ) {
726  $bar = pg_result_error( $this->mLastResult );
727  if ( $bar != false ) {
728  $savepoint->rollback();
729  } else {
730  $savepoint->release();
731  $numrowsinserted++;
732  }
733  error_reporting( $olde );
734  $savepoint->commit();
735 
736  // Set the affected row count for the whole operation
737  $this->mAffectedRows = $numrowsinserted;
738 
739  // IGNORE always returns true
740  return true;
741  }
742 
743  return $res;
744  }
745 
746  public function tableName( $name, $format = 'quoted' ) {
747  // Replace reserved words with better ones
748  $name = $this->remappedTableName( $name );
749 
750  return parent::tableName( $name, $format );
751  }
752 
757  public function remappedTableName( $name ) {
758  return isset( $this->keywordTableMap[$name] ) ? $this->keywordTableMap[$name] : $name;
759  }
760 
766  public function realTableName( $name, $format = 'quoted' ) {
767  return parent::tableName( $name, $format );
768  }
769 
770  public function nextSequenceValue( $seqName ) {
771  $safeseq = str_replace( "'", "''", $seqName );
772  $res = $this->query( "SELECT nextval('$safeseq')" );
773  $row = $this->fetchRow( $res );
774  $this->mInsertId = $row[0];
775 
776  return $this->mInsertId;
777  }
778 
785  public function currentSequenceValue( $seqName ) {
786  $safeseq = str_replace( "'", "''", $seqName );
787  $res = $this->query( "SELECT currval('$safeseq')" );
788  $row = $this->fetchRow( $res );
789  $currval = $row[0];
790 
791  return $currval;
792  }
793 
794  public function textFieldSize( $table, $field ) {
795  $table = $this->tableName( $table );
796  $sql = "SELECT t.typname as ftype,a.atttypmod as size
797  FROM pg_class c, pg_attribute a, pg_type t
798  WHERE relname='$table' AND a.attrelid=c.oid AND
799  a.atttypid=t.oid and a.attname='$field'";
800  $res = $this->query( $sql );
801  $row = $this->fetchObject( $res );
802  if ( $row->ftype == 'varchar' ) {
803  $size = $row->size - 4;
804  } else {
805  $size = $row->size;
806  }
807 
808  return $size;
809  }
810 
811  public function limitResult( $sql, $limit, $offset = false ) {
812  return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
813  }
814 
815  public function wasDeadlock() {
816  return $this->lastErrno() == '40P01';
817  }
818 
819  public function duplicateTableStructure(
820  $oldName, $newName, $temporary = false, $fname = __METHOD__
821  ) {
822  $newName = $this->addIdentifierQuotes( $newName );
823  $oldName = $this->addIdentifierQuotes( $oldName );
824 
825  return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " .
826  "(LIKE $oldName INCLUDING DEFAULTS)", $fname );
827  }
828 
829  public function listTables( $prefix = null, $fname = __METHOD__ ) {
830  $eschema = $this->addQuotes( $this->getCoreSchema() );
831  $result = $this->query(
832  "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
833  $endArray = [];
834 
835  foreach ( $result as $table ) {
836  $vars = get_object_vars( $table );
837  $table = array_pop( $vars );
838  if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
839  $endArray[] = $table;
840  }
841  }
842 
843  return $endArray;
844  }
845 
846  public function timestamp( $ts = 0 ) {
847  $ct = new ConvertibleTimestamp( $ts );
848 
849  return $ct->getTimestamp( TS_POSTGRES );
850  }
851 
870  private function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
871  if ( false === $limit ) {
872  $limit = strlen( $text ) - 1;
873  $output = [];
874  }
875  if ( '{}' == $text ) {
876  return $output;
877  }
878  do {
879  if ( '{' != $text[$offset] ) {
880  preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
881  $text, $match, 0, $offset );
882  $offset += strlen( $match[0] );
883  $output[] = ( '"' != $match[1][0]
884  ? $match[1]
885  : stripcslashes( substr( $match[1], 1, -1 ) ) );
886  if ( '},' == $match[3] ) {
887  return $output;
888  }
889  } else {
890  $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
891  }
892  } while ( $limit > $offset );
893 
894  return $output;
895  }
896 
897  public function aggregateValue( $valuedata, $valuename = 'value' ) {
898  return $valuedata;
899  }
900 
901  public function getSoftwareLink() {
902  return '[{{int:version-db-postgres-url}} PostgreSQL]';
903  }
904 
912  public function getCurrentSchema() {
913  $res = $this->query( "SELECT current_schema()", __METHOD__ );
914  $row = $this->fetchRow( $res );
915 
916  return $row[0];
917  }
918 
929  public function getSchemas() {
930  $res = $this->query( "SELECT current_schemas(false)", __METHOD__ );
931  $row = $this->fetchRow( $res );
932  $schemas = [];
933 
934  /* PHP pgsql support does not support array type, "{a,b}" string is returned */
935 
936  return $this->pg_array_parse( $row[0], $schemas );
937  }
938 
948  public function getSearchPath() {
949  $res = $this->query( "SHOW search_path", __METHOD__ );
950  $row = $this->fetchRow( $res );
951 
952  /* PostgreSQL returns SHOW values as strings */
953 
954  return explode( ",", $row[0] );
955  }
956 
964  private function setSearchPath( $search_path ) {
965  $this->query( "SET search_path = " . implode( ", ", $search_path ) );
966  }
967 
982  public function determineCoreSchema( $desiredSchema ) {
983  $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
984  if ( $this->schemaExists( $desiredSchema ) ) {
985  if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
986  $this->mCoreSchema = $desiredSchema;
987  $this->queryLogger->debug(
988  "Schema \"" . $desiredSchema . "\" already in the search path\n" );
989  } else {
995  $search_path = $this->getSearchPath();
996  array_unshift( $search_path,
997  $this->addIdentifierQuotes( $desiredSchema ) );
998  $this->setSearchPath( $search_path );
999  $this->mCoreSchema = $desiredSchema;
1000  $this->queryLogger->debug(
1001  "Schema \"" . $desiredSchema . "\" added to the search path\n" );
1002  }
1003  } else {
1004  $this->mCoreSchema = $this->getCurrentSchema();
1005  $this->queryLogger->debug(
1006  "Schema \"" . $desiredSchema . "\" not found, using current \"" .
1007  $this->mCoreSchema . "\"\n" );
1008  }
1009  /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
1010  $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
1011  }
1012 
1019  public function getCoreSchema() {
1020  return $this->mCoreSchema;
1021  }
1022 
1023  public function getServerVersion() {
1024  if ( !isset( $this->numericVersion ) ) {
1025  $conn = $this->getBindingHandle();
1026  $versionInfo = pg_version( $conn );
1027  if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
1028  // Old client, abort install
1029  $this->numericVersion = '7.3 or earlier';
1030  } elseif ( isset( $versionInfo['server'] ) ) {
1031  // Normal client
1032  $this->numericVersion = $versionInfo['server'];
1033  } else {
1034  // T18937: broken pgsql extension from PHP<5.3
1035  $this->numericVersion = pg_parameter_status( $conn, 'server_version' );
1036  }
1037  }
1038 
1039  return $this->numericVersion;
1040  }
1041 
1050  private function relationExists( $table, $types, $schema = false ) {
1051  if ( !is_array( $types ) ) {
1052  $types = [ $types ];
1053  }
1054  if ( $schema === false ) {
1055  $schema = $this->getCoreSchema();
1056  }
1057  $table = $this->realTableName( $table, 'raw' );
1058  $etable = $this->addQuotes( $table );
1059  $eschema = $this->addQuotes( $schema );
1060  $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
1061  . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
1062  . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
1063  $res = $this->query( $sql );
1064  $count = $res ? $res->numRows() : 0;
1065 
1066  return (bool)$count;
1067  }
1068 
1076  public function tableExists( $table, $fname = __METHOD__, $schema = false ) {
1077  return $this->relationExists( $table, [ 'r', 'v' ], $schema );
1078  }
1079 
1080  public function sequenceExists( $sequence, $schema = false ) {
1081  return $this->relationExists( $sequence, 'S', $schema );
1082  }
1083 
1084  public function triggerExists( $table, $trigger ) {
1085  $q = <<<SQL
1086  SELECT 1 FROM pg_class, pg_namespace, pg_trigger
1087  WHERE relnamespace=pg_namespace.oid AND relkind='r'
1088  AND tgrelid=pg_class.oid
1089  AND nspname=%s AND relname=%s AND tgname=%s
1090 SQL;
1091  $res = $this->query(
1092  sprintf(
1093  $q,
1094  $this->addQuotes( $this->getCoreSchema() ),
1095  $this->addQuotes( $table ),
1096  $this->addQuotes( $trigger )
1097  )
1098  );
1099  if ( !$res ) {
1100  return null;
1101  }
1102  $rows = $res->numRows();
1103 
1104  return $rows;
1105  }
1106 
1107  public function ruleExists( $table, $rule ) {
1108  $exists = $this->selectField( 'pg_rules', 'rulename',
1109  [
1110  'rulename' => $rule,
1111  'tablename' => $table,
1112  'schemaname' => $this->getCoreSchema()
1113  ]
1114  );
1115 
1116  return $exists === $rule;
1117  }
1118 
1119  public function constraintExists( $table, $constraint ) {
1120  $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
1121  "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
1122  $this->addQuotes( $this->getCoreSchema() ),
1123  $this->addQuotes( $table ),
1124  $this->addQuotes( $constraint )
1125  );
1126  $res = $this->query( $sql );
1127  if ( !$res ) {
1128  return null;
1129  }
1130  $rows = $res->numRows();
1131 
1132  return $rows;
1133  }
1134 
1140  public function schemaExists( $schema ) {
1141  if ( !strlen( $schema ) ) {
1142  return false; // short-circuit
1143  }
1144 
1145  $exists = $this->selectField(
1146  '"pg_catalog"."pg_namespace"', 1, [ 'nspname' => $schema ], __METHOD__ );
1147 
1148  return (bool)$exists;
1149  }
1150 
1156  public function roleExists( $roleName ) {
1157  $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
1158  [ 'rolname' => $roleName ], __METHOD__ );
1159 
1160  return (bool)$exists;
1161  }
1162 
1168  public function fieldInfo( $table, $field ) {
1169  return PostgresField::fromText( $this, $table, $field );
1170  }
1171 
1178  public function fieldType( $res, $index ) {
1179  if ( $res instanceof ResultWrapper ) {
1180  $res = $res->result;
1181  }
1182 
1183  return pg_field_type( $res, $index );
1184  }
1185 
1186  public function encodeBlob( $b ) {
1187  return new PostgresBlob( pg_escape_bytea( $b ) );
1188  }
1189 
1190  public function decodeBlob( $b ) {
1191  if ( $b instanceof PostgresBlob ) {
1192  $b = $b->fetch();
1193  } elseif ( $b instanceof Blob ) {
1194  return $b->fetch();
1195  }
1196 
1197  return pg_unescape_bytea( $b );
1198  }
1199 
1200  public function strencode( $s ) {
1201  // Should not be called by us
1202  return pg_escape_string( $this->getBindingHandle(), (string)$s );
1203  }
1204 
1205  public function addQuotes( $s ) {
1206  $conn = $this->getBindingHandle();
1207 
1208  if ( is_null( $s ) ) {
1209  return 'NULL';
1210  } elseif ( is_bool( $s ) ) {
1211  return intval( $s );
1212  } elseif ( $s instanceof Blob ) {
1213  if ( $s instanceof PostgresBlob ) {
1214  $s = $s->fetch();
1215  } else {
1216  $s = pg_escape_bytea( $conn, $s->fetch() );
1217  }
1218  return "'$s'";
1219  }
1220 
1221  return "'" . pg_escape_string( $conn, (string)$s ) . "'";
1222  }
1223 
1231  protected function replaceVars( $ins ) {
1232  $ins = parent::replaceVars( $ins );
1233 
1234  if ( $this->numericVersion >= 8.3 ) {
1235  // Thanks for not providing backwards-compatibility, 8.3
1236  $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
1237  }
1238 
1239  if ( $this->numericVersion <= 8.1 ) { // Our minimum version
1240  $ins = str_replace( 'USING gin', 'USING gist', $ins );
1241  }
1242 
1243  return $ins;
1244  }
1245 
1246  public function makeSelectOptions( $options ) {
1247  $preLimitTail = $postLimitTail = '';
1248  $startOpts = $useIndex = $ignoreIndex = '';
1249 
1250  $noKeyOptions = [];
1251  foreach ( $options as $key => $option ) {
1252  if ( is_numeric( $key ) ) {
1253  $noKeyOptions[$option] = true;
1254  }
1255  }
1256 
1257  $preLimitTail .= $this->makeGroupByWithHaving( $options );
1258 
1259  $preLimitTail .= $this->makeOrderBy( $options );
1260 
1261  if ( isset( $options['FOR UPDATE'] ) ) {
1262  $postLimitTail .= ' FOR UPDATE OF ' .
1263  implode( ', ', array_map( [ $this, 'tableName' ], $options['FOR UPDATE'] ) );
1264  } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1265  $postLimitTail .= ' FOR UPDATE';
1266  }
1267 
1268  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1269  $startOpts .= 'DISTINCT';
1270  }
1271 
1272  return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1273  }
1274 
1275  public function getDBname() {
1276  return $this->mDBname;
1277  }
1278 
1279  public function getServer() {
1280  return $this->mServer;
1281  }
1282 
1283  public function buildConcat( $stringList ) {
1284  return implode( ' || ', $stringList );
1285  }
1286 
1287  public function buildGroupConcatField(
1288  $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
1289  ) {
1290  $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
1291 
1292  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1293  }
1294 
1295  public function buildStringCast( $field ) {
1296  return $field . '::text';
1297  }
1298 
1299  public function streamStatementEnd( &$sql, &$newLine ) {
1300  # Allow dollar quoting for function declarations
1301  if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
1302  if ( $this->delimiter ) {
1303  $this->delimiter = false;
1304  } else {
1305  $this->delimiter = ';';
1306  }
1307  }
1308 
1309  return parent::streamStatementEnd( $sql, $newLine );
1310  }
1311 
1312  public function doLockTables( array $read, array $write, $method ) {
1313  $tablesWrite = [];
1314  foreach ( $write as $table ) {
1315  $tablesWrite[] = $this->tableName( $table );
1316  }
1317  $tablesRead = [];
1318  foreach ( $read as $table ) {
1319  $tablesRead[] = $this->tableName( $table );
1320  }
1321 
1322  // Acquire locks for the duration of the current transaction...
1323  if ( $tablesWrite ) {
1324  $this->query(
1325  'LOCK TABLE ONLY ' . implode( ',', $tablesWrite ) . ' IN EXCLUSIVE MODE',
1326  $method
1327  );
1328  }
1329  if ( $tablesRead ) {
1330  $this->query(
1331  'LOCK TABLE ONLY ' . implode( ',', $tablesRead ) . ' IN SHARE MODE',
1332  $method
1333  );
1334  }
1335 
1336  return true;
1337  }
1338 
1339  public function lockIsFree( $lockName, $method ) {
1340  // http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1341  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1342  $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
1343  WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
1344  $row = $this->fetchObject( $result );
1345 
1346  return ( $row->lockstatus === 't' );
1347  }
1348 
1349  public function lock( $lockName, $method, $timeout = 5 ) {
1350  // http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1351  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1352  $loop = new WaitConditionLoop(
1353  function () use ( $lockName, $key, $timeout, $method ) {
1354  $res = $this->query( "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
1355  $row = $this->fetchObject( $res );
1356  if ( $row->lockstatus === 't' ) {
1357  parent::lock( $lockName, $method, $timeout ); // record
1358  return true;
1359  }
1360 
1361  return WaitConditionLoop::CONDITION_CONTINUE;
1362  },
1363  $timeout
1364  );
1365 
1366  return ( $loop->invoke() === $loop::CONDITION_REACHED );
1367  }
1368 
1369  public function unlock( $lockName, $method ) {
1370  // http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1371  $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1372  $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
1373  $row = $this->fetchObject( $result );
1374 
1375  if ( $row->lockstatus === 't' ) {
1376  parent::unlock( $lockName, $method ); // record
1377  return true;
1378  }
1379 
1380  $this->queryLogger->debug( __METHOD__ . " failed to release lock\n" );
1381 
1382  return false;
1383  }
1384 
1389  private function bigintFromLockName( $lockName ) {
1390  return \Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
1391  }
1392 }
1393 
1394 class_alias( DatabasePostgres::class, 'DatabasePostgres' );
Wikimedia\Rdbms\DatabasePostgres\freeResult
freeResult( $res)
Free a result object returned by query() or select().
Definition: DatabasePostgres.php:259
Wikimedia\Rdbms\Database\getLastPHPError
getLastPHPError()
Definition: Database.php:688
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:45
Wikimedia\Rdbms\DatabasePostgres\doLockTables
doLockTables(array $read, array $write, $method)
Definition: DatabasePostgres.php:1312
Wikimedia\Rdbms\DatabasePostgres\numFields
numFields( $res)
Get the number of fields in a result object.
Definition: DatabasePostgres.php:331
Wikimedia\Rdbms\DatabasePostgres\bigintFromLockName
bigintFromLockName( $lockName)
Definition: DatabasePostgres.php:1389
Wikimedia\Rdbms\Database\getBindingHandle
getBindingHandle()
Get the underlying binding handle, mConn.
Definition: Database.php:3426
Wikimedia\Rdbms\DatabasePostgres\tableName
tableName( $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.
Definition: DatabasePostgres.php:746
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:901
Wikimedia\Rdbms\DatabasePostgres\closeConnection
closeConnection()
Closes underlying database connection.
Definition: DatabasePostgres.php:195
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:1050
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:929
Wikimedia\Rdbms\DatabasePostgres\implicitGroupby
implicitGroupby()
Returns true if this database does an implicit sort when doing GROUP BY.
Definition: DatabasePostgres.php:71
Wikimedia\Rdbms\Database\commit
commit( $fname=__METHOD__, $flush='')
Commits a transaction previously started using begin().
Definition: Database.php:2763
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:757
Wikimedia\Rdbms\DatabasePostgres\addQuotes
addQuotes( $s)
Adds quotes and backslashes.
Definition: DatabasePostgres.php:1205
Wikimedia\Rdbms\DatabasePostgres\$connectString
string $connectString
Connect string to open a PostgreSQL connection.
Definition: DatabasePostgres.php:47
Wikimedia\Rdbms\DatabasePostgres\fieldInfo
fieldInfo( $table, $field)
mysql_fetch_field() wrapper Returns false if the field doesn't exist
Definition: DatabasePostgres.php:1168
captcha-old.count
count
Definition: captcha-old.py:225
Wikimedia\Rdbms\DatabasePostgres\unlock
unlock( $lockName, $method)
Release a lock.
Definition: DatabasePostgres.php:1369
$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:1954
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:1971
Wikimedia\Rdbms\DatabasePostgres\$mInsertId
int $mInsertId
Definition: DatabasePostgres.php:43
Wikimedia\Rdbms\DatabasePostgres\fieldType
fieldType( $res, $index)
pg_field_type() wrapper
Definition: DatabasePostgres.php:1178
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\Database\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[])
A SELECT wrapper which returns a single field from a single result row.
Definition: Database.php:1082
$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:246
Wikimedia\Rdbms\DatabasePostgres\$numericVersion
float string $numericVersion
Definition: DatabasePostgres.php:45
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:304
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:1246
Wikimedia\Rdbms\DatabasePostgres\textFieldSize
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited".
Definition: DatabasePostgres.php:794
Wikimedia\Rdbms\DatabasePostgres\indexUnique
indexUnique( $table, $index, $fname=__METHOD__)
Definition: DatabasePostgres.php:500
Wikimedia\Rdbms\DatabasePostgres\insertId
insertId()
Return the result of the last call to nextSequenceValue(); This must be called after nextSequenceValu...
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:271
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:897
LIST_AND
const LIST_AND
Definition: Defines.php:41
Wikimedia\Rdbms\DatabasePostgres\roleExists
roleExists( $roleName)
Returns true if a given role (i.e.
Definition: DatabasePostgres.php:1156
Wikimedia\Rdbms\DatabasePostgres\insert
insert( $table, $args, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
Definition: DatabasePostgres.php:555
Wikimedia\Rdbms\Database\begin
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
Definition: Database.php:2703
Wikimedia\Rdbms\DatabasePostgres\nativeInsertSelect
nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[])
INSERT SELECT wrapper $varMap must be an associative array of the form [ 'dest1' => 'source1',...
Definition: DatabasePostgres.php:681
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:1186
Wikimedia\Rdbms\DatabasePostgres\makeConnectionString
makeConnectionString( $vars)
Definition: DatabasePostgres.php:186
Wikimedia\Rdbms\DatabasePostgres\lockIsFree
lockIsFree( $lockName, $method)
Check to see if a named lock is available (non-blocking)
Definition: DatabasePostgres.php:1339
Wikimedia\Rdbms\Database\rollback
rollback( $fname=__METHOD__, $flush='')
Rollback a transaction previously started using begin().
Definition: Database.php:2824
Wikimedia\Rdbms\DatabasePostgres\schemaExists
schemaExists( $schema)
Query whether a given schema exists.
Definition: DatabasePostgres.php:1140
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:75
Wikimedia\Rdbms\DatabasePostgres\limitResult
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.
Definition: DatabasePostgres.php:811
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:819
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:199
Wikimedia\Rdbms\DatabasePostgres\getDBname
getDBname()
Get the current DB name.
Definition: DatabasePostgres.php:1275
Wikimedia\Rdbms\DatabasePostgres\buildStringCast
buildStringCast( $field)
Definition: DatabasePostgres.php:1295
$limit
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers please use GetContentModels hook to make them known to core if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive $limit
Definition: hooks.txt:1049
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:870
Wikimedia\Rdbms\DatabasePostgres\lastErrno
lastErrno()
Get the last error number.
Definition: DatabasePostgres.php:377
MediaWiki
This document describes the state of Postgres support in MediaWiki
Definition: postgres.txt:4
$output
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object & $output
Definition: hooks.txt:1049
$vars
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2179
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:513
Wikimedia\Rdbms\DatabasePostgres\numRows
numRows( $res)
Get the number of rows in a result object.
Definition: DatabasePostgres.php:312
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
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
Wikimedia\Rdbms\DatabasePostgres\strencode
strencode( $s)
Wrapper for addslashes()
Definition: DatabasePostgres.php:1200
Wikimedia\Rdbms\SavepointPostgres
Manage savepoints within a transaction.
Definition: SavepointPostgres.php:31
Wikimedia\Rdbms\Database\installErrorHandler
installErrorHandler()
Definition: Database.php:667
Wikimedia\Rdbms\Database\restoreErrorHandler
restoreErrorHandler()
Definition: Database.php:676
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
$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:385
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:846
Wikimedia\Rdbms\DatabasePostgres\getServer
getServer()
Get the server hostname or IP address.
Definition: DatabasePostgres.php:1279
Wikimedia\Rdbms\DatabasePostgres\decodeBlob
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects.
Definition: DatabasePostgres.php:1190
Wikimedia\Rdbms\Database\query
query( $sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition: Database.php:850
Wikimedia\Rdbms\DatabasePostgres\getCurrentSchema
getCurrentSchema()
Return current schema (executes SELECT current_schema()) Needs transaction.
Definition: DatabasePostgres.php:912
Wikimedia\Rdbms\DBUnexpectedError
Definition: DBUnexpectedError.php:27
Wikimedia\Rdbms\DatabasePostgres\realTableName
realTableName( $name, $format='quoted')
Definition: DatabasePostgres.php:766
Wikimedia\Rdbms\DatabasePostgres\nextSequenceValue
nextSequenceValue( $seqName)
Returns an appropriately quoted sequence value for inserting a new row.
Definition: DatabasePostgres.php:770
Wikimedia\Rdbms\DatabasePostgres\buildConcat
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.
Definition: DatabasePostgres.php:1283
Wikimedia\Rdbms\Database\makeList
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Definition: Database.php:1562
Wikimedia\Rdbms\DatabasePostgres\getType
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
Definition: DatabasePostgres.php:67
Wikimedia\Rdbms\Database\addIdentifierQuotes
addIdentifierQuotes( $s)
Quotes an identifier using backticks or "double quotes" depending on the database type.
Definition: Database.php:2001
Wikimedia\Rdbms\DatabasePostgres\getServerVersion
getServerVersion()
A string describing the current software version, like from mysql_get_server_info().
Definition: DatabasePostgres.php:1023
$args
if( $line===false) $args
Definition: cdb.php:63
Wikimedia\Rdbms\Database\close
close()
Closes a database connection.
Definition: Database.php:726
Wikimedia\Rdbms\DatabasePostgres\lastError
lastError()
Get a description of the last error.
Definition: DatabasePostgres.php:365
Wikimedia\Rdbms\DatabasePostgres\__construct
__construct(array $params)
Definition: DatabasePostgres.php:58
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:411
Wikimedia\Rdbms\DatabasePostgres\constraintExists
constraintExists( $table, $constraint)
Definition: DatabasePostgres.php:1119
Wikimedia\Rdbms\DatabasePostgres\$mCoreSchema
string $mCoreSchema
Definition: DatabasePostgres.php:49
Wikimedia\Rdbms\DatabasePostgres\streamStatementEnd
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
Definition: DatabasePostgres.php:1299
Wikimedia\Rdbms\Database\makeGroupByWithHaving
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1227
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:91
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
Wikimedia\Rdbms\DatabasePostgres\sequenceExists
sequenceExists( $sequence, $schema=false)
Definition: DatabasePostgres.php:1080
$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:1076
Wikimedia\Rdbms\DatabasePostgres\$keywordTableMap
string[] $keywordTableMap
Map of (reserved table name => alternate table name)
Definition: DatabasePostgres.php:51
Wikimedia\Rdbms\DatabasePostgres\buildGroupConcatField
buildGroupConcatField( $delimiter, $table, $field, $conds='', $options=[], $join_conds=[])
Definition: DatabasePostgres.php:1287
Wikimedia\Rdbms\Database\makeOrderBy
makeOrderBy( $options)
Returns an optional ORDER BY.
Definition: Database.php:1253
Wikimedia\Rdbms\DatabasePostgres\dataSeek
dataSeek( $res, $row)
Change the position of the cursor in a result object.
Definition: DatabasePostgres.php:357
Wikimedia\Rdbms\DatabasePostgres\ruleExists
ruleExists( $table, $rule)
Definition: DatabasePostgres.php:1107
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:1265
Wikimedia\Rdbms\DatabasePostgres\fetchRow
fetchRow( $res)
Fetch the next row from the given result object, in associative array form.
Definition: DatabasePostgres.php:293
Wikimedia\Rdbms\DatabasePostgres\fieldName
fieldName( $res, $n)
Get a field name in a result object.
Definition: DatabasePostgres.php:339
Wikimedia\Rdbms\DatabasePostgres\selectDB
selectDB( $db)
Postgres doesn't support selectDB in the same way MySQL does.
Definition: DatabasePostgres.php:174
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:219
Wikimedia\Rdbms\DatabasePostgres\determineCoreSchema
determineCoreSchema( $desiredSchema)
Determine default schema for the current application Adjust this session schema search path if desire...
Definition: DatabasePostgres.php:982
Wikimedia\Rdbms\DatabasePostgres\getSearchPath
getSearchPath()
Return search patch for schemas This is different from getSchemas() since it contain magic keywords (...
Definition: DatabasePostgres.php:948
Wikimedia\Rdbms\DatabasePostgres\getCoreSchema
getCoreSchema()
Return schema name for core application tables.
Definition: DatabasePostgres.php:1019
Wikimedia\Rdbms\DatabasePostgres\reportQueryError
reportQueryError( $error, $errno, $sql, $fname, $tempIgnore=false)
Report a query error.
Definition: DatabasePostgres.php:240
Wikimedia\Rdbms\DatabasePostgres\indexInfo
indexInfo( $table, $index, $fname=__METHOD__)
Get information about an index into an object.
Definition: DatabasePostgres.php:428
Wikimedia\Rdbms\DatabasePostgres\triggerExists
triggerExists( $table, $trigger)
Definition: DatabasePostgres.php:1084
Wikimedia\Rdbms\DatabasePostgres\currentSequenceValue
currentSequenceValue( $seqName)
Return the current value of a sequence.
Definition: DatabasePostgres.php:785
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:964
Wikimedia\Rdbms\DatabasePostgres\replaceVars
replaceVars( $ins)
Postgres specific version of replaceVars.
Definition: DatabasePostgres.php:1231
Wikimedia\Rdbms\DatabasePostgres\wasDeadlock
wasDeadlock()
Determines if the last failure was due to a deadlock.
Definition: DatabasePostgres.php:815
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1049
Wikimedia\Rdbms\PostgresBlob
Definition: PostgresBlob.php:5
Wikimedia\Rdbms\DatabasePostgres\lock
lock( $lockName, $method, $timeout=5)
Acquire a named lock.
Definition: DatabasePostgres.php:1349
Wikimedia\Rdbms\DatabasePostgres\hasConstraint
hasConstraint( $name)
Definition: DatabasePostgres.php:79
Wikimedia\Rdbms\DatabasePostgres\listTables
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
Definition: DatabasePostgres.php:829
array
the array() calling protocol came about after MediaWiki 1.4rc1.
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:443
Wikimedia\Rdbms\Blob
Definition: Blob.php:5