MediaWiki REL1_28
DatabasePostgres.php
Go to the documentation of this file.
1<?php
23use Wikimedia\WaitConditionLoop;
24
30 protected $port;
31
33 protected $mLastResult = null;
35 protected $mAffectedRows = null;
36
38 private $mInsertId = null;
40 private $numericVersion = null;
44 private $mCoreSchema;
45
46 public function __construct( array $params ) {
47 $this->port = isset( $params['port'] ) ? $params['port'] : false;
48 parent::__construct( $params );
49 }
50
51 function getType() {
52 return 'postgres';
53 }
54
55 function implicitGroupby() {
56 return false;
57 }
58
59 function implicitOrderby() {
60 return false;
61 }
62
63 function hasConstraint( $name ) {
64 $conn = $this->getBindingHandle();
65
66 $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
67 "WHERE c.connamespace = n.oid AND conname = '" .
68 pg_escape_string( $conn, $name ) . "' AND n.nspname = '" .
69 pg_escape_string( $conn, $this->getCoreSchema() ) . "'";
70 $res = $this->doQuery( $sql );
71
72 return $this->numRows( $res );
73 }
74
84 function open( $server, $user, $password, $dbName ) {
85 # Test for Postgres support, to avoid suppressed fatal error
86 if ( !function_exists( 'pg_connect' ) ) {
87 throw new DBConnectionError(
88 $this,
89 "Postgres functions missing, have you compiled PHP with the --with-pgsql\n" .
90 "option? (Note: if you recently installed PHP, you may need to restart your\n" .
91 "webserver and database)\n"
92 );
93 }
94
95 $this->mServer = $server;
96 $this->mUser = $user;
97 $this->mPassword = $password;
98 $this->mDBname = $dbName;
99
100 $connectVars = [
101 'dbname' => $dbName,
102 'user' => $user,
103 'password' => $password
104 ];
105 if ( $server != false && $server != '' ) {
106 $connectVars['host'] = $server;
107 }
108 if ( (int)$this->port > 0 ) {
109 $connectVars['port'] = (int)$this->port;
110 }
111 if ( $this->mFlags & self::DBO_SSL ) {
112 $connectVars['sslmode'] = 1;
113 }
114
115 $this->connectString = $this->makeConnectionString( $connectVars );
116 $this->close();
117 $this->installErrorHandler();
118
119 try {
120 // Use new connections to let LoadBalancer/LBFactory handle reuse
121 $this->mConn = pg_connect( $this->connectString, PGSQL_CONNECT_FORCE_NEW );
122 } catch ( Exception $ex ) {
123 $this->restoreErrorHandler();
124 throw $ex;
125 }
126
127 $phpError = $this->restoreErrorHandler();
128
129 if ( !$this->mConn ) {
130 $this->queryLogger->debug(
131 "DB connection error\n" .
132 "Server: $server, Database: $dbName, User: $user, Password: " .
133 substr( $password, 0, 3 ) . "...\n"
134 );
135 $this->queryLogger->debug( $this->lastError() . "\n" );
136 throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
137 }
138
139 $this->mOpened = true;
140
141 # If called from the command-line (e.g. importDump), only show errors
142 if ( $this->cliMode ) {
143 $this->doQuery( "SET client_min_messages = 'ERROR'" );
144 }
145
146 $this->query( "SET client_encoding='UTF8'", __METHOD__ );
147 $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
148 $this->query( "SET timezone = 'GMT'", __METHOD__ );
149 $this->query( "SET standard_conforming_strings = on", __METHOD__ );
150 if ( $this->getServerVersion() >= 9.0 ) {
151 $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
152 }
153
154 $this->determineCoreSchema( $this->mSchema );
155 // The schema to be used is now in the search path; no need for explicit qualification
156 $this->mSchema = null;
157
158 return $this->mConn;
159 }
160
167 function selectDB( $db ) {
168 if ( $this->mDBname !== $db ) {
169 return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
170 } else {
171 return true;
172 }
173 }
174
176 $s = '';
177 foreach ( $vars as $name => $value ) {
178 $s .= "$name='" . str_replace( "'", "\\'", $value ) . "' ";
179 }
180
181 return $s;
182 }
183
189 protected function closeConnection() {
190 return $this->mConn ? pg_close( $this->mConn ) : true;
191 }
192
193 public function doQuery( $sql ) {
194 $conn = $this->getBindingHandle();
195
196 $sql = mb_convert_encoding( $sql, 'UTF-8' );
197 // Clear previously left over PQresult
198 while ( $res = pg_get_result( $conn ) ) {
199 pg_free_result( $res );
200 }
201 if ( pg_send_query( $conn, $sql ) === false ) {
202 throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
203 }
204 $this->mLastResult = pg_get_result( $conn );
205 $this->mAffectedRows = null;
206 if ( pg_result_error( $this->mLastResult ) ) {
207 return false;
208 }
209
210 return $this->mLastResult;
211 }
212
213 protected function dumpError() {
214 $diags = [
215 PGSQL_DIAG_SEVERITY,
216 PGSQL_DIAG_SQLSTATE,
217 PGSQL_DIAG_MESSAGE_PRIMARY,
218 PGSQL_DIAG_MESSAGE_DETAIL,
219 PGSQL_DIAG_MESSAGE_HINT,
220 PGSQL_DIAG_STATEMENT_POSITION,
221 PGSQL_DIAG_INTERNAL_POSITION,
222 PGSQL_DIAG_INTERNAL_QUERY,
223 PGSQL_DIAG_CONTEXT,
224 PGSQL_DIAG_SOURCE_FILE,
225 PGSQL_DIAG_SOURCE_LINE,
226 PGSQL_DIAG_SOURCE_FUNCTION
227 ];
228 foreach ( $diags as $d ) {
229 $this->queryLogger->debug( sprintf( "PgSQL ERROR(%d): %s\n",
230 $d, pg_result_error_field( $this->mLastResult, $d ) ) );
231 }
232 }
233
234 function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
235 if ( $tempIgnore ) {
236 /* Check for constraint violation */
237 if ( $errno === '23505' ) {
238 parent::reportQueryError( $error, $errno, $sql, $fname, $tempIgnore );
239
240 return;
241 }
242 }
243 /* Transaction stays in the ERROR state until rolled back */
244 if ( $this->mTrxLevel ) {
245 $ignore = $this->ignoreErrors( true );
246 $this->rollback( __METHOD__ );
247 $this->ignoreErrors( $ignore );
248 }
249 parent::reportQueryError( $error, $errno, $sql, $fname, false );
250 }
251
252 function queryIgnore( $sql, $fname = __METHOD__ ) {
253 return $this->query( $sql, $fname, true );
254 }
255
260 function freeResult( $res ) {
261 if ( $res instanceof ResultWrapper ) {
262 $res = $res->result;
263 }
264 MediaWiki\suppressWarnings();
265 $ok = pg_free_result( $res );
266 MediaWiki\restoreWarnings();
267 if ( !$ok ) {
268 throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
269 }
270 }
271
277 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 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 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 function numFields( $res ) {
338 if ( $res instanceof ResultWrapper ) {
339 $res = $res->result;
340 }
341
342 return pg_num_fields( $res );
343 }
344
345 function fieldName( $res, $n ) {
346 if ( $res instanceof ResultWrapper ) {
347 $res = $res->result;
348 }
349
350 return pg_field_name( $res, $n );
351 }
352
359 function insertId() {
360 return $this->mInsertId;
361 }
362
368 function dataSeek( $res, $row ) {
369 if ( $res instanceof ResultWrapper ) {
370 $res = $res->result;
371 }
372
373 return pg_result_seek( $res, $row );
374 }
375
376 function lastError() {
377 if ( $this->mConn ) {
378 if ( $this->mLastResult ) {
379 return pg_result_error( $this->mLastResult );
380 } else {
381 return pg_last_error();
382 }
383 }
384
385 return $this->getLastPHPError() ?: 'No database connection';
386 }
387
388 function lastErrno() {
389 if ( $this->mLastResult ) {
390 return pg_result_error_field( $this->mLastResult, PGSQL_DIAG_SQLSTATE );
391 } else {
392 return false;
393 }
394 }
395
396 function affectedRows() {
397 if ( !is_null( $this->mAffectedRows ) ) {
398 // Forced result for simulated queries
400 }
401 if ( empty( $this->mLastResult ) ) {
402 return 0;
403 }
404
405 return pg_affected_rows( $this->mLastResult );
406 }
407
422 function estimateRowCount( $table, $vars = '*', $conds = '',
423 $fname = __METHOD__, $options = []
424 ) {
425 $options['EXPLAIN'] = true;
426 $res = $this->select( $table, $vars, $conds, $fname, $options );
427 $rows = -1;
428 if ( $res ) {
429 $row = $this->fetchRow( $res );
430 $count = [];
431 if ( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
432 $rows = (int)$count[1];
433 }
434 }
435
436 return $rows;
437 }
438
448 function indexInfo( $table, $index, $fname = __METHOD__ ) {
449 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
450 $res = $this->query( $sql, $fname );
451 if ( !$res ) {
452 return null;
453 }
454 foreach ( $res as $row ) {
455 if ( $row->indexname == $this->indexName( $index ) ) {
456 return $row;
457 }
458 }
459
460 return false;
461 }
462
471 function indexAttributes( $index, $schema = false ) {
472 if ( $schema === false ) {
473 $schema = $this->getCoreSchema();
474 }
475 /*
476 * A subquery would be not needed if we didn't care about the order
477 * of attributes, but we do
478 */
479 $sql = <<<__INDEXATTR__
480
481 SELECT opcname,
482 attname,
483 i.indoption[s.g] as option,
484 pg_am.amname
485 FROM
486 (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
487 FROM
488 pg_index isub
489 JOIN pg_class cis
490 ON cis.oid=isub.indexrelid
491 JOIN pg_namespace ns
492 ON cis.relnamespace = ns.oid
493 WHERE cis.relname='$index' AND ns.nspname='$schema') AS s,
494 pg_attribute,
495 pg_opclass opcls,
496 pg_am,
497 pg_class ci
498 JOIN pg_index i
499 ON ci.oid=i.indexrelid
500 JOIN pg_class ct
501 ON ct.oid = i.indrelid
502 JOIN pg_namespace n
503 ON ci.relnamespace = n.oid
504 WHERE
505 ci.relname='$index' AND n.nspname='$schema'
506 AND attrelid = ct.oid
507 AND i.indkey[s.g] = attnum
508 AND i.indclass[s.g] = opcls.oid
509 AND pg_am.oid = opcls.opcmethod
510__INDEXATTR__;
511 $res = $this->query( $sql, __METHOD__ );
512 $a = [];
513 if ( $res ) {
514 foreach ( $res as $row ) {
515 $a[] = [
516 $row->attname,
517 $row->opcname,
518 $row->amname,
519 $row->option ];
520 }
521 } else {
522 return null;
523 }
524
525 return $a;
526 }
527
528 function indexUnique( $table, $index, $fname = __METHOD__ ) {
529 $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'" .
530 " AND indexdef LIKE 'CREATE UNIQUE%(" .
531 $this->strencode( $this->indexName( $index ) ) .
532 ")'";
533 $res = $this->query( $sql, $fname );
534 if ( !$res ) {
535 return null;
536 }
537
538 return $res->numRows() > 0;
539 }
540
542 $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
543 ) {
544 // Change the FOR UPDATE option as necessary based on the join conditions. Then pass
545 // to the parent function to get the actual SQL text.
546 // In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
547 // can be locked. That means tables in an outer join cannot be FOR UPDATE locked. Trying to
548 // do so causes a DB error. This wrapper checks which tables can be locked and adjusts it
549 // accordingly.
550 // MySQL uses "ORDER BY NULL" as an optimization hint, but that is illegal in PostgreSQL.
551 if ( is_array( $options ) ) {
552 $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
553 if ( $forUpdateKey !== false && $join_conds ) {
554 unset( $options[$forUpdateKey] );
555
556 foreach ( $join_conds as $table_cond => $join_cond ) {
557 if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) {
558 $options['FOR UPDATE'][] = $table_cond;
559 }
560 }
561 }
562
563 if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
564 unset( $options['ORDER BY'] );
565 }
566 }
567
568 return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
569 }
570
583 function insert( $table, $args, $fname = __METHOD__, $options = [] ) {
584 if ( !count( $args ) ) {
585 return true;
586 }
587
588 $table = $this->tableName( $table );
589 if ( !isset( $this->numericVersion ) ) {
590 $this->getServerVersion();
591 }
592
593 if ( !is_array( $options ) ) {
594 $options = [ $options ];
595 }
596
597 if ( isset( $args[0] ) && is_array( $args[0] ) ) {
598 $multi = true;
599 $keys = array_keys( $args[0] );
600 } else {
601 $multi = false;
602 $keys = array_keys( $args );
603 }
604
605 // If IGNORE is set, we use savepoints to emulate mysql's behavior
606 $savepoint = $olde = null;
607 $numrowsinserted = 0;
608 if ( in_array( 'IGNORE', $options ) ) {
609 $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
610 $olde = error_reporting( 0 );
611 // For future use, we may want to track the number of actual inserts
612 // Right now, insert (all writes) simply return true/false
613 }
614
615 $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
616
617 if ( $multi ) {
618 if ( $this->numericVersion >= 8.2 && !$savepoint ) {
619 $first = true;
620 foreach ( $args as $row ) {
621 if ( $first ) {
622 $first = false;
623 } else {
624 $sql .= ',';
625 }
626 $sql .= '(' . $this->makeList( $row ) . ')';
627 }
628 $res = (bool)$this->query( $sql, $fname, $savepoint );
629 } else {
630 $res = true;
631 $origsql = $sql;
632 foreach ( $args as $row ) {
633 $tempsql = $origsql;
634 $tempsql .= '(' . $this->makeList( $row ) . ')';
635
636 if ( $savepoint ) {
637 $savepoint->savepoint();
638 }
639
640 $tempres = (bool)$this->query( $tempsql, $fname, $savepoint );
641
642 if ( $savepoint ) {
643 $bar = pg_result_error( $this->mLastResult );
644 if ( $bar != false ) {
645 $savepoint->rollback();
646 } else {
647 $savepoint->release();
648 $numrowsinserted++;
649 }
650 }
651
652 // If any of them fail, we fail overall for this function call
653 // Note that this will be ignored if IGNORE is set
654 if ( !$tempres ) {
655 $res = false;
656 }
657 }
658 }
659 } else {
660 // Not multi, just a lone insert
661 if ( $savepoint ) {
662 $savepoint->savepoint();
663 }
664
665 $sql .= '(' . $this->makeList( $args ) . ')';
666 $res = (bool)$this->query( $sql, $fname, $savepoint );
667 if ( $savepoint ) {
668 $bar = pg_result_error( $this->mLastResult );
669 if ( $bar != false ) {
670 $savepoint->rollback();
671 } else {
672 $savepoint->release();
673 $numrowsinserted++;
674 }
675 }
676 }
677 if ( $savepoint ) {
678 error_reporting( $olde );
679 $savepoint->commit();
680
681 // Set the affected row count for the whole operation
682 $this->mAffectedRows = $numrowsinserted;
683
684 // IGNORE always returns true
685 return true;
686 }
687
688 return $res;
689 }
690
709 function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname = __METHOD__,
710 $insertOptions = [], $selectOptions = [] ) {
711 $destTable = $this->tableName( $destTable );
712
713 if ( !is_array( $insertOptions ) ) {
714 $insertOptions = [ $insertOptions ];
715 }
716
717 /*
718 * If IGNORE is set, we use savepoints to emulate mysql's behavior
719 * Ignore LOW PRIORITY option, since it is MySQL-specific
720 */
721 $savepoint = $olde = null;
722 $numrowsinserted = 0;
723 if ( in_array( 'IGNORE', $insertOptions ) ) {
724 $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
725 $olde = error_reporting( 0 );
726 $savepoint->savepoint();
727 }
728
729 if ( !is_array( $selectOptions ) ) {
730 $selectOptions = [ $selectOptions ];
731 }
732 list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) =
733 $this->makeSelectOptions( $selectOptions );
734 if ( is_array( $srcTable ) ) {
735 $srcTable = implode( ',', array_map( [ $this, 'tableName' ], $srcTable ) );
736 } else {
737 $srcTable = $this->tableName( $srcTable );
738 }
739
740 $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
741 " SELECT $startOpts " . implode( ',', $varMap ) .
742 " FROM $srcTable $useIndex $ignoreIndex ";
743
744 if ( $conds != '*' ) {
745 $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
746 }
747
748 $sql .= " $tailOpts";
749
750 $res = (bool)$this->query( $sql, $fname, $savepoint );
751 if ( $savepoint ) {
752 $bar = pg_result_error( $this->mLastResult );
753 if ( $bar != false ) {
754 $savepoint->rollback();
755 } else {
756 $savepoint->release();
757 $numrowsinserted++;
758 }
759 error_reporting( $olde );
760 $savepoint->commit();
761
762 // Set the affected row count for the whole operation
763 $this->mAffectedRows = $numrowsinserted;
764
765 // IGNORE always returns true
766 return true;
767 }
768
769 return $res;
770 }
771
772 function tableName( $name, $format = 'quoted' ) {
773 // Replace reserved words with better ones
774 $name = $this->remappedTableName( $name );
775
776 return parent::tableName( $name, $format );
777 }
778
784 public function remappedTableName( $name ) {
785 if ( $name === 'user' ) {
786 return 'mwuser';
787 } elseif ( $name === 'text' ) {
788 return 'pagecontent';
789 }
790
791 return $name;
792 }
793
799 public function realTableName( $name, $format = 'quoted' ) {
800 return parent::tableName( $name, $format );
801 }
802
809 function nextSequenceValue( $seqName ) {
810 $safeseq = str_replace( "'", "''", $seqName );
811 $res = $this->query( "SELECT nextval('$safeseq')" );
812 $row = $this->fetchRow( $res );
813 $this->mInsertId = $row[0];
814
815 return $this->mInsertId;
816 }
817
824 function currentSequenceValue( $seqName ) {
825 $safeseq = str_replace( "'", "''", $seqName );
826 $res = $this->query( "SELECT currval('$safeseq')" );
827 $row = $this->fetchRow( $res );
828 $currval = $row[0];
829
830 return $currval;
831 }
832
833 # Returns the size of a text field, or -1 for "unlimited"
834 function textFieldSize( $table, $field ) {
835 $table = $this->tableName( $table );
836 $sql = "SELECT t.typname as ftype,a.atttypmod as size
837 FROM pg_class c, pg_attribute a, pg_type t
838 WHERE relname='$table' AND a.attrelid=c.oid AND
839 a.atttypid=t.oid and a.attname='$field'";
840 $res = $this->query( $sql );
841 $row = $this->fetchObject( $res );
842 if ( $row->ftype == 'varchar' ) {
843 $size = $row->size - 4;
844 } else {
845 $size = $row->size;
846 }
847
848 return $size;
849 }
850
851 function limitResult( $sql, $limit, $offset = false ) {
852 return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
853 }
854
855 function wasDeadlock() {
856 return $this->lastErrno() == '40P01';
857 }
858
860 $oldName, $newName, $temporary = false, $fname = __METHOD__
861 ) {
862 $newName = $this->addIdentifierQuotes( $newName );
863 $oldName = $this->addIdentifierQuotes( $oldName );
864
865 return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " .
866 "(LIKE $oldName INCLUDING DEFAULTS)", $fname );
867 }
868
869 function listTables( $prefix = null, $fname = __METHOD__ ) {
870 $eschema = $this->addQuotes( $this->getCoreSchema() );
871 $result = $this->query(
872 "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
873 $endArray = [];
874
875 foreach ( $result as $table ) {
876 $vars = get_object_vars( $table );
877 $table = array_pop( $vars );
878 if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
879 $endArray[] = $table;
880 }
881 }
882
883 return $endArray;
884 }
885
886 function timestamp( $ts = 0 ) {
887 $ct = new ConvertibleTimestamp( $ts );
888
889 return $ct->getTimestamp( TS_POSTGRES );
890 }
891
910 function pg_array_parse( $text, &$output, $limit = false, $offset = 1 ) {
911 if ( false === $limit ) {
912 $limit = strlen( $text ) - 1;
913 $output = [];
914 }
915 if ( '{}' == $text ) {
916 return $output;
917 }
918 do {
919 if ( '{' != $text[$offset] ) {
920 preg_match( "/(\\{?\"([^\"\\\\]|\\\\.)*\"|[^,{}]+)+([,}]+)/",
921 $text, $match, 0, $offset );
922 $offset += strlen( $match[0] );
923 $output[] = ( '"' != $match[1][0]
924 ? $match[1]
925 : stripcslashes( substr( $match[1], 1, -1 ) ) );
926 if ( '},' == $match[3] ) {
927 return $output;
928 }
929 } else {
930 $offset = $this->pg_array_parse( $text, $output, $limit, $offset + 1 );
931 }
932 } while ( $limit > $offset );
933
934 return $output;
935 }
936
943 public function aggregateValue( $valuedata, $valuename = 'value' ) {
944 return $valuedata;
945 }
946
950 public function getSoftwareLink() {
951 return '[{{int:version-db-postgres-url}} PostgreSQL]';
952 }
953
961 function getCurrentSchema() {
962 $res = $this->query( "SELECT current_schema()", __METHOD__ );
963 $row = $this->fetchRow( $res );
964
965 return $row[0];
966 }
967
978 function getSchemas() {
979 $res = $this->query( "SELECT current_schemas(false)", __METHOD__ );
980 $row = $this->fetchRow( $res );
981 $schemas = [];
982
983 /* PHP pgsql support does not support array type, "{a,b}" string is returned */
984
985 return $this->pg_array_parse( $row[0], $schemas );
986 }
987
997 function getSearchPath() {
998 $res = $this->query( "SHOW search_path", __METHOD__ );
999 $row = $this->fetchRow( $res );
1000
1001 /* PostgreSQL returns SHOW values as strings */
1002
1003 return explode( ",", $row[0] );
1004 }
1005
1013 function setSearchPath( $search_path ) {
1014 $this->query( "SET search_path = " . implode( ", ", $search_path ) );
1015 }
1016
1031 function determineCoreSchema( $desiredSchema ) {
1032 $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
1033 if ( $this->schemaExists( $desiredSchema ) ) {
1034 if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
1035 $this->mCoreSchema = $desiredSchema;
1036 $this->queryLogger->debug(
1037 "Schema \"" . $desiredSchema . "\" already in the search path\n" );
1038 } else {
1044 $search_path = $this->getSearchPath();
1045 array_unshift( $search_path,
1046 $this->addIdentifierQuotes( $desiredSchema ) );
1047 $this->setSearchPath( $search_path );
1048 $this->mCoreSchema = $desiredSchema;
1049 $this->queryLogger->debug(
1050 "Schema \"" . $desiredSchema . "\" added to the search path\n" );
1051 }
1052 } else {
1053 $this->mCoreSchema = $this->getCurrentSchema();
1054 $this->queryLogger->debug(
1055 "Schema \"" . $desiredSchema . "\" not found, using current \"" .
1056 $this->mCoreSchema . "\"\n" );
1057 }
1058 /* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
1059 $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
1060 }
1061
1068 function getCoreSchema() {
1069 return $this->mCoreSchema;
1070 }
1071
1075 function getServerVersion() {
1076 if ( !isset( $this->numericVersion ) ) {
1077 $conn = $this->getBindingHandle();
1078 $versionInfo = pg_version( $conn );
1079 if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
1080 // Old client, abort install
1081 $this->numericVersion = '7.3 or earlier';
1082 } elseif ( isset( $versionInfo['server'] ) ) {
1083 // Normal client
1084 $this->numericVersion = $versionInfo['server'];
1085 } else {
1086 // Bug 16937: broken pgsql extension from PHP<5.3
1087 $this->numericVersion = pg_parameter_status( $conn, 'server_version' );
1088 }
1089 }
1090
1091 return $this->numericVersion;
1092 }
1093
1102 function relationExists( $table, $types, $schema = false ) {
1103 if ( !is_array( $types ) ) {
1104 $types = [ $types ];
1105 }
1106 if ( $schema === false ) {
1107 $schema = $this->getCoreSchema();
1108 }
1109 $table = $this->realTableName( $table, 'raw' );
1110 $etable = $this->addQuotes( $table );
1111 $eschema = $this->addQuotes( $schema );
1112 $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
1113 . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
1114 . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
1115 $res = $this->query( $sql );
1116 $count = $res ? $res->numRows() : 0;
1117
1118 return (bool)$count;
1119 }
1120
1129 function tableExists( $table, $fname = __METHOD__, $schema = false ) {
1130 return $this->relationExists( $table, [ 'r', 'v' ], $schema );
1131 }
1132
1133 function sequenceExists( $sequence, $schema = false ) {
1134 return $this->relationExists( $sequence, 'S', $schema );
1135 }
1136
1137 function triggerExists( $table, $trigger ) {
1138 $q = <<<SQL
1139 SELECT 1 FROM pg_class, pg_namespace, pg_trigger
1140 WHERE relnamespace=pg_namespace.oid AND relkind='r'
1141 AND tgrelid=pg_class.oid
1142 AND nspname=%s AND relname=%s AND tgname=%s
1143SQL;
1144 $res = $this->query(
1145 sprintf(
1146 $q,
1147 $this->addQuotes( $this->getCoreSchema() ),
1148 $this->addQuotes( $table ),
1149 $this->addQuotes( $trigger )
1150 )
1151 );
1152 if ( !$res ) {
1153 return null;
1154 }
1155 $rows = $res->numRows();
1156
1157 return $rows;
1158 }
1159
1160 function ruleExists( $table, $rule ) {
1161 $exists = $this->selectField( 'pg_rules', 'rulename',
1162 [
1163 'rulename' => $rule,
1164 'tablename' => $table,
1165 'schemaname' => $this->getCoreSchema()
1166 ]
1167 );
1168
1169 return $exists === $rule;
1170 }
1171
1172 function constraintExists( $table, $constraint ) {
1173 $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
1174 "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
1175 $this->addQuotes( $this->getCoreSchema() ),
1176 $this->addQuotes( $table ),
1177 $this->addQuotes( $constraint )
1178 );
1179 $res = $this->query( $sql );
1180 if ( !$res ) {
1181 return null;
1182 }
1183 $rows = $res->numRows();
1184
1185 return $rows;
1186 }
1187
1193 function schemaExists( $schema ) {
1194 $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
1195 [ 'nspname' => $schema ], __METHOD__ );
1196
1197 return (bool)$exists;
1198 }
1199
1205 function roleExists( $roleName ) {
1206 $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
1207 [ 'rolname' => $roleName ], __METHOD__ );
1208
1209 return (bool)$exists;
1210 }
1211
1217 function fieldInfo( $table, $field ) {
1218 return PostgresField::fromText( $this, $table, $field );
1219 }
1220
1227 function fieldType( $res, $index ) {
1228 if ( $res instanceof ResultWrapper ) {
1229 $res = $res->result;
1230 }
1231
1232 return pg_field_type( $res, $index );
1233 }
1234
1239 function encodeBlob( $b ) {
1240 return new PostgresBlob( pg_escape_bytea( $b ) );
1241 }
1242
1243 function decodeBlob( $b ) {
1244 if ( $b instanceof PostgresBlob ) {
1245 $b = $b->fetch();
1246 } elseif ( $b instanceof Blob ) {
1247 return $b->fetch();
1248 }
1249
1250 return pg_unescape_bytea( $b );
1251 }
1252
1253 function strencode( $s ) {
1254 // Should not be called by us
1255 return pg_escape_string( $this->getBindingHandle(), $s );
1256 }
1257
1262 function addQuotes( $s ) {
1263 $conn = $this->getBindingHandle();
1264
1265 if ( is_null( $s ) ) {
1266 return 'NULL';
1267 } elseif ( is_bool( $s ) ) {
1268 return intval( $s );
1269 } elseif ( $s instanceof Blob ) {
1270 if ( $s instanceof PostgresBlob ) {
1271 $s = $s->fetch();
1272 } else {
1273 $s = pg_escape_bytea( $conn, $s->fetch() );
1274 }
1275 return "'$s'";
1276 }
1277
1278 return "'" . pg_escape_string( $conn, $s ) . "'";
1279 }
1280
1288 protected function replaceVars( $ins ) {
1289 $ins = parent::replaceVars( $ins );
1290
1291 if ( $this->numericVersion >= 8.3 ) {
1292 // Thanks for not providing backwards-compatibility, 8.3
1293 $ins = preg_replace( "/to_tsvector\s*\‍(\s*'default'\s*,/", 'to_tsvector(', $ins );
1294 }
1295
1296 if ( $this->numericVersion <= 8.1 ) { // Our minimum version
1297 $ins = str_replace( 'USING gin', 'USING gist', $ins );
1298 }
1299
1300 return $ins;
1301 }
1302
1311 $preLimitTail = $postLimitTail = '';
1312 $startOpts = $useIndex = $ignoreIndex = '';
1313
1314 $noKeyOptions = [];
1315 foreach ( $options as $key => $option ) {
1316 if ( is_numeric( $key ) ) {
1317 $noKeyOptions[$option] = true;
1318 }
1319 }
1320
1321 $preLimitTail .= $this->makeGroupByWithHaving( $options );
1322
1323 $preLimitTail .= $this->makeOrderBy( $options );
1324
1325 // if ( isset( $options['LIMIT'] ) ) {
1326 // $tailOpts .= $this->limitResult( '', $options['LIMIT'],
1327 // isset( $options['OFFSET'] ) ? $options['OFFSET']
1328 // : false );
1329 // }
1330
1331 if ( isset( $options['FOR UPDATE'] ) ) {
1332 $postLimitTail .= ' FOR UPDATE OF ' .
1333 implode( ', ', array_map( [ $this, 'tableName' ], $options['FOR UPDATE'] ) );
1334 } elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1335 $postLimitTail .= ' FOR UPDATE';
1336 }
1337
1338 if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1339 $startOpts .= 'DISTINCT';
1340 }
1341
1342 return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1343 }
1344
1345 function getDBname() {
1346 return $this->mDBname;
1347 }
1348
1349 function getServer() {
1350 return $this->mServer;
1351 }
1352
1353 function buildConcat( $stringList ) {
1354 return implode( ' || ', $stringList );
1355 }
1356
1357 public function buildGroupConcatField(
1358 $delimiter, $table, $field, $conds = '', $options = [], $join_conds = []
1359 ) {
1360 $fld = "array_to_string(array_agg($field)," . $this->addQuotes( $delimiter ) . ')';
1361
1362 return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1363 }
1364
1370 public function buildStringCast( $field ) {
1371 return $field . '::text';
1372 }
1373
1374 public function streamStatementEnd( &$sql, &$newLine ) {
1375 # Allow dollar quoting for function declarations
1376 if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
1377 if ( $this->delimiter ) {
1378 $this->delimiter = false;
1379 } else {
1380 $this->delimiter = ';';
1381 }
1382 }
1383
1384 return parent::streamStatementEnd( $sql, $newLine );
1385 }
1386
1396 public function lockIsFree( $lockName, $method ) {
1397 $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1398 $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
1399 WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
1400 $row = $this->fetchObject( $result );
1401
1402 return ( $row->lockstatus === 't' );
1403 }
1404
1412 public function lock( $lockName, $method, $timeout = 5 ) {
1413 $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1414 $loop = new WaitConditionLoop(
1415 function () use ( $lockName, $key, $timeout, $method ) {
1416 $res = $this->query( "SELECT pg_try_advisory_lock($key) AS lockstatus", $method );
1417 $row = $this->fetchObject( $res );
1418 if ( $row->lockstatus === 't' ) {
1419 parent::lock( $lockName, $method, $timeout ); // record
1420 return true;
1421 }
1422
1423 return WaitConditionLoop::CONDITION_CONTINUE;
1424 },
1425 $timeout
1426 );
1427
1428 return ( $loop->invoke() === $loop::CONDITION_REACHED );
1429 }
1430
1438 public function unlock( $lockName, $method ) {
1439 $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
1440 $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
1441 $row = $this->fetchObject( $result );
1442
1443 if ( $row->lockstatus === 't' ) {
1444 parent::unlock( $lockName, $method ); // record
1445 return true;
1446 }
1447
1448 $this->queryLogger->debug( __METHOD__ . " failed to release lock\n" );
1449
1450 return false;
1451 }
1452
1457 private function bigintFromLockName( $lockName ) {
1458 return Wikimedia\base_convert( substr( sha1( $lockName ), 0, 15 ), 16, 10 );
1459 }
1460}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition Setup.php:36
if( $line===false) $args
Definition cdb.php:64
Utility class.
Definition Blob.php:8
Library for creating, parsing, and converting timestamps.
bigintFromLockName( $lockName)
string $connectString
Connect string to open a PostgreSQL connection.
affectedRows()
Get the number of rows affected by the last write query.
getCoreSchema()
Return schema name for core application tables.
triggerExists( $table, $trigger)
replaceVars( $ins)
Postgres specific version of replaceVars.
reportQueryError( $error, $errno, $sql, $fname, $tempIgnore=false)
Report a query error.
implicitGroupby()
Returns true if this database does an implicit sort when doing GROUP BY.
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
unlock( $lockName, $method)
See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKSFROM PG DO...
wasDeadlock()
Determines if the last failure was due to a deadlock.
fieldName( $res, $n)
Get a field name in a result object.
getDBname()
Get the current DB name.
lastError()
Get a description of the last error.
nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[])
INSERT SELECT wrapper $varMap must be an associative array of the form [ 'dest1' => 'source1',...
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
The equivalent of IDatabase::select() except that the constructed SQL is returned,...
makeSelectOptions( $options)
Various select options.
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited".
constraintExists( $table, $constraint)
fieldInfo( $table, $field)
mysql_fetch_field() wrapper Returns false if the field doesn't exist
getCurrentSchema()
Return current schema (executes SELECT current_schema()) Needs transaction.
selectDB( $db)
Postgres doesn't support selectDB in the same way MySQL does.
schemaExists( $schema)
Query whether a given schema exists.
buildGroupConcatField( $delimiter, $table, $field, $conds='', $options=[], $join_conds=[])
currentSequenceValue( $seqName)
Return the current value of a sequence.
fieldType( $res, $index)
pg_field_type() wrapper
lastErrno()
Get the last error number.
__construct(array $params)
Constructor and database handle and attempt to connect to the DB server.
realTableName( $name, $format='quoted')
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
getServer()
Get the server hostname or IP address.
roleExists( $roleName)
Returns true if a given role (i.e.
indexUnique( $table, $index, $fname=__METHOD__)
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
indexInfo( $table, $index, $fname=__METHOD__)
Returns information about an index If errors are explicitly ignored, returns NULL on failure.
determineCoreSchema( $desiredSchema)
Determine default schema for the current application Adjust this session schema search path if desire...
numRows( $res)
Get the number of rows in a result object.
float string $numericVersion
indexAttributes( $index, $schema=false)
Returns is of attributes used in index.
fetchRow( $res)
Fetch the next row from the given result object, in associative array form.
insert( $table, $args, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
tableName( $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.
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 http://www.php.net/manual/en/ref....
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
estimateRowCount( $table, $vars=' *', $conds='', $fname=__METHOD__, $options=[])
Estimate rows in dataset Returns estimated count, based on EXPLAIN output This is not necessarily an ...
numFields( $res)
Get the number of fields in a result object.
queryIgnore( $sql, $fname=__METHOD__)
getSchemas()
Return list of schemas which are accessible without schema name This is list does not contain magic k...
open( $server, $user, $password, $dbName)
Usually aborts on failure.
aggregateValue( $valuedata, $valuename='value')
Return aggregated value function call.
insertId()
Return the result of the last call to nextSequenceValue(); This must be called after nextSequenceValu...
strencode( $s)
Wrapper for addslashes()
nextSequenceValue( $seqName)
Return the next in a sequence, save the value for retrieval via insertId()
closeConnection()
Closes a database connection, if it is open Returns success, true if already closed.
relationExists( $table, $types, $schema=false)
Query whether a given relation exists (in the given schema, or the default mw one if not given)
setSearchPath( $search_path)
Update search_path, values should already be sanitized Values may contain magic keywords like "$user"...
lock( $lockName, $method, $timeout=5)
See http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS.
doQuery( $sql)
The DBMS-dependent part of query()
int $mAffectedRows
The number of rows affected as an integer.
tableExists( $table, $fname=__METHOD__, $schema=false)
For backward compatibility, this function checks both tables and views.
lockIsFree( $lockName, $method)
Check to see if a named lock is available.
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects.
getSearchPath()
Return search patch for schemas This is different from getSchemas() since it contain magic keywords (...
ruleExists( $table, $rule)
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Creates a new table with structure copied from existing table Note that unlike most database abstract...
sequenceExists( $sequence, $schema=false)
Relational database abstraction object.
Definition Database.php:36
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
string $mServer
Definition Database.php:59
installErrorHandler()
Definition Database.php:646
close()
Closes a database connection.
Definition Database.php:705
resource null $mConn
Database connection.
Definition Database.php:83
indexName( $index)
Get the name of an index in a given table.
addIdentifierQuotes( $s)
Quotes an identifier using backticks or "double quotes" depending on the database type.
ignoreErrors( $ignoreErrors=null)
Turns on (false) or off (true) the automatic generation and sending of a "we're sorry,...
Definition Database.php:429
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
string $delimiter
Definition Database.php:117
makeOrderBy( $options)
Returns an optional ORDER BY.
getLastPHPError()
Definition Database.php:667
getBindingHandle()
Get the underlying binding handle, mConn.
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[])
A SELECT wrapper which returns a single field from a single result row.
commit( $fname=__METHOD__, $flush='')
Commits a transaction previously started using begin().
query( $sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition Database.php:829
string $mDBname
Definition Database.php:65
restoreErrorHandler()
Definition Database.php:655
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
static fromText(DatabasePostgres $db, $table, $field)
Result wrapper for grabbing data queried from an IDatabase object.
Manage savepoints within a transaction.
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 select() and insert() are usually more convenient. They take care of things like table prefixes and escaping for you. If you really need to make your own SQL
$res
Definition database.txt:21
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
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
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
const LIST_AND
Definition Defines.php:35
the array() calling protocol came about after MediaWiki 1.4rc1.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist 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:1102
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2162
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 local account $user
Definition hooks.txt:249
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:Associative array mapping language codes to prefixed links of the form "language:title". & $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:1937
presenting them properly to the user as errors is done by the caller return true use this to change the list i e rollback
Definition hooks.txt:1731
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist 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:1096
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist 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 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:1135
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
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 LICENSE.txt:24
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:37
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
$params
const TS_POSTGRES
Postgres format time.
Definition defines.php:47