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