MediaWiki REL1_30
DatabaseMysqlBase.php
Go to the documentation of this file.
1<?php
23namespace Wikimedia\Rdbms;
24
25use DateTime;
26use DateTimeZone;
27use MediaWiki;
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;
63
65 private $serverVersion = null;
66
85 function __construct( array $params ) {
86 $this->lagDetectionMethod = isset( $params['lagDetectionMethod'] )
87 ? $params['lagDetectionMethod']
88 : 'Seconds_Behind_Master';
89 $this->lagDetectionOptions = isset( $params['lagDetectionOptions'] )
90 ? $params['lagDetectionOptions']
91 : [];
92 $this->useGTIDs = !empty( $params['useGTIDs' ] );
93 foreach ( [ 'KeyPath', 'CertPath', 'CAFile', 'CAPath', 'Ciphers' ] as $name ) {
94 $var = "ssl{$name}";
95 if ( isset( $params[$var] ) ) {
96 $this->$var = $params[$var];
97 }
98 }
99 $this->sqlMode = isset( $params['sqlMode'] ) ? $params['sqlMode'] : '';
100 $this->utf8Mode = !empty( $params['utf8Mode'] );
101
102 parent::__construct( $params );
103 }
104
108 public function getType() {
109 return 'mysql';
110 }
111
120 public function open( $server, $user, $password, $dbName ) {
121 # Close/unset connection handle
122 $this->close();
123
124 $this->mServer = $server;
125 $this->mUser = $user;
126 $this->mPassword = $password;
127 $this->mDBname = $dbName;
128
129 $this->installErrorHandler();
130 try {
131 $this->mConn = $this->mysqlConnect( $this->mServer );
132 } catch ( Exception $ex ) {
133 $this->restoreErrorHandler();
134 throw $ex;
135 }
136 $error = $this->restoreErrorHandler();
137
138 # Always log connection errors
139 if ( !$this->mConn ) {
140 if ( !$error ) {
141 $error = $this->lastError();
142 }
143 $this->connLogger->error(
144 "Error connecting to {db_server}: {error}",
145 $this->getLogContext( [
146 'method' => __METHOD__,
147 'error' => $error,
148 ] )
149 );
150 $this->connLogger->debug( "DB connection error\n" .
151 "Server: $server, User: $user, Password: " .
152 substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
153
154 $this->reportConnectionError( $error );
155 }
156
157 if ( $dbName != '' ) {
158 MediaWiki\suppressWarnings();
159 $success = $this->selectDB( $dbName );
160 MediaWiki\restoreWarnings();
161 if ( !$success ) {
162 $this->queryLogger->error(
163 "Error selecting database {db_name} on server {db_server}",
164 $this->getLogContext( [
165 'method' => __METHOD__,
166 ] )
167 );
168 $this->queryLogger->debug(
169 "Error selecting database $dbName on server {$this->mServer}" );
170
171 $this->reportConnectionError( "Error selecting database $dbName" );
172 }
173 }
174
175 // Tell the server what we're communicating with
176 if ( !$this->connectInitCharset() ) {
177 $this->reportConnectionError( "Error setting character set" );
178 }
179
180 // Abstract over any insane MySQL defaults
181 $set = [ 'group_concat_max_len = 262144' ];
182 // Set SQL mode, default is turning them all off, can be overridden or skipped with null
183 if ( is_string( $this->sqlMode ) ) {
184 $set[] = 'sql_mode = ' . $this->addQuotes( $this->sqlMode );
185 }
186 // Set any custom settings defined by site config
187 // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html)
188 foreach ( $this->mSessionVars as $var => $val ) {
189 // Escape strings but not numbers to avoid MySQL complaining
190 if ( !is_int( $val ) && !is_float( $val ) ) {
191 $val = $this->addQuotes( $val );
192 }
193 $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val;
194 }
195
196 if ( $set ) {
197 // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
198 $success = $this->doQuery( 'SET ' . implode( ', ', $set ) );
199 if ( !$success ) {
200 $this->queryLogger->error(
201 'Error setting MySQL variables on server {db_server} (check $wgSQLMode)',
202 $this->getLogContext( [
203 'method' => __METHOD__,
204 ] )
205 );
207 'Error setting MySQL variables on server {db_server} (check $wgSQLMode)' );
208 }
209 }
210
211 $this->mOpened = true;
212
213 return true;
214 }
215
220 protected function connectInitCharset() {
221 if ( $this->utf8Mode ) {
222 // Tell the server we're communicating with it in UTF-8.
223 // This may engage various charset conversions.
224 return $this->mysqlSetCharset( 'utf8' );
225 } else {
226 return $this->mysqlSetCharset( 'binary' );
227 }
228 }
229
237 abstract protected function mysqlConnect( $realServer );
238
245 abstract protected function mysqlSetCharset( $charset );
246
251 public function freeResult( $res ) {
252 if ( $res instanceof ResultWrapper ) {
253 $res = $res->result;
254 }
255 MediaWiki\suppressWarnings();
256 $ok = $this->mysqlFreeResult( $res );
257 MediaWiki\restoreWarnings();
258 if ( !$ok ) {
259 throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
260 }
261 }
262
269 abstract protected function mysqlFreeResult( $res );
270
276 public function fetchObject( $res ) {
277 if ( $res instanceof ResultWrapper ) {
278 $res = $res->result;
279 }
280 MediaWiki\suppressWarnings();
281 $row = $this->mysqlFetchObject( $res );
282 MediaWiki\restoreWarnings();
283
284 $errno = $this->lastErrno();
285 // Unfortunately, mysql_fetch_object does not reset the last errno.
286 // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
287 // these are the only errors mysql_fetch_object can cause.
288 // See https://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
289 if ( $errno == 2000 || $errno == 2013 ) {
290 throw new DBUnexpectedError(
291 $this,
292 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() )
293 );
294 }
295
296 return $row;
297 }
298
305 abstract protected function mysqlFetchObject( $res );
306
312 public function fetchRow( $res ) {
313 if ( $res instanceof ResultWrapper ) {
314 $res = $res->result;
315 }
316 MediaWiki\suppressWarnings();
317 $row = $this->mysqlFetchArray( $res );
318 MediaWiki\restoreWarnings();
319
320 $errno = $this->lastErrno();
321 // Unfortunately, mysql_fetch_array does not reset the last errno.
322 // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
323 // these are the only errors mysql_fetch_array can cause.
324 // See https://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
325 if ( $errno == 2000 || $errno == 2013 ) {
326 throw new DBUnexpectedError(
327 $this,
328 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() )
329 );
330 }
331
332 return $row;
333 }
334
341 abstract protected function mysqlFetchArray( $res );
342
348 function numRows( $res ) {
349 if ( $res instanceof ResultWrapper ) {
350 $res = $res->result;
351 }
352 MediaWiki\suppressWarnings();
353 $n = $this->mysqlNumRows( $res );
354 MediaWiki\restoreWarnings();
355
356 // Unfortunately, mysql_num_rows does not reset the last errno.
357 // We are not checking for any errors here, since
358 // these are no errors mysql_num_rows can cause.
359 // See https://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
360 // See https://phabricator.wikimedia.org/T44430
361 return $n;
362 }
363
370 abstract protected function mysqlNumRows( $res );
371
376 public function numFields( $res ) {
377 if ( $res instanceof ResultWrapper ) {
378 $res = $res->result;
379 }
380
381 return $this->mysqlNumFields( $res );
382 }
383
390 abstract protected function mysqlNumFields( $res );
391
397 public function fieldName( $res, $n ) {
398 if ( $res instanceof ResultWrapper ) {
399 $res = $res->result;
400 }
401
402 return $this->mysqlFieldName( $res, $n );
403 }
404
412 abstract protected function mysqlFieldName( $res, $n );
413
420 public function fieldType( $res, $n ) {
421 if ( $res instanceof ResultWrapper ) {
422 $res = $res->result;
423 }
424
425 return $this->mysqlFieldType( $res, $n );
426 }
427
435 abstract protected function mysqlFieldType( $res, $n );
436
442 public function dataSeek( $res, $row ) {
443 if ( $res instanceof ResultWrapper ) {
444 $res = $res->result;
445 }
446
447 return $this->mysqlDataSeek( $res, $row );
448 }
449
457 abstract protected function mysqlDataSeek( $res, $row );
458
462 public function lastError() {
463 if ( $this->mConn ) {
464 # Even if it's non-zero, it can still be invalid
465 MediaWiki\suppressWarnings();
466 $error = $this->mysqlError( $this->mConn );
467 if ( !$error ) {
468 $error = $this->mysqlError();
469 }
470 MediaWiki\restoreWarnings();
471 } else {
472 $error = $this->mysqlError();
473 }
474 if ( $error ) {
475 $error .= ' (' . $this->mServer . ')';
476 }
477
478 return $error;
479 }
480
487 abstract protected function mysqlError( $conn = null );
488
496 public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
497 return $this->nativeReplace( $table, $rows, $fname );
498 }
499
512 public function estimateRowCount( $table, $vars = '*', $conds = '',
513 $fname = __METHOD__, $options = []
514 ) {
515 $options['EXPLAIN'] = true;
516 $res = $this->select( $table, $vars, $conds, $fname, $options );
517 if ( $res === false ) {
518 return false;
519 }
520 if ( !$this->numRows( $res ) ) {
521 return 0;
522 }
523
524 $rows = 1;
525 foreach ( $res as $plan ) {
526 $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
527 }
528
529 return (int)$rows;
530 }
531
532 public function tableExists( $table, $fname = __METHOD__ ) {
533 // Split database and table into proper variables as Database::tableName() returns
534 // shared tables prefixed with their database, which do not work in SHOW TABLES statements
535 list( $database, , $prefix, $table ) = $this->qualifiedTableComponents( $table );
536 $tableName = "{$prefix}{$table}";
537
538 if ( isset( $this->mSessionTempTables[$tableName] ) ) {
539 return true; // already known to exist and won't show in SHOW TABLES anyway
540 }
541
542 // We can't use buildLike() here, because it specifies an escape character
543 // other than the backslash, which is the only one supported by SHOW TABLES
544 $encLike = $this->escapeLikeInternal( $tableName, '\\' );
545
546 // If the database has been specified (such as for shared tables), use "FROM"
547 if ( $database !== '' ) {
548 $encDatabase = $this->addIdentifierQuotes( $database );
549 $query = "SHOW TABLES FROM $encDatabase LIKE '$encLike'";
550 } else {
551 $query = "SHOW TABLES LIKE '$encLike'";
552 }
553
554 return $this->query( $query, $fname )->numRows() > 0;
555 }
556
562 public function fieldInfo( $table, $field ) {
563 $table = $this->tableName( $table );
564 $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
565 if ( !$res ) {
566 return false;
567 }
568 $n = $this->mysqlNumFields( $res->result );
569 for ( $i = 0; $i < $n; $i++ ) {
570 $meta = $this->mysqlFetchField( $res->result, $i );
571 if ( $field == $meta->name ) {
572 return new MySQLField( $meta );
573 }
574 }
575
576 return false;
577 }
578
586 abstract protected function mysqlFetchField( $res, $n );
587
597 public function indexInfo( $table, $index, $fname = __METHOD__ ) {
598 # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
599 # SHOW INDEX should work for 3.x and up:
600 # https://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
601 $table = $this->tableName( $table );
602 $index = $this->indexName( $index );
603
604 $sql = 'SHOW INDEX FROM ' . $table;
605 $res = $this->query( $sql, $fname );
606
607 if ( !$res ) {
608 return null;
609 }
610
611 $result = [];
612
613 foreach ( $res as $row ) {
614 if ( $row->Key_name == $index ) {
615 $result[] = $row;
616 }
617 }
618
619 return empty( $result ) ? false : $result;
620 }
621
626 public function strencode( $s ) {
627 return $this->mysqlRealEscapeString( $s );
628 }
629
634 abstract protected function mysqlRealEscapeString( $s );
635
636 public function addQuotes( $s ) {
637 if ( is_bool( $s ) ) {
638 // Parent would transform to int, which does not play nice with MySQL type juggling.
639 // When searching for an int in a string column, the strings are cast to int, which
640 // means false would match any string not starting with a number.
641 $s = (string)(int)$s;
642 }
643 return parent::addQuotes( $s );
644 }
645
652 public function addIdentifierQuotes( $s ) {
653 // Characters in the range \u0001-\uFFFF are valid in a quoted identifier
654 // Remove NUL bytes and escape backticks by doubling
655 return '`' . str_replace( [ "\0", '`' ], [ '', '``' ], $s ) . '`';
656 }
657
662 public function isQuotedIdentifier( $name ) {
663 return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
664 }
665
666 public function getLag() {
667 if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
668 return $this->getLagFromPtHeartbeat();
669 } else {
670 return $this->getLagFromSlaveStatus();
671 }
672 }
673
677 protected function getLagDetectionMethod() {
679 }
680
684 protected function getLagFromSlaveStatus() {
685 $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
686 $row = $res ? $res->fetchObject() : false;
687 if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
688 return intval( $row->Seconds_Behind_Master );
689 }
690
691 return false;
692 }
693
697 protected function getLagFromPtHeartbeat() {
699
700 if ( isset( $options['conds'] ) ) {
701 // Best method for multi-DC setups: use logical channel names
702 $data = $this->getHeartbeatData( $options['conds'] );
703 } else {
704 // Standard method: use master server ID (works with stock pt-heartbeat)
705 $masterInfo = $this->getMasterServerInfo();
706 if ( !$masterInfo ) {
707 $this->queryLogger->error(
708 "Unable to query master of {db_server} for server ID",
709 $this->getLogContext( [
710 'method' => __METHOD__
711 ] )
712 );
713
714 return false; // could not get master server ID
715 }
716
717 $conds = [ 'server_id' => intval( $masterInfo['serverId'] ) ];
718 $data = $this->getHeartbeatData( $conds );
719 }
720
721 list( $time, $nowUnix ) = $data;
722 if ( $time !== null ) {
723 // @time is in ISO format like "2015-09-25T16:48:10.000510"
724 $dateTime = new DateTime( $time, new DateTimeZone( 'UTC' ) );
725 $timeUnix = (int)$dateTime->format( 'U' ) + $dateTime->format( 'u' ) / 1e6;
726
727 return max( $nowUnix - $timeUnix, 0.0 );
728 }
729
730 $this->queryLogger->error(
731 "Unable to find pt-heartbeat row for {db_server}",
732 $this->getLogContext( [
733 'method' => __METHOD__
734 ] )
735 );
736
737 return false;
738 }
739
740 protected function getMasterServerInfo() {
742 $key = $cache->makeGlobalKey(
743 'mysql',
744 'master-info',
745 // Using one key for all cluster replica DBs is preferable
746 $this->getLBInfo( 'clusterMasterHost' ) ?: $this->getServer()
747 );
748
749 return $cache->getWithSetCallback(
750 $key,
751 $cache::TTL_INDEFINITE,
752 function () use ( $cache, $key ) {
753 // Get and leave a lock key in place for a short period
754 if ( !$cache->lock( $key, 0, 10 ) ) {
755 return false; // avoid master connection spike slams
756 }
757
758 $conn = $this->getLazyMasterHandle();
759 if ( !$conn ) {
760 return false; // something is misconfigured
761 }
762
763 // Connect to and query the master; catch errors to avoid outages
764 try {
765 $res = $conn->query( 'SELECT @@server_id AS id', __METHOD__ );
766 $row = $res ? $res->fetchObject() : false;
767 $id = $row ? (int)$row->id : 0;
768 } catch ( DBError $e ) {
769 $id = 0;
770 }
771
772 // Cache the ID if it was retrieved
773 return $id ? [ 'serverId' => $id, 'asOf' => time() ] : false;
774 }
775 );
776 }
777
783 protected function getHeartbeatData( array $conds ) {
784 // Query time and trip time are not counted
785 $nowUnix = microtime( true );
786 // Do not bother starting implicit transactions here
787 $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
788 try {
789 $whereSQL = $this->makeList( $conds, self::LIST_AND );
790 // Use ORDER BY for channel based queries since that field might not be UNIQUE.
791 // Note: this would use "TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6))" but the
792 // percision field is not supported in MySQL <= 5.5.
793 $res = $this->query(
794 "SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1"
795 );
796 $row = $res ? $res->fetchObject() : false;
797 } finally {
798 $this->restoreFlags();
799 }
800
801 return [ $row ? $row->ts : null, $nowUnix ];
802 }
803
804 protected function getApproximateLagStatus() {
805 if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
806 // Disable caching since this is fast enough and we don't wan't
807 // to be *too* pessimistic by having both the cache TTL and the
808 // pt-heartbeat interval count as lag in getSessionLagStatus()
809 return parent::getApproximateLagStatus();
810 }
811
812 $key = $this->srvCache->makeGlobalKey( 'mysql-lag', $this->getServer() );
813 $approxLag = $this->srvCache->get( $key );
814 if ( !$approxLag ) {
815 $approxLag = parent::getApproximateLagStatus();
816 $this->srvCache->set( $key, $approxLag, 1 );
817 }
818
819 return $approxLag;
820 }
821
822 public function masterPosWait( DBMasterPos $pos, $timeout ) {
823 if ( !( $pos instanceof MySQLMasterPos ) ) {
824 throw new InvalidArgumentException( "Position not an instance of MySQLMasterPos" );
825 }
826
827 if ( $this->getLBInfo( 'is static' ) === true ) {
828 return 0; // this is a copy of a read-only dataset with no master DB
829 } elseif ( $this->lastKnownReplicaPos && $this->lastKnownReplicaPos->hasReached( $pos ) ) {
830 return 0; // already reached this point for sure
831 }
832
833 // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
834 if ( $this->useGTIDs && $pos->gtids ) {
835 // Wait on the GTID set (MariaDB only)
836 $gtidArg = $this->addQuotes( implode( ',', $pos->gtids ) );
837 $res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" );
838 } else {
839 // Wait on the binlog coordinates
840 $encFile = $this->addQuotes( $pos->file );
841 $encPos = intval( $pos->pos );
842 $res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" );
843 }
844
845 $row = $res ? $this->fetchRow( $res ) : false;
846 if ( !$row ) {
847 throw new DBExpectedError( $this,
848 "MASTER_POS_WAIT() or MASTER_GTID_WAIT() failed: {$this->lastError()}" );
849 }
850
851 // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
852 $status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
853 if ( $status === null ) {
854 // T126436: jobs programmed to wait on master positions might be referencing binlogs
855 // with an old master hostname. Such calls make MASTER_POS_WAIT() return null. Try
856 // to detect this and treat the replica DB as having reached the position; a proper master
857 // switchover already requires that the new master be caught up before the switch.
858 $replicationPos = $this->getReplicaPos();
859 if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) {
860 $this->lastKnownReplicaPos = $replicationPos;
861 $status = 0;
862 }
863 } elseif ( $status >= 0 ) {
864 // Remember that this position was reached to save queries next time
865 $this->lastKnownReplicaPos = $pos;
866 }
867
868 return $status;
869 }
870
876 public function getReplicaPos() {
877 $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
878 $row = $this->fetchObject( $res );
879
880 if ( $row ) {
881 $pos = isset( $row->Exec_master_log_pos )
882 ? $row->Exec_master_log_pos
883 : $row->Exec_Master_Log_Pos;
884 // Also fetch the last-applied GTID set (MariaDB)
885 if ( $this->useGTIDs ) {
886 $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_slave_pos'", __METHOD__ );
887 $gtidRow = $this->fetchObject( $res );
888 $gtidSet = $gtidRow ? $gtidRow->Value : '';
889 } else {
890 $gtidSet = '';
891 }
892
893 return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos, $gtidSet );
894 } else {
895 return false;
896 }
897 }
898
904 public function getMasterPos() {
905 $res = $this->query( 'SHOW MASTER STATUS', __METHOD__ );
906 $row = $this->fetchObject( $res );
907
908 if ( $row ) {
909 // Also fetch the last-written GTID set (MariaDB)
910 if ( $this->useGTIDs ) {
911 $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_pos'", __METHOD__ );
912 $gtidRow = $this->fetchObject( $res );
913 $gtidSet = $gtidRow ? $gtidRow->Value : '';
914 } else {
915 $gtidSet = '';
916 }
917
918 return new MySQLMasterPos( $row->File, $row->Position, $gtidSet );
919 } else {
920 return false;
921 }
922 }
923
924 public function serverIsReadOnly() {
925 $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__ );
926 $row = $this->fetchObject( $res );
927
928 return $row ? ( strtolower( $row->Value ) === 'on' ) : false;
929 }
930
935 function useIndexClause( $index ) {
936 return "FORCE INDEX (" . $this->indexName( $index ) . ")";
937 }
938
943 function ignoreIndexClause( $index ) {
944 return "IGNORE INDEX (" . $this->indexName( $index ) . ")";
945 }
946
950 function lowPriorityOption() {
951 return 'LOW_PRIORITY';
952 }
953
957 public function getSoftwareLink() {
958 // MariaDB includes its name in its version string; this is how MariaDB's version of
959 // the mysql command-line client identifies MariaDB servers (see mariadb_connection()
960 // in libmysql/libmysql.c).
961 $version = $this->getServerVersion();
962 if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
963 return '[{{int:version-db-mariadb-url}} MariaDB]';
964 }
965
966 // Percona Server's version suffix is not very distinctive, and @@version_comment
967 // doesn't give the necessary info for source builds, so assume the server is MySQL.
968 // (Even Percona's version of mysql doesn't try to make the distinction.)
969 return '[{{int:version-db-mysql-url}} MySQL]';
970 }
971
975 public function getServerVersion() {
976 // Not using mysql_get_server_info() or similar for consistency: in the handshake,
977 // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
978 // it off (see RPL_VERSION_HACK in include/mysql_com.h).
979 if ( $this->serverVersion === null ) {
980 $this->serverVersion = $this->selectField( '', 'VERSION()', '', __METHOD__ );
981 }
983 }
984
988 public function setSessionOptions( array $options ) {
989 if ( isset( $options['connTimeout'] ) ) {
990 $timeout = (int)$options['connTimeout'];
991 $this->query( "SET net_read_timeout=$timeout" );
992 $this->query( "SET net_write_timeout=$timeout" );
993 }
994 }
995
1001 public function streamStatementEnd( &$sql, &$newLine ) {
1002 if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
1003 preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
1004 $this->delimiter = $m[1];
1005 $newLine = '';
1006 }
1007
1008 return parent::streamStatementEnd( $sql, $newLine );
1009 }
1010
1019 public function lockIsFree( $lockName, $method ) {
1020 $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1021 $result = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method );
1022 $row = $this->fetchObject( $result );
1023
1024 return ( $row->lockstatus == 1 );
1025 }
1026
1033 public function lock( $lockName, $method, $timeout = 5 ) {
1034 $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1035 $result = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method );
1036 $row = $this->fetchObject( $result );
1037
1038 if ( $row->lockstatus == 1 ) {
1039 parent::lock( $lockName, $method, $timeout ); // record
1040 return true;
1041 }
1042
1043 $this->queryLogger->warning( __METHOD__ . " failed to acquire lock '$lockName'\n" );
1044
1045 return false;
1046 }
1047
1055 public function unlock( $lockName, $method ) {
1056 $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1057 $result = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method );
1058 $row = $this->fetchObject( $result );
1059
1060 if ( $row->lockstatus == 1 ) {
1061 parent::unlock( $lockName, $method ); // record
1062 return true;
1063 }
1064
1065 $this->queryLogger->warning( __METHOD__ . " failed to release lock '$lockName'\n" );
1066
1067 return false;
1068 }
1069
1070 private function makeLockName( $lockName ) {
1071 // https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
1072 // Newer version enforce a 64 char length limit.
1073 return ( strlen( $lockName ) > 64 ) ? sha1( $lockName ) : $lockName;
1074 }
1075
1076 public function namedLocksEnqueue() {
1077 return true;
1078 }
1079
1081 return false; // tied to TCP connection
1082 }
1083
1084 protected function doLockTables( array $read, array $write, $method ) {
1085 $items = [];
1086 foreach ( $write as $table ) {
1087 $items[] = $this->tableName( $table ) . ' WRITE';
1088 }
1089 foreach ( $read as $table ) {
1090 $items[] = $this->tableName( $table ) . ' READ';
1091 }
1092
1093 $sql = "LOCK TABLES " . implode( ',', $items );
1094 $this->query( $sql, $method );
1095
1096 return true;
1097 }
1098
1099 protected function doUnlockTables( $method ) {
1100 $this->query( "UNLOCK TABLES", $method );
1101
1102 return true;
1103 }
1104
1108 public function setBigSelects( $value = true ) {
1109 if ( $value === 'default' ) {
1110 if ( $this->mDefaultBigSelects === null ) {
1111 # Function hasn't been called before so it must already be set to the default
1112 return;
1113 } else {
1115 }
1116 } elseif ( $this->mDefaultBigSelects === null ) {
1117 $this->mDefaultBigSelects =
1118 (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
1119 }
1120 $encValue = $value ? '1' : '0';
1121 $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
1122 }
1123
1135 public function deleteJoin(
1136 $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
1137 ) {
1138 if ( !$conds ) {
1139 throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
1140 }
1141
1142 $delTable = $this->tableName( $delTable );
1143 $joinTable = $this->tableName( $joinTable );
1144 $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
1145
1146 if ( $conds != '*' ) {
1147 $sql .= ' AND ' . $this->makeList( $conds, self::LIST_AND );
1148 }
1149
1150 return $this->query( $sql, $fname );
1151 }
1152
1161 public function upsert( $table, array $rows, array $uniqueIndexes,
1162 array $set, $fname = __METHOD__
1163 ) {
1164 if ( !count( $rows ) ) {
1165 return true; // nothing to do
1166 }
1167
1168 if ( !is_array( reset( $rows ) ) ) {
1169 $rows = [ $rows ];
1170 }
1171
1172 $table = $this->tableName( $table );
1173 $columns = array_keys( $rows[0] );
1174
1175 $sql = "INSERT INTO $table (" . implode( ',', $columns ) . ') VALUES ';
1176 $rowTuples = [];
1177 foreach ( $rows as $row ) {
1178 $rowTuples[] = '(' . $this->makeList( $row ) . ')';
1179 }
1180 $sql .= implode( ',', $rowTuples );
1181 $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, self::LIST_SET );
1182
1183 return (bool)$this->query( $sql, $fname );
1184 }
1185
1191 public function getServerUptime() {
1192 $vars = $this->getMysqlStatus( 'Uptime' );
1193
1194 return (int)$vars['Uptime'];
1195 }
1196
1202 public function wasDeadlock() {
1203 return $this->lastErrno() == 1213;
1204 }
1205
1211 public function wasLockTimeout() {
1212 return $this->lastErrno() == 1205;
1213 }
1214
1215 public function wasErrorReissuable() {
1216 return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
1217 }
1218
1224 public function wasReadOnlyError() {
1225 return $this->lastErrno() == 1223 ||
1226 ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
1227 }
1228
1229 public function wasConnectionError( $errno ) {
1230 return $errno == 2013 || $errno == 2006;
1231 }
1232
1241 $oldName, $newName, $temporary = false, $fname = __METHOD__
1242 ) {
1243 $tmp = $temporary ? 'TEMPORARY ' : '';
1244 $newName = $this->addIdentifierQuotes( $newName );
1245 $oldName = $this->addIdentifierQuotes( $oldName );
1246 $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
1247
1248 return $this->query( $query, $fname );
1249 }
1250
1258 public function listTables( $prefix = null, $fname = __METHOD__ ) {
1259 $result = $this->query( "SHOW TABLES", $fname );
1260
1261 $endArray = [];
1262
1263 foreach ( $result as $table ) {
1264 $vars = get_object_vars( $table );
1265 $table = array_pop( $vars );
1266
1267 if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
1268 $endArray[] = $table;
1269 }
1270 }
1271
1272 return $endArray;
1273 }
1274
1280 public function dropTable( $tableName, $fName = __METHOD__ ) {
1281 if ( !$this->tableExists( $tableName, $fName ) ) {
1282 return false;
1283 }
1284
1285 return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
1286 }
1287
1294 private function getMysqlStatus( $which = "%" ) {
1295 $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
1296 $status = [];
1297
1298 foreach ( $res as $row ) {
1299 $status[$row->Variable_name] = $row->Value;
1300 }
1301
1302 return $status;
1303 }
1304
1314 public function listViews( $prefix = null, $fname = __METHOD__ ) {
1315 // The name of the column containing the name of the VIEW
1316 $propertyName = 'Tables_in_' . $this->mDBname;
1317
1318 // Query for the VIEWS
1319 $res = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
1320 $allViews = [];
1321 foreach ( $res as $row ) {
1322 array_push( $allViews, $row->$propertyName );
1323 }
1324
1325 if ( is_null( $prefix ) || $prefix === '' ) {
1326 return $allViews;
1327 }
1328
1329 $filteredViews = [];
1330 foreach ( $allViews as $viewName ) {
1331 // Does the name of this VIEW start with the table-prefix?
1332 if ( strpos( $viewName, $prefix ) === 0 ) {
1333 array_push( $filteredViews, $viewName );
1334 }
1335 }
1336
1337 return $filteredViews;
1338 }
1339
1348 public function isView( $name, $prefix = null ) {
1349 return in_array( $name, $this->listViews( $prefix ) );
1350 }
1351
1358 protected function indexName( $index ) {
1373 $renamed = [
1374 'ar_usertext_timestamp' => 'usertext_timestamp',
1375 'un_user_id' => 'user_id',
1376 'un_user_ip' => 'user_ip',
1377 ];
1378
1379 if ( isset( $renamed[$index] ) ) {
1380 return $renamed[$index];
1381 } else {
1382 return $index;
1383 }
1384 }
1385}
1386
1387class_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( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition Setup.php:36
makeGlobalKey()
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.
dropTable( $tableName, $fName=__METHOD__)
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
wasReadOnlyError()
Determines if the last failure was due to the database being read-only.
estimateRowCount( $table, $vars=' *', $conds='', $fname=__METHOD__, $options=[])
Estimate rows in dataset Returns estimated count, based on EXPLAIN output Takes same arguments as Dat...
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.
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.
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.
wasErrorReissuable()
Determines if the last query error was due to a dropped connection and should be dealt with by pingin...
lock( $lockName, $method, $timeout=5)
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.
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:45
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition Database.php:702
selectDB( $db)
Change the current database.
restoreFlags( $state=self::RESTORE_PRIOR)
Restore the flags to their prior state before the last setFlag/clearFlag call.
Definition Database.php:637
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.
clearFlag( $flag, $remember=self::REMEMBER_NOTHING)
Clear a flag for this connection.
Definition Database.php:630
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:501
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition Database.php:742
reportConnectionError( $error='Unknown error')
Definition Database.php:793
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.
escapeLikeInternal( $s, $escapeChar='`')
bool null $mDefaultBigSelects
Definition Database.php:116
BagOStuff $srvCache
APC cache.
Definition Database.php:83
doQuery( $sql)
The DBMS-dependent part of query()
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.
DBMasterPos class for MySQL/MariaDB.
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
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2198
namespace being checked & $result
Definition hooks.txt:2293
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
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1778
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:1971
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). '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:1049
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:1610
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
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
returning false will NOT prevent logging $e
Definition hooks.txt:2146
An object representing a master or replica DB position in a replicated setup.
lastErrno()
Get the last error number.
$cache
Definition mcc.php:33
A helper class for throttling authentication attempts.
$params