MediaWiki REL1_31
DatabaseMysqlBase.php
Go to the documentation of this file.
1<?php
23namespace Wikimedia\Rdbms;
24
25use DateTime;
26use DateTimeZone;
27use Wikimedia;
28use InvalidArgumentException;
29use Exception;
30use stdClass;
31
40abstract class DatabaseMysqlBase extends Database {
46 protected $lagDetectionOptions = [];
48 protected $useGTIDs = false;
50 protected $sslKeyPath;
52 protected $sslCertPath;
54 protected $sslCAFile;
56 protected $sslCAPath;
58 protected $sslCiphers;
60 protected $sqlMode;
62 protected $utf8Mode;
64 protected $defaultBigSelects = null;
65
67 private $serverVersion = null;
69 private $insertSelectIsSafe = null;
71 private $replicationInfoRow = null;
72
73 // Cache getServerId() for 24 hours
74 const SERVER_ID_CACHE_TTL = 86400;
75
77 const LAG_STALE_WARN_THRESHOLD = 0.100;
78
98 function __construct( array $params ) {
99 $this->lagDetectionMethod = isset( $params['lagDetectionMethod'] )
100 ? $params['lagDetectionMethod']
101 : 'Seconds_Behind_Master';
102 $this->lagDetectionOptions = isset( $params['lagDetectionOptions'] )
103 ? $params['lagDetectionOptions']
104 : [];
105 $this->useGTIDs = !empty( $params['useGTIDs' ] );
106 foreach ( [ 'KeyPath', 'CertPath', 'CAFile', 'CAPath', 'Ciphers' ] as $name ) {
107 $var = "ssl{$name}";
108 if ( isset( $params[$var] ) ) {
109 $this->$var = $params[$var];
110 }
111 }
112 $this->sqlMode = isset( $params['sqlMode'] ) ? $params['sqlMode'] : '';
113 $this->utf8Mode = !empty( $params['utf8Mode'] );
114 $this->insertSelectIsSafe = isset( $params['insertSelectIsSafe'] )
115 ? (bool)$params['insertSelectIsSafe'] : null;
116
117 parent::__construct( $params );
118 }
119
123 public function getType() {
124 return 'mysql';
125 }
126
135 public function open( $server, $user, $password, $dbName ) {
136 # Close/unset connection handle
137 $this->close();
138
139 $this->server = $server;
140 $this->user = $user;
141 $this->password = $password;
142 $this->dbName = $dbName;
143
144 $this->installErrorHandler();
145 try {
146 $this->conn = $this->mysqlConnect( $this->server );
147 } catch ( Exception $ex ) {
148 $this->restoreErrorHandler();
149 throw $ex;
150 }
151 $error = $this->restoreErrorHandler();
152
153 # Always log connection errors
154 if ( !$this->conn ) {
155 if ( !$error ) {
156 $error = $this->lastError();
157 }
158 $this->connLogger->error(
159 "Error connecting to {db_server}: {error}",
160 $this->getLogContext( [
161 'method' => __METHOD__,
162 'error' => $error,
163 ] )
164 );
165 $this->connLogger->debug( "DB connection error\n" .
166 "Server: $server, User: $user, Password: " .
167 substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
168
169 $this->reportConnectionError( $error );
170 }
171
172 if ( strlen( $dbName ) ) {
173 Wikimedia\suppressWarnings();
174 $success = $this->selectDB( $dbName );
175 Wikimedia\restoreWarnings();
176 if ( !$success ) {
177 $this->queryLogger->error(
178 "Error selecting database {db_name} on server {db_server}",
179 $this->getLogContext( [
180 'method' => __METHOD__,
181 ] )
182 );
183 $this->queryLogger->debug(
184 "Error selecting database $dbName on server {$this->server}" );
185
186 $this->reportConnectionError( "Error selecting database $dbName" );
187 }
188 }
189
190 // Tell the server what we're communicating with
191 if ( !$this->connectInitCharset() ) {
192 $this->reportConnectionError( "Error setting character set" );
193 }
194
195 // Abstract over any insane MySQL defaults
196 $set = [ 'group_concat_max_len = 262144' ];
197 // Set SQL mode, default is turning them all off, can be overridden or skipped with null
198 if ( is_string( $this->sqlMode ) ) {
199 $set[] = 'sql_mode = ' . $this->addQuotes( $this->sqlMode );
200 }
201 // Set any custom settings defined by site config
202 // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html)
203 foreach ( $this->sessionVars as $var => $val ) {
204 // Escape strings but not numbers to avoid MySQL complaining
205 if ( !is_int( $val ) && !is_float( $val ) ) {
206 $val = $this->addQuotes( $val );
207 }
208 $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val;
209 }
210
211 if ( $set ) {
212 // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
213 $success = $this->doQuery( 'SET ' . implode( ', ', $set ) );
214 if ( !$success ) {
215 $this->queryLogger->error(
216 'Error setting MySQL variables on server {db_server} (check $wgSQLMode)',
217 $this->getLogContext( [
218 'method' => __METHOD__,
219 ] )
220 );
222 'Error setting MySQL variables on server {db_server} (check $wgSQLMode)' );
223 }
224 }
225
226 $this->opened = true;
227
228 return true;
229 }
230
235 protected function connectInitCharset() {
236 if ( $this->utf8Mode ) {
237 // Tell the server we're communicating with it in UTF-8.
238 // This may engage various charset conversions.
239 return $this->mysqlSetCharset( 'utf8' );
240 } else {
241 return $this->mysqlSetCharset( 'binary' );
242 }
243 }
244
252 abstract protected function mysqlConnect( $realServer );
253
260 abstract protected function mysqlSetCharset( $charset );
261
266 public function freeResult( $res ) {
267 if ( $res instanceof ResultWrapper ) {
268 $res = $res->result;
269 }
270 Wikimedia\suppressWarnings();
271 $ok = $this->mysqlFreeResult( $res );
272 Wikimedia\restoreWarnings();
273 if ( !$ok ) {
274 throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
275 }
276 }
277
284 abstract protected function mysqlFreeResult( $res );
285
291 public function fetchObject( $res ) {
292 if ( $res instanceof ResultWrapper ) {
293 $res = $res->result;
294 }
295 Wikimedia\suppressWarnings();
296 $row = $this->mysqlFetchObject( $res );
297 Wikimedia\restoreWarnings();
298
299 $errno = $this->lastErrno();
300 // Unfortunately, mysql_fetch_object does not reset the last errno.
301 // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
302 // these are the only errors mysql_fetch_object can cause.
303 // See https://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
304 if ( $errno == 2000 || $errno == 2013 ) {
305 throw new DBUnexpectedError(
306 $this,
307 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() )
308 );
309 }
310
311 return $row;
312 }
313
320 abstract protected function mysqlFetchObject( $res );
321
327 public function fetchRow( $res ) {
328 if ( $res instanceof ResultWrapper ) {
329 $res = $res->result;
330 }
331 Wikimedia\suppressWarnings();
332 $row = $this->mysqlFetchArray( $res );
333 Wikimedia\restoreWarnings();
334
335 $errno = $this->lastErrno();
336 // Unfortunately, mysql_fetch_array does not reset the last errno.
337 // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
338 // these are the only errors mysql_fetch_array can cause.
339 // See https://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
340 if ( $errno == 2000 || $errno == 2013 ) {
341 throw new DBUnexpectedError(
342 $this,
343 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() )
344 );
345 }
346
347 return $row;
348 }
349
356 abstract protected function mysqlFetchArray( $res );
357
363 function numRows( $res ) {
364 if ( $res instanceof ResultWrapper ) {
365 $res = $res->result;
366 }
367 Wikimedia\suppressWarnings();
368 $n = $this->mysqlNumRows( $res );
369 Wikimedia\restoreWarnings();
370
371 // Unfortunately, mysql_num_rows does not reset the last errno.
372 // We are not checking for any errors here, since
373 // these are no errors mysql_num_rows can cause.
374 // See https://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
375 // See https://phabricator.wikimedia.org/T44430
376 return $n;
377 }
378
385 abstract protected function mysqlNumRows( $res );
386
391 public function numFields( $res ) {
392 if ( $res instanceof ResultWrapper ) {
393 $res = $res->result;
394 }
395
396 return $this->mysqlNumFields( $res );
397 }
398
405 abstract protected function mysqlNumFields( $res );
406
412 public function fieldName( $res, $n ) {
413 if ( $res instanceof ResultWrapper ) {
414 $res = $res->result;
415 }
416
417 return $this->mysqlFieldName( $res, $n );
418 }
419
427 abstract protected function mysqlFieldName( $res, $n );
428
435 public function fieldType( $res, $n ) {
436 if ( $res instanceof ResultWrapper ) {
437 $res = $res->result;
438 }
439
440 return $this->mysqlFieldType( $res, $n );
441 }
442
450 abstract protected function mysqlFieldType( $res, $n );
451
457 public function dataSeek( $res, $row ) {
458 if ( $res instanceof ResultWrapper ) {
459 $res = $res->result;
460 }
461
462 return $this->mysqlDataSeek( $res, $row );
463 }
464
472 abstract protected function mysqlDataSeek( $res, $row );
473
477 public function lastError() {
478 if ( $this->conn ) {
479 # Even if it's non-zero, it can still be invalid
480 Wikimedia\suppressWarnings();
481 $error = $this->mysqlError( $this->conn );
482 if ( !$error ) {
483 $error = $this->mysqlError();
484 }
485 Wikimedia\restoreWarnings();
486 } else {
487 $error = $this->mysqlError();
488 }
489 if ( $error ) {
490 $error .= ' (' . $this->server . ')';
491 }
492
493 return $error;
494 }
495
502 abstract protected function mysqlError( $conn = null );
503
504 protected function wasQueryTimeout( $error, $errno ) {
505 return $errno == 2062;
506 }
507
515 public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
516 return $this->nativeReplace( $table, $rows, $fname );
517 }
518
519 protected function isInsertSelectSafe( array $insertOptions, array $selectOptions ) {
520 $row = $this->getReplicationSafetyInfo();
521 // For row-based-replication, the resulting changes will be relayed, not the query
522 if ( $row->binlog_format === 'ROW' ) {
523 return true;
524 }
525 // LIMIT requires ORDER BY on a unique key or it is non-deterministic
526 if ( isset( $selectOptions['LIMIT'] ) ) {
527 return false;
528 }
529 // In MySQL, an INSERT SELECT is only replication safe with row-based
530 // replication or if innodb_autoinc_lock_mode is 0. When those
531 // conditions aren't met, use non-native mode.
532 // While we could try to determine if the insert is safe anyway by
533 // checking if the target table has an auto-increment column that
534 // isn't set in $varMap, that seems unlikely to be worth the extra
535 // complexity.
536 return (
537 in_array( 'NO_AUTO_COLUMNS', $insertOptions ) ||
538 (int)$row->innodb_autoinc_lock_mode === 0
539 );
540 }
541
545 protected function getReplicationSafetyInfo() {
546 if ( $this->replicationInfoRow === null ) {
547 $this->replicationInfoRow = $this->selectRow(
548 false,
549 [
550 'innodb_autoinc_lock_mode' => '@@innodb_autoinc_lock_mode',
551 'binlog_format' => '@@binlog_format',
552 ],
553 [],
554 __METHOD__
555 );
556 }
557
559 }
560
574 public function estimateRowCount( $table, $var = '*', $conds = '',
575 $fname = __METHOD__, $options = [], $join_conds = []
576 ) {
577 $conds = $this->normalizeConditions( $conds, $fname );
578 $column = $this->extractSingleFieldFromList( $var );
579 if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
580 $conds[] = "$column IS NOT NULL";
581 }
582
583 $options['EXPLAIN'] = true;
584 $res = $this->select( $table, $var, $conds, $fname, $options, $join_conds );
585 if ( $res === false ) {
586 return false;
587 }
588 if ( !$this->numRows( $res ) ) {
589 return 0;
590 }
591
592 $rows = 1;
593 foreach ( $res as $plan ) {
594 $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
595 }
596
597 return (int)$rows;
598 }
599
600 public function tableExists( $table, $fname = __METHOD__ ) {
601 // Split database and table into proper variables as Database::tableName() returns
602 // shared tables prefixed with their database, which do not work in SHOW TABLES statements
603 list( $database, , $prefix, $table ) = $this->qualifiedTableComponents( $table );
604 $tableName = "{$prefix}{$table}";
605
606 if ( isset( $this->sessionTempTables[$tableName] ) ) {
607 return true; // already known to exist and won't show in SHOW TABLES anyway
608 }
609
610 // We can't use buildLike() here, because it specifies an escape character
611 // other than the backslash, which is the only one supported by SHOW TABLES
612 $encLike = $this->escapeLikeInternal( $tableName, '\\' );
613
614 // If the database has been specified (such as for shared tables), use "FROM"
615 if ( $database !== '' ) {
616 $encDatabase = $this->addIdentifierQuotes( $database );
617 $query = "SHOW TABLES FROM $encDatabase LIKE '$encLike'";
618 } else {
619 $query = "SHOW TABLES LIKE '$encLike'";
620 }
621
622 return $this->query( $query, $fname )->numRows() > 0;
623 }
624
630 public function fieldInfo( $table, $field ) {
631 $table = $this->tableName( $table );
632 $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
633 if ( !$res ) {
634 return false;
635 }
636 $n = $this->mysqlNumFields( $res->result );
637 for ( $i = 0; $i < $n; $i++ ) {
638 $meta = $this->mysqlFetchField( $res->result, $i );
639 if ( $field == $meta->name ) {
640 return new MySQLField( $meta );
641 }
642 }
643
644 return false;
645 }
646
654 abstract protected function mysqlFetchField( $res, $n );
655
665 public function indexInfo( $table, $index, $fname = __METHOD__ ) {
666 # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
667 # SHOW INDEX should work for 3.x and up:
668 # https://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
669 $table = $this->tableName( $table );
670 $index = $this->indexName( $index );
671
672 $sql = 'SHOW INDEX FROM ' . $table;
673 $res = $this->query( $sql, $fname );
674
675 if ( !$res ) {
676 return null;
677 }
678
679 $result = [];
680
681 foreach ( $res as $row ) {
682 if ( $row->Key_name == $index ) {
683 $result[] = $row;
684 }
685 }
686
687 return $result ?: false;
688 }
689
694 public function strencode( $s ) {
695 return $this->mysqlRealEscapeString( $s );
696 }
697
702 abstract protected function mysqlRealEscapeString( $s );
703
704 public function addQuotes( $s ) {
705 if ( is_bool( $s ) ) {
706 // Parent would transform to int, which does not play nice with MySQL type juggling.
707 // When searching for an int in a string column, the strings are cast to int, which
708 // means false would match any string not starting with a number.
709 $s = (string)(int)$s;
710 }
711 return parent::addQuotes( $s );
712 }
713
720 public function addIdentifierQuotes( $s ) {
721 // Characters in the range \u0001-\uFFFF are valid in a quoted identifier
722 // Remove NUL bytes and escape backticks by doubling
723 return '`' . str_replace( [ "\0", '`' ], [ '', '``' ], $s ) . '`';
724 }
725
730 public function isQuotedIdentifier( $name ) {
731 return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
732 }
733
734 public function getLag() {
735 if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
736 return $this->getLagFromPtHeartbeat();
737 } else {
738 return $this->getLagFromSlaveStatus();
739 }
740 }
741
745 protected function getLagDetectionMethod() {
747 }
748
752 protected function getLagFromSlaveStatus() {
753 $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
754 $row = $res ? $res->fetchObject() : false;
755 // If the server is not replicating, there will be no row
756 if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
757 return intval( $row->Seconds_Behind_Master );
758 }
759
760 return false;
761 }
762
766 protected function getLagFromPtHeartbeat() {
768
769 $currentTrxInfo = $this->getRecordedTransactionLagStatus();
770 if ( $currentTrxInfo ) {
771 // There is an active transaction and the initial lag was already queried
772 $staleness = microtime( true ) - $currentTrxInfo['since'];
773 if ( $staleness > self::LAG_STALE_WARN_THRESHOLD ) {
774 // Avoid returning higher and higher lag value due to snapshot age
775 // given that the isolation level will typically be REPEATABLE-READ
776 $this->queryLogger->warning(
777 "Using cached lag value for {db_server} due to active transaction",
778 $this->getLogContext( [ 'method' => __METHOD__, 'age' => $staleness ] )
779 );
780 }
781
782 return $currentTrxInfo['lag'];
783 }
784
785 if ( isset( $options['conds'] ) ) {
786 // Best method for multi-DC setups: use logical channel names
787 $data = $this->getHeartbeatData( $options['conds'] );
788 } else {
789 // Standard method: use master server ID (works with stock pt-heartbeat)
790 $masterInfo = $this->getMasterServerInfo();
791 if ( !$masterInfo ) {
792 $this->queryLogger->error(
793 "Unable to query master of {db_server} for server ID",
794 $this->getLogContext( [
795 'method' => __METHOD__
796 ] )
797 );
798
799 return false; // could not get master server ID
800 }
801
802 $conds = [ 'server_id' => intval( $masterInfo['serverId'] ) ];
803 $data = $this->getHeartbeatData( $conds );
804 }
805
806 list( $time, $nowUnix ) = $data;
807 if ( $time !== null ) {
808 // @time is in ISO format like "2015-09-25T16:48:10.000510"
809 $dateTime = new DateTime( $time, new DateTimeZone( 'UTC' ) );
810 $timeUnix = (int)$dateTime->format( 'U' ) + $dateTime->format( 'u' ) / 1e6;
811
812 return max( $nowUnix - $timeUnix, 0.0 );
813 }
814
815 $this->queryLogger->error(
816 "Unable to find pt-heartbeat row for {db_server}",
817 $this->getLogContext( [
818 'method' => __METHOD__
819 ] )
820 );
821
822 return false;
823 }
824
825 protected function getMasterServerInfo() {
827 $key = $cache->makeGlobalKey(
828 'mysql',
829 'master-info',
830 // Using one key for all cluster replica DBs is preferable
831 $this->getLBInfo( 'clusterMasterHost' ) ?: $this->getServer()
832 );
833
834 return $cache->getWithSetCallback(
835 $key,
836 $cache::TTL_INDEFINITE,
837 function () use ( $cache, $key ) {
838 // Get and leave a lock key in place for a short period
839 if ( !$cache->lock( $key, 0, 10 ) ) {
840 return false; // avoid master connection spike slams
841 }
842
843 $conn = $this->getLazyMasterHandle();
844 if ( !$conn ) {
845 return false; // something is misconfigured
846 }
847
848 // Connect to and query the master; catch errors to avoid outages
849 try {
850 $res = $conn->query( 'SELECT @@server_id AS id', __METHOD__ );
851 $row = $res ? $res->fetchObject() : false;
852 $id = $row ? (int)$row->id : 0;
853 } catch ( DBError $e ) {
854 $id = 0;
855 }
856
857 // Cache the ID if it was retrieved
858 return $id ? [ 'serverId' => $id, 'asOf' => time() ] : false;
859 }
860 );
861 }
862
868 protected function getHeartbeatData( array $conds ) {
869 // Query time and trip time are not counted
870 $nowUnix = microtime( true );
871 // Do not bother starting implicit transactions here
872 $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
873 try {
874 $whereSQL = $this->makeList( $conds, self::LIST_AND );
875 // Use ORDER BY for channel based queries since that field might not be UNIQUE.
876 // Note: this would use "TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6))" but the
877 // percision field is not supported in MySQL <= 5.5.
878 $res = $this->query(
879 "SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1",
880 __METHOD__
881 );
882 $row = $res ? $res->fetchObject() : false;
883 } finally {
884 $this->restoreFlags();
885 }
886
887 return [ $row ? $row->ts : null, $nowUnix ];
888 }
889
890 protected function getApproximateLagStatus() {
891 if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
892 // Disable caching since this is fast enough and we don't wan't
893 // to be *too* pessimistic by having both the cache TTL and the
894 // pt-heartbeat interval count as lag in getSessionLagStatus()
895 return parent::getApproximateLagStatus();
896 }
897
898 $key = $this->srvCache->makeGlobalKey( 'mysql-lag', $this->getServer() );
899 $approxLag = $this->srvCache->get( $key );
900 if ( !$approxLag ) {
901 $approxLag = parent::getApproximateLagStatus();
902 $this->srvCache->set( $key, $approxLag, 1 );
903 }
904
905 return $approxLag;
906 }
907
908 public function masterPosWait( DBMasterPos $pos, $timeout ) {
909 if ( !( $pos instanceof MySQLMasterPos ) ) {
910 throw new InvalidArgumentException( "Position not an instance of MySQLMasterPos" );
911 }
912
913 if ( $this->getLBInfo( 'is static' ) === true ) {
914 return 0; // this is a copy of a read-only dataset with no master DB
915 } elseif ( $this->lastKnownReplicaPos && $this->lastKnownReplicaPos->hasReached( $pos ) ) {
916 return 0; // already reached this point for sure
917 }
918
919 // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
920 if ( $pos->getGTIDs() ) {
921 // Ignore GTIDs from domains exclusive to the master DB (presumably inactive)
922 $rpos = $this->getReplicaPos();
923 $gtidsWait = $rpos ? MySQLMasterPos::getCommonDomainGTIDs( $pos, $rpos ) : [];
924 if ( !$gtidsWait ) {
925 $this->queryLogger->error(
926 "No GTIDs with the same domain between master ($pos) and replica ($rpos)",
927 $this->getLogContext( [
928 'method' => __METHOD__,
929 ] )
930 );
931
932 return -1; // $pos is from the wrong cluster?
933 }
934 // Wait on the GTID set (MariaDB only)
935 $gtidArg = $this->addQuotes( implode( ',', $gtidsWait ) );
936 if ( strpos( $gtidArg, ':' ) !== false ) {
937 // MySQL GTIDs, e.g "source_id:transaction_id"
938 $res = $this->doQuery( "SELECT WAIT_FOR_EXECUTED_GTID_SET($gtidArg, $timeout)" );
939 } else {
940 // MariaDB GTIDs, e.g."domain:server:sequence"
941 $res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" );
942 }
943 } else {
944 // Wait on the binlog coordinates
945 $encFile = $this->addQuotes( $pos->getLogFile() );
946 $encPos = intval( $pos->getLogPosition()[$pos::CORD_EVENT] );
947 $res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" );
948 }
949
950 $row = $res ? $this->fetchRow( $res ) : false;
951 if ( !$row ) {
952 throw new DBExpectedError( $this, "Replication wait failed: {$this->lastError()}" );
953 }
954
955 // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
956 $status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
957 if ( $status === null ) {
958 if ( !$pos->getGTIDs() ) {
959 // T126436: jobs programmed to wait on master positions might be referencing
960 // binlogs with an old master hostname; this makes MASTER_POS_WAIT() return null.
961 // Try to detect this case and treat the replica DB as having reached the given
962 // position (any master switchover already requires that the new master be caught
963 // up before the switch).
964 $replicationPos = $this->getReplicaPos();
965 if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) {
966 $this->lastKnownReplicaPos = $replicationPos;
967 $status = 0;
968 }
969 }
970 } elseif ( $status >= 0 ) {
971 // Remember that this position was reached to save queries next time
972 $this->lastKnownReplicaPos = $pos;
973 }
974
975 return $status;
976 }
977
983 public function getReplicaPos() {
984 $now = microtime( true ); // as-of-time *before* fetching GTID variables
985
986 if ( $this->useGTIDs() ) {
987 // Try to use GTIDs, fallbacking to binlog positions if not possible
988 $data = $this->getServerGTIDs( __METHOD__ );
989 // Use gtid_slave_pos for MariaDB and gtid_executed for MySQL
990 foreach ( [ 'gtid_slave_pos', 'gtid_executed' ] as $name ) {
991 if ( isset( $data[$name] ) && strlen( $data[$name] ) ) {
992 return new MySQLMasterPos( $data[$name], $now );
993 }
994 }
995 }
996
997 $data = $this->getServerRoleStatus( 'SLAVE', __METHOD__ );
998 if ( $data && strlen( $data['Relay_Master_Log_File'] ) ) {
999 return new MySQLMasterPos(
1000 "{$data['Relay_Master_Log_File']}/{$data['Exec_Master_Log_Pos']}",
1001 $now
1002 );
1003 }
1004
1005 return false;
1006 }
1007
1013 public function getMasterPos() {
1014 $now = microtime( true ); // as-of-time *before* fetching GTID variables
1015
1016 $pos = false;
1017 if ( $this->useGTIDs() ) {
1018 // Try to use GTIDs, fallbacking to binlog positions if not possible
1019 $data = $this->getServerGTIDs( __METHOD__ );
1020 // Use gtid_binlog_pos for MariaDB and gtid_executed for MySQL
1021 foreach ( [ 'gtid_binlog_pos', 'gtid_executed' ] as $name ) {
1022 if ( isset( $data[$name] ) && strlen( $data[$name] ) ) {
1023 $pos = new MySQLMasterPos( $data[$name], $now );
1024 break;
1025 }
1026 }
1027 // Filter domains that are inactive or not relevant to the session
1028 if ( $pos ) {
1029 $pos->setActiveOriginServerId( $this->getServerId() );
1030 $pos->setActiveOriginServerUUID( $this->getServerUUID() );
1031 if ( isset( $data['gtid_domain_id'] ) ) {
1032 $pos->setActiveDomain( $data['gtid_domain_id'] );
1033 }
1034 }
1035 }
1036
1037 if ( !$pos ) {
1038 $data = $this->getServerRoleStatus( 'MASTER', __METHOD__ );
1039 if ( $data && strlen( $data['File'] ) ) {
1040 $pos = new MySQLMasterPos( "{$data['File']}/{$data['Position']}", $now );
1041 }
1042 }
1043
1044 return $pos;
1045 }
1046
1051 protected function getServerId() {
1052 return $this->srvCache->getWithSetCallback(
1053 $this->srvCache->makeGlobalKey( 'mysql-server-id', $this->getServer() ),
1054 self::SERVER_ID_CACHE_TTL,
1055 function () {
1056 $res = $this->query( "SELECT @@server_id AS id", __METHOD__ );
1057 return intval( $this->fetchObject( $res )->id );
1058 }
1059 );
1060 }
1061
1065 protected function getServerUUID() {
1066 return $this->srvCache->getWithSetCallback(
1067 $this->srvCache->makeGlobalKey( 'mysql-server-uuid', $this->getServer() ),
1068 self::SERVER_ID_CACHE_TTL,
1069 function () {
1070 $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'server_uuid'" );
1071 $row = $this->fetchObject( $res );
1072
1073 return $row ? $row->Value : null;
1074 }
1075 );
1076 }
1077
1082 protected function getServerGTIDs( $fname = __METHOD__ ) {
1083 $map = [];
1084 // Get global-only variables like gtid_executed
1085 $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_%'", $fname );
1086 foreach ( $res as $row ) {
1087 $map[$row->Variable_name] = $row->Value;
1088 }
1089 // Get session-specific (e.g. gtid_domain_id since that is were writes will log)
1090 $res = $this->query( "SHOW SESSION VARIABLES LIKE 'gtid_%'", $fname );
1091 foreach ( $res as $row ) {
1092 $map[$row->Variable_name] = $row->Value;
1093 }
1094
1095 return $map;
1096 }
1097
1103 protected function getServerRoleStatus( $role, $fname = __METHOD__ ) {
1104 return $this->query( "SHOW $role STATUS", $fname )->fetchRow() ?: [];
1105 }
1106
1107 public function serverIsReadOnly() {
1108 $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__ );
1109 $row = $this->fetchObject( $res );
1110
1111 return $row ? ( strtolower( $row->Value ) === 'on' ) : false;
1112 }
1113
1118 function useIndexClause( $index ) {
1119 return "FORCE INDEX (" . $this->indexName( $index ) . ")";
1120 }
1121
1126 function ignoreIndexClause( $index ) {
1127 return "IGNORE INDEX (" . $this->indexName( $index ) . ")";
1128 }
1129
1134 return 'LOW_PRIORITY';
1135 }
1136
1140 public function getSoftwareLink() {
1141 // MariaDB includes its name in its version string; this is how MariaDB's version of
1142 // the mysql command-line client identifies MariaDB servers (see mariadb_connection()
1143 // in libmysql/libmysql.c).
1144 $version = $this->getServerVersion();
1145 if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
1146 return '[{{int:version-db-mariadb-url}} MariaDB]';
1147 }
1148
1149 // Percona Server's version suffix is not very distinctive, and @@version_comment
1150 // doesn't give the necessary info for source builds, so assume the server is MySQL.
1151 // (Even Percona's version of mysql doesn't try to make the distinction.)
1152 return '[{{int:version-db-mysql-url}} MySQL]';
1153 }
1154
1158 public function getServerVersion() {
1159 // Not using mysql_get_server_info() or similar for consistency: in the handshake,
1160 // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
1161 // it off (see RPL_VERSION_HACK in include/mysql_com.h).
1162 if ( $this->serverVersion === null ) {
1163 $this->serverVersion = $this->selectField( '', 'VERSION()', '', __METHOD__ );
1164 }
1165 return $this->serverVersion;
1166 }
1167
1171 public function setSessionOptions( array $options ) {
1172 if ( isset( $options['connTimeout'] ) ) {
1173 $timeout = (int)$options['connTimeout'];
1174 $this->query( "SET net_read_timeout=$timeout" );
1175 $this->query( "SET net_write_timeout=$timeout" );
1176 }
1177 }
1178
1184 public function streamStatementEnd( &$sql, &$newLine ) {
1185 if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
1186 preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
1187 $this->delimiter = $m[1];
1188 $newLine = '';
1189 }
1190
1191 return parent::streamStatementEnd( $sql, $newLine );
1192 }
1193
1202 public function lockIsFree( $lockName, $method ) {
1203 if ( !parent::lockIsFree( $lockName, $method ) ) {
1204 return false; // already held
1205 }
1206
1207 $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1208 $result = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method );
1209 $row = $this->fetchObject( $result );
1210
1211 return ( $row->lockstatus == 1 );
1212 }
1213
1220 public function lock( $lockName, $method, $timeout = 5 ) {
1221 $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1222 $result = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method );
1223 $row = $this->fetchObject( $result );
1224
1225 if ( $row->lockstatus == 1 ) {
1226 parent::lock( $lockName, $method, $timeout ); // record
1227 return true;
1228 }
1229
1230 $this->queryLogger->info( __METHOD__ . " failed to acquire lock '{lockname}'",
1231 [ 'lockname' => $lockName ] );
1232
1233 return false;
1234 }
1235
1243 public function unlock( $lockName, $method ) {
1244 $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1245 $result = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method );
1246 $row = $this->fetchObject( $result );
1247
1248 if ( $row->lockstatus == 1 ) {
1249 parent::unlock( $lockName, $method ); // record
1250 return true;
1251 }
1252
1253 $this->queryLogger->warning( __METHOD__ . " failed to release lock '$lockName'\n" );
1254
1255 return false;
1256 }
1257
1258 private function makeLockName( $lockName ) {
1259 // https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
1260 // Newer version enforce a 64 char length limit.
1261 return ( strlen( $lockName ) > 64 ) ? sha1( $lockName ) : $lockName;
1262 }
1263
1264 public function namedLocksEnqueue() {
1265 return true;
1266 }
1267
1269 return false; // tied to TCP connection
1270 }
1271
1272 protected function doLockTables( array $read, array $write, $method ) {
1273 $items = [];
1274 foreach ( $write as $table ) {
1275 $items[] = $this->tableName( $table ) . ' WRITE';
1276 }
1277 foreach ( $read as $table ) {
1278 $items[] = $this->tableName( $table ) . ' READ';
1279 }
1280
1281 $sql = "LOCK TABLES " . implode( ',', $items );
1282 $this->query( $sql, $method );
1283
1284 return true;
1285 }
1286
1287 protected function doUnlockTables( $method ) {
1288 $this->query( "UNLOCK TABLES", $method );
1289
1290 return true;
1291 }
1292
1296 public function setBigSelects( $value = true ) {
1297 if ( $value === 'default' ) {
1298 if ( $this->defaultBigSelects === null ) {
1299 # Function hasn't been called before so it must already be set to the default
1300 return;
1301 } else {
1303 }
1304 } elseif ( $this->defaultBigSelects === null ) {
1305 $this->defaultBigSelects =
1306 (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
1307 }
1308 $encValue = $value ? '1' : '0';
1309 $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
1310 }
1311
1323 public function deleteJoin(
1324 $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
1325 ) {
1326 if ( !$conds ) {
1327 throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
1328 }
1329
1330 $delTable = $this->tableName( $delTable );
1331 $joinTable = $this->tableName( $joinTable );
1332 $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
1333
1334 if ( $conds != '*' ) {
1335 $sql .= ' AND ' . $this->makeList( $conds, self::LIST_AND );
1336 }
1337
1338 return $this->query( $sql, $fname );
1339 }
1340
1349 public function upsert( $table, array $rows, array $uniqueIndexes,
1350 array $set, $fname = __METHOD__
1351 ) {
1352 if ( !count( $rows ) ) {
1353 return true; // nothing to do
1354 }
1355
1356 if ( !is_array( reset( $rows ) ) ) {
1357 $rows = [ $rows ];
1358 }
1359
1360 $table = $this->tableName( $table );
1361 $columns = array_keys( $rows[0] );
1362
1363 $sql = "INSERT INTO $table (" . implode( ',', $columns ) . ') VALUES ';
1364 $rowTuples = [];
1365 foreach ( $rows as $row ) {
1366 $rowTuples[] = '(' . $this->makeList( $row ) . ')';
1367 }
1368 $sql .= implode( ',', $rowTuples );
1369 $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, self::LIST_SET );
1370
1371 return (bool)$this->query( $sql, $fname );
1372 }
1373
1379 public function getServerUptime() {
1380 $vars = $this->getMysqlStatus( 'Uptime' );
1381
1382 return (int)$vars['Uptime'];
1383 }
1384
1390 public function wasDeadlock() {
1391 return $this->lastErrno() == 1213;
1392 }
1393
1399 public function wasLockTimeout() {
1400 return $this->lastErrno() == 1205;
1401 }
1402
1408 public function wasReadOnlyError() {
1409 return $this->lastErrno() == 1223 ||
1410 ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
1411 }
1412
1413 public function wasConnectionError( $errno ) {
1414 return $errno == 2013 || $errno == 2006;
1415 }
1416
1417 protected function wasKnownStatementRollbackError() {
1418 $errno = $this->lastErrno();
1419
1420 if ( $errno === 1205 ) { // lock wait timeout
1421 // Note that this is uncached to avoid stale values of SET is used
1422 $row = $this->selectRow(
1423 false,
1424 [ 'innodb_rollback_on_timeout' => '@@innodb_rollback_on_timeout' ],
1425 [],
1426 __METHOD__
1427 );
1428 // https://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html
1429 // https://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html
1430 return $row->innodb_rollback_on_timeout ? false : true;
1431 }
1432
1433 // See https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html
1434 return in_array( $errno, [ 1022, 1216, 1217, 1137 ], true );
1435 }
1436
1445 $oldName, $newName, $temporary = false, $fname = __METHOD__
1446 ) {
1447 $tmp = $temporary ? 'TEMPORARY ' : '';
1448 $newName = $this->addIdentifierQuotes( $newName );
1449 $oldName = $this->addIdentifierQuotes( $oldName );
1450 $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
1451
1452 return $this->query( $query, $fname );
1453 }
1454
1462 public function listTables( $prefix = null, $fname = __METHOD__ ) {
1463 $result = $this->query( "SHOW TABLES", $fname );
1464
1465 $endArray = [];
1466
1467 foreach ( $result as $table ) {
1468 $vars = get_object_vars( $table );
1469 $table = array_pop( $vars );
1470
1471 if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
1472 $endArray[] = $table;
1473 }
1474 }
1475
1476 return $endArray;
1477 }
1478
1484 public function dropTable( $tableName, $fName = __METHOD__ ) {
1485 if ( !$this->tableExists( $tableName, $fName ) ) {
1486 return false;
1487 }
1488
1489 return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
1490 }
1491
1498 private function getMysqlStatus( $which = "%" ) {
1499 $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
1500 $status = [];
1501
1502 foreach ( $res as $row ) {
1503 $status[$row->Variable_name] = $row->Value;
1504 }
1505
1506 return $status;
1507 }
1508
1518 public function listViews( $prefix = null, $fname = __METHOD__ ) {
1519 // The name of the column containing the name of the VIEW
1520 $propertyName = 'Tables_in_' . $this->dbName;
1521
1522 // Query for the VIEWS
1523 $res = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
1524 $allViews = [];
1525 foreach ( $res as $row ) {
1526 array_push( $allViews, $row->$propertyName );
1527 }
1528
1529 if ( is_null( $prefix ) || $prefix === '' ) {
1530 return $allViews;
1531 }
1532
1533 $filteredViews = [];
1534 foreach ( $allViews as $viewName ) {
1535 // Does the name of this VIEW start with the table-prefix?
1536 if ( strpos( $viewName, $prefix ) === 0 ) {
1537 array_push( $filteredViews, $viewName );
1538 }
1539 }
1540
1541 return $filteredViews;
1542 }
1543
1552 public function isView( $name, $prefix = null ) {
1553 return in_array( $name, $this->listViews( $prefix ) );
1554 }
1555
1556 protected function isTransactableQuery( $sql ) {
1557 return parent::isTransactableQuery( $sql ) &&
1558 !preg_match( '/^SELECT\s+(GET|RELEASE|IS_FREE)_LOCK\‍(/', $sql );
1559 }
1560
1565 public function buildIntegerCast( $field ) {
1566 return 'CAST( ' . $field . ' AS SIGNED )';
1567 }
1568
1569 /*
1570 * @return bool Whether GTID support is used (mockable for testing)
1571 */
1572 protected function useGTIDs() {
1573 return $this->useGTIDs;
1574 }
1575}
1576
1577class_alias( DatabaseMysqlBase::class, 'DatabaseMysqlBase' );
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition Setup.php:112
makeGlobalKey( $class, $component=null)
Make a global cache key.
Database error base class.
Definition DBError.php:30
Base class for the more common types of database errors.
Database abstraction object for MySQL.
__construct(array $params)
Additional $params include:
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
getMysqlStatus( $which="%")
Get status information from SHOW STATUS in an associative array.
addQuotes( $s)
Adds quotes and backslashes.
namedLocksEnqueue()
Check to see if a named lock used by lock() use blocking queues.
mysqlFetchArray( $res)
Fetch a result row as an associative and numeric array.
array $lagDetectionOptions
Method to detect replica DB lag.
isInsertSelectSafe(array $insertOptions, array $selectOptions)
dropTable( $tableName, $fName=__METHOD__)
wasReadOnlyError()
Determines if the last failure was due to the database being read-only.
fieldType( $res, $n)
mysql_field_type() wrapper
getMasterPos()
Get the position of the master from SHOW MASTER STATUS.
mysqlFetchField( $res, $n)
Get column information from a result.
string $lagDetectionMethod
Method to detect replica DB lag.
getApproximateLagStatus()
Get a replica DB lag estimate for this server.
mysqlFieldType( $res, $n)
Get the type of the specified field in a result.
doLockTables(array $read, array $write, $method)
Helper function for lockTables() that handles the actual table locking.
getLag()
Get the amount of replication lag for this database server.
open( $server, $user, $password, $dbName)
mysqlSetCharset( $charset)
Set the character set of the MySQL link.
mysqlFreeResult( $res)
Free result memory.
lockIsFree( $lockName, $method)
Check to see if a named lock is available.
masterPosWait(DBMasterPos $pos, $timeout)
Wait for the replica DB to catch up to a given master position.
indexInfo( $table, $index, $fname=__METHOD__)
Get information about an index into an object Returns false if the index does not exist.
estimateRowCount( $table, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Estimate rows in dataset Returns estimated count, based on EXPLAIN output Takes same arguments as Dat...
mysqlDataSeek( $res, $row)
Move internal result pointer.
upsert( $table, array $rows, array $uniqueIndexes, array $set, $fname=__METHOD__)
mysqlError( $conn=null)
Returns the text of the error message from previous MySQL operation.
addIdentifierQuotes( $s)
MySQL uses backticks for identifier quoting instead of the sql standard "double quotes".
mysqlFetchObject( $res)
Fetch a result row as an object.
mysqlFieldName( $res, $n)
Get the name of the specified field in a result.
isTransactableQuery( $sql)
Determine whether a SQL statement is sensitive to isolation level.
lock( $lockName, $method, $timeout=5)
wasQueryTimeout( $error, $errno)
Checks whether the cause of the error is detected to be a timeout.
unlock( $lockName, $method)
FROM MYSQL DOCS: https://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_releas...
bool $utf8Mode
Use experimental UTF-8 transmission encoding.
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
bool $useGTIDs
bool Whether to use GTID methods
mysqlConnect( $realServer)
Open a connection to a MySQL server.
getServerRoleStatus( $role, $fname=__METHOD__)
wasLockTimeout()
Determines if the last failure was due to a lock timeout.
string $sqlMode
sql_mode value to send on connection
connectInitCharset()
Set the character set information right after connection.
mysqlNumRows( $res)
Get number of rows in result.
tableLocksHaveTransactionScope()
Checks if table locks acquired by lockTables() are transaction-bound in their scope.
getServerUptime()
Determines how long the server has been up.
doUnlockTables( $method)
Helper function for unlockTables() that handles the actual table unlocking.
getReplicaPos()
Get the position of the master from SHOW SLAVE STATUS.
listViews( $prefix=null, $fname=__METHOD__)
Lists VIEWs in the database.
isView( $name, $prefix=null)
Differentiates between a TABLE and a VIEW.
tableExists( $table, $fname=__METHOD__)
Query whether a given table exists.
wasDeadlock()
Determines if the last failure was due to a deadlock.
wasConnectionError( $errno)
Do not use this method outside of Database/DBError classes.
deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
mysqlNumFields( $res)
Get number of fields in result.
replace( $table, $uniqueIndexes, $rows, $fname=__METHOD__)
Relational database abstraction object.
Definition Database.php:48
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Single row SELECT wrapper.
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition Database.php:849
selectDB( $db)
Change the current database.
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
string $user
User that this instance is currently connected under the name of.
Definition Database.php:81
getRecordedTransactionLagStatus()
Get the replica DB lag when the current transaction started.
restoreFlags( $state=self::RESTORE_PRIOR)
Restore the flags to their prior state before the last setFlag/clearFlag call.
Definition Database.php:784
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition Database.php:838
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
clearFlag( $flag, $remember=self::REMEMBER_NOTHING)
Clear a flag for this connection.
Definition Database.php:773
qualifiedTableComponents( $name)
Get the table components needed for a query given the currently selected database.
getLBInfo( $name=null)
Get properties passed down from the server info array of the load balancer.
Definition Database.php:614
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition Database.php:889
resource null $conn
Database connection.
Definition Database.php:108
reportConnectionError( $error='Unknown error')
Definition Database.php:997
nativeReplace( $table, $rows, $fname)
REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE statement.
getServer()
Get the server hostname or IP address.
string $password
Password used to establish the current connection.
Definition Database.php:83
escapeLikeInternal( $s, $escapeChar='`')
string $server
Server that this instance is currently connected to.
Definition Database.php:79
normalizeConditions( $conds, $fname)
BagOStuff $srvCache
APC cache.
Definition Database.php:97
doQuery( $sql)
Run a query and return a DBMS-dependent wrapper (that has all IResultWrapper methods)
string $dbName
Database that this instance is currently connected to.
Definition Database.php:85
close()
Close the database connection.
Definition Database.php:900
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
DBMasterPos class for MySQL/MariaDB.
static getCommonDomainGTIDs(MySQLMasterPos $pos, MySQLMasterPos $refPos)
Result wrapper for grabbing data queried from an IDatabase object.
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
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
when a variable name is used in a function
Definition design.txt:94
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2228
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:2783
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1795
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action or null $user:User who performed the tagging when the tagging is subsequent to the action or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, 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. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy: boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition hooks.txt:1051
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition hooks.txt:181
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:2001
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition hooks.txt:1620
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
returning false will NOT prevent logging $e
Definition hooks.txt:2176
An object representing a master or replica DB position in a replicated setup.
lastErrno()
Get the last error number.
$cache
Definition mcc.php:33
$params