MediaWiki  1.28.1
DatabaseMysqlBase.php
Go to the documentation of this file.
1 <?php
32 abstract class DatabaseMysqlBase extends Database {
38  protected $lagDetectionOptions = [];
40  protected $useGTIDs = false;
42  protected $sslKeyPath;
44  protected $sslCertPath;
46  protected $sslCAPath;
48  protected $sslCiphers;
50  protected $sqlMode;
52  protected $utf8Mode;
53 
55  private $serverVersion = null;
56 
74  function __construct( array $params ) {
75  $this->lagDetectionMethod = isset( $params['lagDetectionMethod'] )
76  ? $params['lagDetectionMethod']
77  : 'Seconds_Behind_Master';
78  $this->lagDetectionOptions = isset( $params['lagDetectionOptions'] )
79  ? $params['lagDetectionOptions']
80  : [];
81  $this->useGTIDs = !empty( $params['useGTIDs' ] );
82  foreach ( [ 'KeyPath', 'CertPath', 'CAPath', 'Ciphers' ] as $name ) {
83  $var = "ssl{$name}";
84  if ( isset( $params[$var] ) ) {
85  $this->$var = $params[$var];
86  }
87  }
88  $this->sqlMode = isset( $params['sqlMode'] ) ? $params['sqlMode'] : '';
89  $this->utf8Mode = !empty( $params['utf8Mode'] );
90 
91  parent::__construct( $params );
92  }
93 
97  function getType() {
98  return 'mysql';
99  }
100 
109  function open( $server, $user, $password, $dbName ) {
110  # Close/unset connection handle
111  $this->close();
112 
113  $this->mServer = $server;
114  $this->mUser = $user;
115  $this->mPassword = $password;
116  $this->mDBname = $dbName;
117 
118  $this->installErrorHandler();
119  try {
120  $this->mConn = $this->mysqlConnect( $this->mServer );
121  } catch ( Exception $ex ) {
122  $this->restoreErrorHandler();
123  throw $ex;
124  }
125  $error = $this->restoreErrorHandler();
126 
127  # Always log connection errors
128  if ( !$this->mConn ) {
129  if ( !$error ) {
130  $error = $this->lastError();
131  }
132  $this->connLogger->error(
133  "Error connecting to {db_server}: {error}",
134  $this->getLogContext( [
135  'method' => __METHOD__,
136  'error' => $error,
137  ] )
138  );
139  $this->connLogger->debug( "DB connection error\n" .
140  "Server: $server, User: $user, Password: " .
141  substr( $password, 0, 3 ) . "..., error: " . $error . "\n" );
142 
143  $this->reportConnectionError( $error );
144  }
145 
146  if ( $dbName != '' ) {
147  MediaWiki\suppressWarnings();
148  $success = $this->selectDB( $dbName );
149  MediaWiki\restoreWarnings();
150  if ( !$success ) {
151  $this->queryLogger->error(
152  "Error selecting database {db_name} on server {db_server}",
153  $this->getLogContext( [
154  'method' => __METHOD__,
155  ] )
156  );
157  $this->queryLogger->debug(
158  "Error selecting database $dbName on server {$this->mServer}" );
159 
160  $this->reportConnectionError( "Error selecting database $dbName" );
161  }
162  }
163 
164  // Tell the server what we're communicating with
165  if ( !$this->connectInitCharset() ) {
166  $this->reportConnectionError( "Error setting character set" );
167  }
168 
169  // Abstract over any insane MySQL defaults
170  $set = [ 'group_concat_max_len = 262144' ];
171  // Set SQL mode, default is turning them all off, can be overridden or skipped with null
172  if ( is_string( $this->sqlMode ) ) {
173  $set[] = 'sql_mode = ' . $this->addQuotes( $this->sqlMode );
174  }
175  // Set any custom settings defined by site config
176  // (e.g. https://dev.mysql.com/doc/refman/4.1/en/innodb-parameters.html)
177  foreach ( $this->mSessionVars as $var => $val ) {
178  // Escape strings but not numbers to avoid MySQL complaining
179  if ( !is_int( $val ) && !is_float( $val ) ) {
180  $val = $this->addQuotes( $val );
181  }
182  $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val;
183  }
184 
185  if ( $set ) {
186  // Use doQuery() to avoid opening implicit transactions (DBO_TRX)
187  $success = $this->doQuery( 'SET ' . implode( ', ', $set ) );
188  if ( !$success ) {
189  $this->queryLogger->error(
190  'Error setting MySQL variables on server {db_server} (check $wgSQLMode)',
191  $this->getLogContext( [
192  'method' => __METHOD__,
193  ] )
194  );
195  $this->reportConnectionError(
196  'Error setting MySQL variables on server {db_server} (check $wgSQLMode)' );
197  }
198  }
199 
200  $this->mOpened = true;
201 
202  return true;
203  }
204 
209  protected function connectInitCharset() {
210  if ( $this->utf8Mode ) {
211  // Tell the server we're communicating with it in UTF-8.
212  // This may engage various charset conversions.
213  return $this->mysqlSetCharset( 'utf8' );
214  } else {
215  return $this->mysqlSetCharset( 'binary' );
216  }
217  }
218 
226  abstract protected function mysqlConnect( $realServer );
227 
234  abstract protected function mysqlSetCharset( $charset );
235 
240  function freeResult( $res ) {
241  if ( $res instanceof ResultWrapper ) {
242  $res = $res->result;
243  }
244  MediaWiki\suppressWarnings();
245  $ok = $this->mysqlFreeResult( $res );
246  MediaWiki\restoreWarnings();
247  if ( !$ok ) {
248  throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
249  }
250  }
251 
258  abstract protected function mysqlFreeResult( $res );
259 
265  function fetchObject( $res ) {
266  if ( $res instanceof ResultWrapper ) {
267  $res = $res->result;
268  }
269  MediaWiki\suppressWarnings();
270  $row = $this->mysqlFetchObject( $res );
271  MediaWiki\restoreWarnings();
272 
273  $errno = $this->lastErrno();
274  // Unfortunately, mysql_fetch_object does not reset the last errno.
275  // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
276  // these are the only errors mysql_fetch_object can cause.
277  // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
278  if ( $errno == 2000 || $errno == 2013 ) {
279  throw new DBUnexpectedError(
280  $this,
281  'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() )
282  );
283  }
284 
285  return $row;
286  }
287 
294  abstract protected function mysqlFetchObject( $res );
295 
301  function fetchRow( $res ) {
302  if ( $res instanceof ResultWrapper ) {
303  $res = $res->result;
304  }
305  MediaWiki\suppressWarnings();
306  $row = $this->mysqlFetchArray( $res );
307  MediaWiki\restoreWarnings();
308 
309  $errno = $this->lastErrno();
310  // Unfortunately, mysql_fetch_array does not reset the last errno.
311  // Only check for CR_SERVER_LOST and CR_UNKNOWN_ERROR, as
312  // these are the only errors mysql_fetch_array can cause.
313  // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
314  if ( $errno == 2000 || $errno == 2013 ) {
315  throw new DBUnexpectedError(
316  $this,
317  'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() )
318  );
319  }
320 
321  return $row;
322  }
323 
330  abstract protected function mysqlFetchArray( $res );
331 
337  function numRows( $res ) {
338  if ( $res instanceof ResultWrapper ) {
339  $res = $res->result;
340  }
341  MediaWiki\suppressWarnings();
342  $n = $this->mysqlNumRows( $res );
343  MediaWiki\restoreWarnings();
344 
345  // Unfortunately, mysql_num_rows does not reset the last errno.
346  // We are not checking for any errors here, since
347  // these are no errors mysql_num_rows can cause.
348  // See http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html.
349  // See https://phabricator.wikimedia.org/T44430
350  return $n;
351  }
352 
359  abstract protected function mysqlNumRows( $res );
360 
365  function numFields( $res ) {
366  if ( $res instanceof ResultWrapper ) {
367  $res = $res->result;
368  }
369 
370  return $this->mysqlNumFields( $res );
371  }
372 
379  abstract protected function mysqlNumFields( $res );
380 
386  function fieldName( $res, $n ) {
387  if ( $res instanceof ResultWrapper ) {
388  $res = $res->result;
389  }
390 
391  return $this->mysqlFieldName( $res, $n );
392  }
393 
401  abstract protected function mysqlFieldName( $res, $n );
402 
409  public function fieldType( $res, $n ) {
410  if ( $res instanceof ResultWrapper ) {
411  $res = $res->result;
412  }
413 
414  return $this->mysqlFieldType( $res, $n );
415  }
416 
424  abstract protected function mysqlFieldType( $res, $n );
425 
431  function dataSeek( $res, $row ) {
432  if ( $res instanceof ResultWrapper ) {
433  $res = $res->result;
434  }
435 
436  return $this->mysqlDataSeek( $res, $row );
437  }
438 
446  abstract protected function mysqlDataSeek( $res, $row );
447 
451  function lastError() {
452  if ( $this->mConn ) {
453  # Even if it's non-zero, it can still be invalid
454  MediaWiki\suppressWarnings();
455  $error = $this->mysqlError( $this->mConn );
456  if ( !$error ) {
457  $error = $this->mysqlError();
458  }
459  MediaWiki\restoreWarnings();
460  } else {
461  $error = $this->mysqlError();
462  }
463  if ( $error ) {
464  $error .= ' (' . $this->mServer . ')';
465  }
466 
467  return $error;
468  }
469 
476  abstract protected function mysqlError( $conn = null );
477 
485  function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
486  return $this->nativeReplace( $table, $rows, $fname );
487  }
488 
501  public function estimateRowCount( $table, $vars = '*', $conds = '',
502  $fname = __METHOD__, $options = []
503  ) {
504  $options['EXPLAIN'] = true;
505  $res = $this->select( $table, $vars, $conds, $fname, $options );
506  if ( $res === false ) {
507  return false;
508  }
509  if ( !$this->numRows( $res ) ) {
510  return 0;
511  }
512 
513  $rows = 1;
514  foreach ( $res as $plan ) {
515  $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
516  }
517 
518  return (int)$rows;
519  }
520 
521  function tableExists( $table, $fname = __METHOD__ ) {
522  $table = $this->tableName( $table, 'raw' );
523  if ( isset( $this->mSessionTempTables[$table] ) ) {
524  return true; // already known to exist and won't show in SHOW TABLES anyway
525  }
526 
527  $encLike = $this->buildLike( $table );
528 
529  return $this->query( "SHOW TABLES $encLike", $fname )->numRows() > 0;
530  }
531 
537  function fieldInfo( $table, $field ) {
538  $table = $this->tableName( $table );
539  $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
540  if ( !$res ) {
541  return false;
542  }
543  $n = $this->mysqlNumFields( $res->result );
544  for ( $i = 0; $i < $n; $i++ ) {
545  $meta = $this->mysqlFetchField( $res->result, $i );
546  if ( $field == $meta->name ) {
547  return new MySQLField( $meta );
548  }
549  }
550 
551  return false;
552  }
553 
561  abstract protected function mysqlFetchField( $res, $n );
562 
572  function indexInfo( $table, $index, $fname = __METHOD__ ) {
573  # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
574  # SHOW INDEX should work for 3.x and up:
575  # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
576  $table = $this->tableName( $table );
577  $index = $this->indexName( $index );
578 
579  $sql = 'SHOW INDEX FROM ' . $table;
580  $res = $this->query( $sql, $fname );
581 
582  if ( !$res ) {
583  return null;
584  }
585 
586  $result = [];
587 
588  foreach ( $res as $row ) {
589  if ( $row->Key_name == $index ) {
590  $result[] = $row;
591  }
592  }
593 
594  return empty( $result ) ? false : $result;
595  }
596 
601  function strencode( $s ) {
602  return $this->mysqlRealEscapeString( $s );
603  }
604 
609  abstract protected function mysqlRealEscapeString( $s );
610 
611  public function addQuotes( $s ) {
612  if ( is_bool( $s ) ) {
613  // Parent would transform to int, which does not play nice with MySQL type juggling.
614  // When searching for an int in a string column, the strings are cast to int, which
615  // means false would match any string not starting with a number.
616  $s = (string)(int)$s;
617  }
618  return parent::addQuotes( $s );
619  }
620 
627  public function addIdentifierQuotes( $s ) {
628  // Characters in the range \u0001-\uFFFF are valid in a quoted identifier
629  // Remove NUL bytes and escape backticks by doubling
630  return '`' . str_replace( [ "\0", '`' ], [ '', '``' ], $s ) . '`';
631  }
632 
637  public function isQuotedIdentifier( $name ) {
638  return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
639  }
640 
641  function getLag() {
642  if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
643  return $this->getLagFromPtHeartbeat();
644  } else {
645  return $this->getLagFromSlaveStatus();
646  }
647  }
648 
652  protected function getLagDetectionMethod() {
654  }
655 
659  protected function getLagFromSlaveStatus() {
660  $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
661  $row = $res ? $res->fetchObject() : false;
662  if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
663  return intval( $row->Seconds_Behind_Master );
664  }
665 
666  return false;
667  }
668 
672  protected function getLagFromPtHeartbeat() {
674 
675  if ( isset( $options['conds'] ) ) {
676  // Best method for multi-DC setups: use logical channel names
677  $data = $this->getHeartbeatData( $options['conds'] );
678  } else {
679  // Standard method: use master server ID (works with stock pt-heartbeat)
680  $masterInfo = $this->getMasterServerInfo();
681  if ( !$masterInfo ) {
682  $this->queryLogger->error(
683  "Unable to query master of {db_server} for server ID",
684  $this->getLogContext( [
685  'method' => __METHOD__
686  ] )
687  );
688 
689  return false; // could not get master server ID
690  }
691 
692  $conds = [ 'server_id' => intval( $masterInfo['serverId'] ) ];
693  $data = $this->getHeartbeatData( $conds );
694  }
695 
696  list( $time, $nowUnix ) = $data;
697  if ( $time !== null ) {
698  // @time is in ISO format like "2015-09-25T16:48:10.000510"
699  $dateTime = new DateTime( $time, new DateTimeZone( 'UTC' ) );
700  $timeUnix = (int)$dateTime->format( 'U' ) + $dateTime->format( 'u' ) / 1e6;
701 
702  return max( $nowUnix - $timeUnix, 0.0 );
703  }
704 
705  $this->queryLogger->error(
706  "Unable to find pt-heartbeat row for {db_server}",
707  $this->getLogContext( [
708  'method' => __METHOD__
709  ] )
710  );
711 
712  return false;
713  }
714 
715  protected function getMasterServerInfo() {
717  $key = $cache->makeGlobalKey(
718  'mysql',
719  'master-info',
720  // Using one key for all cluster replica DBs is preferable
721  $this->getLBInfo( 'clusterMasterHost' ) ?: $this->getServer()
722  );
723 
724  return $cache->getWithSetCallback(
725  $key,
726  $cache::TTL_INDEFINITE,
727  function () use ( $cache, $key ) {
728  // Get and leave a lock key in place for a short period
729  if ( !$cache->lock( $key, 0, 10 ) ) {
730  return false; // avoid master connection spike slams
731  }
732 
733  $conn = $this->getLazyMasterHandle();
734  if ( !$conn ) {
735  return false; // something is misconfigured
736  }
737 
738  // Connect to and query the master; catch errors to avoid outages
739  try {
740  $res = $conn->query( 'SELECT @@server_id AS id', __METHOD__ );
741  $row = $res ? $res->fetchObject() : false;
742  $id = $row ? (int)$row->id : 0;
743  } catch ( DBError $e ) {
744  $id = 0;
745  }
746 
747  // Cache the ID if it was retrieved
748  return $id ? [ 'serverId' => $id, 'asOf' => time() ] : false;
749  }
750  );
751  }
752 
758  protected function getHeartbeatData( array $conds ) {
759  $whereSQL = $this->makeList( $conds, self::LIST_AND );
760  // Use ORDER BY for channel based queries since that field might not be UNIQUE.
761  // Note: this would use "TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6))" but the
762  // percision field is not supported in MySQL <= 5.5.
763  $res = $this->query(
764  "SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1"
765  );
766  $row = $res ? $res->fetchObject() : false;
767 
768  return [ $row ? $row->ts : null, microtime( true ) ];
769  }
770 
771  public function getApproximateLagStatus() {
772  if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
773  // Disable caching since this is fast enough and we don't wan't
774  // to be *too* pessimistic by having both the cache TTL and the
775  // pt-heartbeat interval count as lag in getSessionLagStatus()
776  return parent::getApproximateLagStatus();
777  }
778 
779  $key = $this->srvCache->makeGlobalKey( 'mysql-lag', $this->getServer() );
780  $approxLag = $this->srvCache->get( $key );
781  if ( !$approxLag ) {
782  $approxLag = parent::getApproximateLagStatus();
783  $this->srvCache->set( $key, $approxLag, 1 );
784  }
785 
786  return $approxLag;
787  }
788 
789  function masterPosWait( DBMasterPos $pos, $timeout ) {
790  if ( !( $pos instanceof MySQLMasterPos ) ) {
791  throw new InvalidArgumentException( "Position not an instance of MySQLMasterPos" );
792  }
793 
794  if ( $this->getLBInfo( 'is static' ) === true ) {
795  return 0; // this is a copy of a read-only dataset with no master DB
796  } elseif ( $this->lastKnownReplicaPos && $this->lastKnownReplicaPos->hasReached( $pos ) ) {
797  return 0; // already reached this point for sure
798  }
799 
800  // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
801  if ( $this->useGTIDs && $pos->gtids ) {
802  // Wait on the GTID set (MariaDB only)
803  $gtidArg = $this->addQuotes( implode( ',', $pos->gtids ) );
804  $res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" );
805  } else {
806  // Wait on the binlog coordinates
807  $encFile = $this->addQuotes( $pos->file );
808  $encPos = intval( $pos->pos );
809  $res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" );
810  }
811 
812  $row = $res ? $this->fetchRow( $res ) : false;
813  if ( !$row ) {
814  throw new DBExpectedError( $this, "Failed to query MASTER_POS_WAIT()" );
815  }
816 
817  // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
818  $status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
819  if ( $status === null ) {
820  // T126436: jobs programmed to wait on master positions might be referencing binlogs
821  // with an old master hostname. Such calls make MASTER_POS_WAIT() return null. Try
822  // to detect this and treat the replica DB as having reached the position; a proper master
823  // switchover already requires that the new master be caught up before the switch.
824  $replicationPos = $this->getReplicaPos();
825  if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) {
826  $this->lastKnownReplicaPos = $replicationPos;
827  $status = 0;
828  }
829  } elseif ( $status >= 0 ) {
830  // Remember that this position was reached to save queries next time
831  $this->lastKnownReplicaPos = $pos;
832  }
833 
834  return $status;
835  }
836 
842  function getReplicaPos() {
843  $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
844  $row = $this->fetchObject( $res );
845 
846  if ( $row ) {
847  $pos = isset( $row->Exec_master_log_pos )
848  ? $row->Exec_master_log_pos
849  : $row->Exec_Master_Log_Pos;
850  // Also fetch the last-applied GTID set (MariaDB)
851  if ( $this->useGTIDs ) {
852  $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_slave_pos'", __METHOD__ );
853  $gtidRow = $this->fetchObject( $res );
854  $gtidSet = $gtidRow ? $gtidRow->Value : '';
855  } else {
856  $gtidSet = '';
857  }
858 
859  return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos, $gtidSet );
860  } else {
861  return false;
862  }
863  }
864 
870  function getMasterPos() {
871  $res = $this->query( 'SHOW MASTER STATUS', __METHOD__ );
872  $row = $this->fetchObject( $res );
873 
874  if ( $row ) {
875  // Also fetch the last-written GTID set (MariaDB)
876  if ( $this->useGTIDs ) {
877  $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_binlog_pos'", __METHOD__ );
878  $gtidRow = $this->fetchObject( $res );
879  $gtidSet = $gtidRow ? $gtidRow->Value : '';
880  } else {
881  $gtidSet = '';
882  }
883 
884  return new MySQLMasterPos( $row->File, $row->Position, $gtidSet );
885  } else {
886  return false;
887  }
888  }
889 
890  public function serverIsReadOnly() {
891  $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'read_only'", __METHOD__ );
892  $row = $this->fetchObject( $res );
893 
894  return $row ? ( strtolower( $row->Value ) === 'on' ) : false;
895  }
896 
901  function useIndexClause( $index ) {
902  return "FORCE INDEX (" . $this->indexName( $index ) . ")";
903  }
904 
909  function ignoreIndexClause( $index ) {
910  return "IGNORE INDEX (" . $this->indexName( $index ) . ")";
911  }
912 
916  function lowPriorityOption() {
917  return 'LOW_PRIORITY';
918  }
919 
923  public function getSoftwareLink() {
924  // MariaDB includes its name in its version string; this is how MariaDB's version of
925  // the mysql command-line client identifies MariaDB servers (see mariadb_connection()
926  // in libmysql/libmysql.c).
927  $version = $this->getServerVersion();
928  if ( strpos( $version, 'MariaDB' ) !== false || strpos( $version, '-maria-' ) !== false ) {
929  return '[{{int:version-db-mariadb-url}} MariaDB]';
930  }
931 
932  // Percona Server's version suffix is not very distinctive, and @@version_comment
933  // doesn't give the necessary info for source builds, so assume the server is MySQL.
934  // (Even Percona's version of mysql doesn't try to make the distinction.)
935  return '[{{int:version-db-mysql-url}} MySQL]';
936  }
937 
941  public function getServerVersion() {
942  // Not using mysql_get_server_info() or similar for consistency: in the handshake,
943  // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
944  // it off (see RPL_VERSION_HACK in include/mysql_com.h).
945  if ( $this->serverVersion === null ) {
946  $this->serverVersion = $this->selectField( '', 'VERSION()', '', __METHOD__ );
947  }
948  return $this->serverVersion;
949  }
950 
954  public function setSessionOptions( array $options ) {
955  if ( isset( $options['connTimeout'] ) ) {
956  $timeout = (int)$options['connTimeout'];
957  $this->query( "SET net_read_timeout=$timeout" );
958  $this->query( "SET net_write_timeout=$timeout" );
959  }
960  }
961 
967  public function streamStatementEnd( &$sql, &$newLine ) {
968  if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
969  preg_match( '/^DELIMITER\s+(\S+)/', $newLine, $m );
970  $this->delimiter = $m[1];
971  $newLine = '';
972  }
973 
974  return parent::streamStatementEnd( $sql, $newLine );
975  }
976 
985  public function lockIsFree( $lockName, $method ) {
986  $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
987  $result = $this->query( "SELECT IS_FREE_LOCK($encName) AS lockstatus", $method );
988  $row = $this->fetchObject( $result );
989 
990  return ( $row->lockstatus == 1 );
991  }
992 
999  public function lock( $lockName, $method, $timeout = 5 ) {
1000  $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1001  $result = $this->query( "SELECT GET_LOCK($encName, $timeout) AS lockstatus", $method );
1002  $row = $this->fetchObject( $result );
1003 
1004  if ( $row->lockstatus == 1 ) {
1005  parent::lock( $lockName, $method, $timeout ); // record
1006  return true;
1007  }
1008 
1009  $this->queryLogger->warning( __METHOD__ . " failed to acquire lock '$lockName'\n" );
1010 
1011  return false;
1012  }
1013 
1021  public function unlock( $lockName, $method ) {
1022  $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1023  $result = $this->query( "SELECT RELEASE_LOCK($encName) as lockstatus", $method );
1024  $row = $this->fetchObject( $result );
1025 
1026  if ( $row->lockstatus == 1 ) {
1027  parent::unlock( $lockName, $method ); // record
1028  return true;
1029  }
1030 
1031  $this->queryLogger->warning( __METHOD__ . " failed to release lock '$lockName'\n" );
1032 
1033  return false;
1034  }
1035 
1036  private function makeLockName( $lockName ) {
1037  // http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
1038  // Newer version enforce a 64 char length limit.
1039  return ( strlen( $lockName ) > 64 ) ? sha1( $lockName ) : $lockName;
1040  }
1041 
1042  public function namedLocksEnqueue() {
1043  return true;
1044  }
1045 
1053  public function lockTables( $read, $write, $method, $lowPriority = true ) {
1054  $items = [];
1055 
1056  foreach ( $write as $table ) {
1057  $tbl = $this->tableName( $table ) .
1058  ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
1059  ' WRITE';
1060  $items[] = $tbl;
1061  }
1062  foreach ( $read as $table ) {
1063  $items[] = $this->tableName( $table ) . ' READ';
1064  }
1065  $sql = "LOCK TABLES " . implode( ',', $items );
1066  $this->query( $sql, $method );
1067 
1068  return true;
1069  }
1070 
1075  public function unlockTables( $method ) {
1076  $this->query( "UNLOCK TABLES", $method );
1077 
1078  return true;
1079  }
1080 
1084  public function setBigSelects( $value = true ) {
1085  if ( $value === 'default' ) {
1086  if ( $this->mDefaultBigSelects === null ) {
1087  # Function hasn't been called before so it must already be set to the default
1088  return;
1089  } else {
1091  }
1092  } elseif ( $this->mDefaultBigSelects === null ) {
1093  $this->mDefaultBigSelects =
1094  (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
1095  }
1096  $encValue = $value ? '1' : '0';
1097  $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
1098  }
1099 
1111  function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__ ) {
1112  if ( !$conds ) {
1113  throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
1114  }
1115 
1116  $delTable = $this->tableName( $delTable );
1117  $joinTable = $this->tableName( $joinTable );
1118  $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
1119 
1120  if ( $conds != '*' ) {
1121  $sql .= ' AND ' . $this->makeList( $conds, self::LIST_AND );
1122  }
1123 
1124  return $this->query( $sql, $fname );
1125  }
1126 
1135  public function upsert( $table, array $rows, array $uniqueIndexes,
1136  array $set, $fname = __METHOD__
1137  ) {
1138  if ( !count( $rows ) ) {
1139  return true; // nothing to do
1140  }
1141 
1142  if ( !is_array( reset( $rows ) ) ) {
1143  $rows = [ $rows ];
1144  }
1145 
1146  $table = $this->tableName( $table );
1147  $columns = array_keys( $rows[0] );
1148 
1149  $sql = "INSERT INTO $table (" . implode( ',', $columns ) . ') VALUES ';
1150  $rowTuples = [];
1151  foreach ( $rows as $row ) {
1152  $rowTuples[] = '(' . $this->makeList( $row ) . ')';
1153  }
1154  $sql .= implode( ',', $rowTuples );
1155  $sql .= " ON DUPLICATE KEY UPDATE " . $this->makeList( $set, self::LIST_SET );
1156 
1157  return (bool)$this->query( $sql, $fname );
1158  }
1159 
1165  function getServerUptime() {
1166  $vars = $this->getMysqlStatus( 'Uptime' );
1167 
1168  return (int)$vars['Uptime'];
1169  }
1170 
1176  function wasDeadlock() {
1177  return $this->lastErrno() == 1213;
1178  }
1179 
1185  function wasLockTimeout() {
1186  return $this->lastErrno() == 1205;
1187  }
1188 
1189  function wasErrorReissuable() {
1190  return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
1191  }
1192 
1198  function wasReadOnlyError() {
1199  return $this->lastErrno() == 1223 ||
1200  ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
1201  }
1202 
1203  function wasConnectionError( $errno ) {
1204  return $errno == 2013 || $errno == 2006;
1205  }
1206 
1214  function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
1215  $tmp = $temporary ? 'TEMPORARY ' : '';
1216  $newName = $this->addIdentifierQuotes( $newName );
1217  $oldName = $this->addIdentifierQuotes( $oldName );
1218  $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
1219 
1220  return $this->query( $query, $fname );
1221  }
1222 
1230  function listTables( $prefix = null, $fname = __METHOD__ ) {
1231  $result = $this->query( "SHOW TABLES", $fname );
1232 
1233  $endArray = [];
1234 
1235  foreach ( $result as $table ) {
1236  $vars = get_object_vars( $table );
1237  $table = array_pop( $vars );
1238 
1239  if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
1240  $endArray[] = $table;
1241  }
1242  }
1243 
1244  return $endArray;
1245  }
1246 
1252  public function dropTable( $tableName, $fName = __METHOD__ ) {
1253  if ( !$this->tableExists( $tableName, $fName ) ) {
1254  return false;
1255  }
1256 
1257  return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
1258  }
1259 
1266  function getMysqlStatus( $which = "%" ) {
1267  $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
1268  $status = [];
1269 
1270  foreach ( $res as $row ) {
1271  $status[$row->Variable_name] = $row->Value;
1272  }
1273 
1274  return $status;
1275  }
1276 
1286  public function listViews( $prefix = null, $fname = __METHOD__ ) {
1287  // The name of the column containing the name of the VIEW
1288  $propertyName = 'Tables_in_' . $this->mDBname;
1289 
1290  // Query for the VIEWS
1291  $res = $this->query( 'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"' );
1292  $allViews = [];
1293  foreach ( $res as $row ) {
1294  array_push( $allViews, $row->$propertyName );
1295  }
1296 
1297  if ( is_null( $prefix ) || $prefix === '' ) {
1298  return $allViews;
1299  }
1300 
1301  $filteredViews = [];
1302  foreach ( $allViews as $viewName ) {
1303  // Does the name of this VIEW start with the table-prefix?
1304  if ( strpos( $viewName, $prefix ) === 0 ) {
1305  array_push( $filteredViews, $viewName );
1306  }
1307  }
1308 
1309  return $filteredViews;
1310  }
1311 
1320  public function isView( $name, $prefix = null ) {
1321  return in_array( $name, $this->listViews( $prefix ) );
1322  }
1323 }
1324 
lockTables($read, $write, $method, $lowPriority=true)
mysqlFieldType($res, $n)
Get the type of the specified field in a result.
mysqlDataSeek($res, $row)
Move internal result pointer.
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
getServerUptime()
Determines how long the server has been up.
getMasterPos()
Get the position of the master from SHOW MASTER STATUS.
Database error base class.
Definition: DBError.php:26
the array() calling protocol came about after MediaWiki 1.4rc1.
namedLocksEnqueue()
Check to see if a named lock used by lock() use blocking queues.
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:1555
masterPosWait(DBMasterPos $pos, $timeout)
Wait for the replica DB to catch up to a given master position.
reportConnectionError($error= 'Unknown error')
Definition: Database.php:741
select($table, $vars, $conds= '', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
Definition: Database.php:1250
fieldType($res, $n)
mysql_field_type() wrapper
string $mDBname
Definition: Database.php:65
streamStatementEnd(&$sql, &$newLine)
mysqlConnect($realServer)
Open a connection to a MySQL server.
$success
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
array $lagDetectionOptions
Method to detect replica DB lag.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2102
addIdentifierQuotes($s)
MySQL uses backticks for identifier quoting instead of the sql standard "double quotes".
wasLockTimeout()
Determines if the last failure was due to a lock timeout.
string $lagDetectionMethod
Method to detect replica DB lag.
string null $sslCertPath
BagOStuff $srvCache
APC cache.
Definition: Database.php:74
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:177
unlock($lockName, $method)
FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release...
$value
isView($name, $prefix=null)
Differentiates between a TABLE and a VIEW.
upsert($table, array $rows, array $uniqueIndexes, array $set, $fname=__METHOD__)
getMysqlStatus($which="%")
Get status information from SHOW STATUS in an associative array.
connectInitCharset()
Set the character set information right after connection.
mysqlFetchArray($res)
Fetch a result row as an associative and numeric array.
An object representing a master or replica DB position in a replicated setup.
Definition: DBMasterPos.php:7
mysqlFetchObject($res)
Fetch a result row as an object.
replace($table, $uniqueIndexes, $rows, $fname=__METHOD__)
tableName($name, $format= 'quoted')
Format a table name ready for use in constructing an SQL query.
Definition: Database.php:1696
lock($lockName, $method, $timeout=5)
selectDB($db)
Change the current database.
Definition: Database.php:1679
lastErrno()
Get the last error number.
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message.Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item.Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page.Return false to stop further processing of the tag $reader:XMLReader object &$pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision.Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag.Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload.Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports.&$fullInterwikiPrefix:Interwiki prefix, may contain colons.&$pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable.Can be used to lazy-load the import sources list.&$importSources:The value of $wgImportSources.Modify as necessary.See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page.$context:IContextSource object &$pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect.&$title:Title object for the current page &$request:WebRequest &$ignoreRedirect:boolean to skip redirect check &$target:Title/string of redirect target &$article:Article object 'InternalParseBeforeLinks':during Parser's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InternalParseBeforeSanitize':during Parser's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings.Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not.Return true without providing an interwiki to continue interwiki search.$prefix:interwiki prefix we are looking for.&$iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user's email has been invalidated successfully.$user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification.Callee may modify $url and $query, URL will be constructed as $url.$query &$url:URL to index.php &$query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) &$article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() &$ip:IP being check &$result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from &$allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn't match your organization.$addr:The e-mail address entered by the user &$result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user &$result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we're looking for a messages file for &$file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED!Use $magicWords in a file listed in $wgExtensionMessagesFiles instead.Use this to define synonyms of magic words depending of the language &$magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces.Do not use this hook to add namespaces.Use CanonicalNamespaces for that.&$namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED!Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead.Use to define aliases of special pages names depending of the language &$specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names.&$names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page's language links.This is called in various places to allow extensions to define the effective language links for a page.$title:The page's Title.&$links:Associative array mapping language codes to prefixed links of the form"language:title".&$linkFlags:Associative array mapping prefixed links to arrays of flags.Currently unused, but planned to provide support for marking individual language links in the UI, e.g.for featured articles. 'LanguageSelector':Hook to change the language selector available on a page.$out:The output page.$cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED!Use HtmlPageLinkRendererBegin instead.Used when generating internal and interwiki links in Linker::link(), before processing starts.Return false to skip default processing and return $ret.See documentation for Linker::link() for details on the expected meanings of parameters.$skin:the Skin object $target:the Title that the link is pointing to &$html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1934
DBMasterPos class for MySQL/MariaDB.
bool $utf8Mode
Use experimental UTF-8 transmission encoding.
nativeReplace($table, $rows, $fname)
REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE statement.
Definition: Database.php:2141
indexName($index)
Get the name of an index in a given table.
Definition: Database.php:1957
dropTable($tableName, $fName=__METHOD__)
string[] null $sslCiphers
listViews($prefix=null, $fname=__METHOD__)
Lists VIEWs in the database.
Base class for the more common types of database errors.
deleteJoin($delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
const LIST_AND
Definition: Defines.php:35
listTables($prefix=null, $fname=__METHOD__)
List all tables on the database.
setSessionOptions(array $options)
close()
Closes a database connection.
Definition: Database.php:705
getLBInfo($name=null)
Get properties passed down from the server info array of the load balancer.
Definition: Database.php:469
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1046
$res
Definition: database.txt:21
getHeartbeatData(array $conds)
getReplicaPos()
Get the position of the master from SHOW SLAVE STATUS.
mysqlError($conn=null)
Returns the text of the error message from previous MySQL operation.
$cache
Definition: mcc.php:33
$params
tableExists($table, $fname=__METHOD__)
Query whether a given table exists.
makeList($a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Definition: Database.php:1548
duplicateTableStructure($oldName, $newName, $temporary=false, $fname=__METHOD__)
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition: Database.php:694
__construct(array $params)
Additional $params include:
addQuotes($s)
Adds quotes and backslashes.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
mysqlNumRows($res)
Get number of rows in result.
const LIST_SET
Definition: Defines.php:36
string $sqlMode
sql_mode value to send on connection
mysqlSetCharset($charset)
Set the character set of the MySQL link.
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:242
buildLike()
LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match conta...
Definition: Database.php:2023
indexInfo($table, $index, $fname=__METHOD__)
Get information about an index into an object Returns false if the index does not exist...
getLazyMasterHandle()
Definition: Database.php:498
mysqlNumFields($res)
Get number of fields in result.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
wasReadOnlyError()
Determines if the last failure was due to the database being read-only.
setBigSelects($value=true)
string null $serverVersion
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined...
Definition: Setup.php:36
wasDeadlock()
Determines if the last failure was due to a deadlock.
Database abstraction object for MySQL.
getLag()
Get replica DB lag.
mysqlFieldName($res, $n)
Get the name of the specified field in a result.
getServer()
Get the server hostname or IP address.
Definition: Database.php:1692
estimateRowCount($table, $vars= '*', $conds= '', $fname=__METHOD__, $options=[])
Estimate rows in dataset Returns estimated count, based on EXPLAIN output Takes same arguments as Dat...
wasErrorReissuable()
Determines if the last query error was due to a dropped connection and should be dealt with by pingin...
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1046
selectField($table, $var, $cond= '', $fname=__METHOD__, $options=[])
A SELECT wrapper which returns a single field from a single result row.
Definition: Database.php:1061
lockIsFree($lockName, $method)
Check to see if a named lock is available.
Result wrapper for grabbing data queried from an IDatabase object.
mysqlFreeResult($res)
Free result memory.
MysqlMasterPos $lastKnownReplicaPos
bool null $mDefaultBigSelects
Definition: Database.php:107
fieldInfo($table, $field)
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2159
doQuery($sql)
The DBMS-dependent part of query()
mysqlFetchField($res, $n)
Get column information from a result.
bool $useGTIDs
bool Whether to use GTID methods
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition: hooks.txt:1749
restoreErrorHandler()
Definition: Database.php:655
open($server, $user, $password, $dbName)
query($sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition: Database.php:829
installErrorHandler()
Definition: Database.php:646
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:300