MediaWiki  master
DatabaseMysqlBase.php
Go to the documentation of this file.
1 <?php
23 namespace Wikimedia\Rdbms;
24 
25 use InvalidArgumentException;
26 use RuntimeException;
27 use stdClass;
28 use Wikimedia\AtEase\AtEase;
29 
43 abstract class DatabaseMysqlBase extends Database {
49  protected $lagDetectionOptions = [];
51  protected $useGTIDs = false;
53  protected $sslKeyPath;
55  protected $sslCertPath;
57  protected $sslCAFile;
59  protected $sslCAPath;
65  protected $sslCiphers;
67  protected $sqlMode;
69  protected $utf8Mode;
71  protected $defaultBigSelects;
72 
77 
78  // Cache getServerId() for 24 hours
79  private const SERVER_ID_CACHE_TTL = 86400;
80 
82  private const LAG_STALE_WARN_THRESHOLD = 0.100;
83 
103  public function __construct( array $params ) {
104  $this->lagDetectionMethod = $params['lagDetectionMethod'] ?? 'Seconds_Behind_Master';
105  $this->lagDetectionOptions = $params['lagDetectionOptions'] ?? [];
106  $this->useGTIDs = !empty( $params['useGTIDs' ] );
107  foreach ( [ 'KeyPath', 'CertPath', 'CAFile', 'CAPath', 'Ciphers' ] as $name ) {
108  $var = "ssl{$name}";
109  if ( isset( $params[$var] ) ) {
110  $this->$var = $params[$var];
111  }
112  }
113  $this->sqlMode = $params['sqlMode'] ?? null;
114  $this->utf8Mode = !empty( $params['utf8Mode'] );
115  $this->insertSelectIsSafe = isset( $params['insertSelectIsSafe'] )
116  ? (bool)$params['insertSelectIsSafe'] : null;
117 
118  parent::__construct( $params );
119  }
120 
124  public function getType() {
125  return 'mysql';
126  }
127 
128  protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
129  $this->close( __METHOD__ );
130 
131  if ( $schema !== null ) {
132  throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
133  }
134 
135  $this->installErrorHandler();
136  try {
137  $this->conn = $this->mysqlConnect( $server, $user, $password, $db );
138  } catch ( RuntimeException $e ) {
139  $this->restoreErrorHandler();
140  throw $this->newExceptionAfterConnectError( $e->getMessage() );
141  }
142  $error = $this->restoreErrorHandler();
143 
144  if ( !$this->conn ) {
145  throw $this->newExceptionAfterConnectError( $error ?: $this->lastError() );
146  }
147 
148  try {
149  $this->currentDomain = new DatabaseDomain(
150  strlen( $db ) ? $db : null,
151  null,
152  $tablePrefix
153  );
154  // Abstract over any excessive MySQL defaults
155  $set = [ 'group_concat_max_len = 262144' ];
156  // Set SQL mode, default is turning them all off, can be overridden or skipped with null
157  if ( is_string( $this->sqlMode ) ) {
158  $set[] = 'sql_mode = ' . $this->addQuotes( $this->sqlMode );
159  }
160  // Set any custom settings defined by site config
161  // https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html
162  foreach ( $this->connectionVariables as $var => $val ) {
163  // Escape strings but not numbers to avoid MySQL complaining
164  if ( !is_int( $val ) && !is_float( $val ) ) {
165  $val = $this->addQuotes( $val );
166  }
167  $set[] = $this->addIdentifierQuotes( $var ) . ' = ' . $val;
168  }
169 
170  // @phan-suppress-next-next-line PhanRedundantCondition
171  // If kept for safety and to avoid broken query
172  if ( $set ) {
173  $this->query(
174  'SET ' . implode( ', ', $set ),
175  __METHOD__,
176  self::QUERY_NO_RETRY | self::QUERY_CHANGE_TRX
177  );
178  }
179  } catch ( RuntimeException $e ) {
180  throw $this->newExceptionAfterConnectError( $e->getMessage() );
181  }
182  }
183 
184  protected function doSelectDomain( DatabaseDomain $domain ) {
185  if ( $domain->getSchema() !== null ) {
186  throw new DBExpectedError(
187  $this,
188  __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
189  );
190  }
191 
192  $database = $domain->getDatabase();
193  // A null database means "don't care" so leave it as is and update the table prefix
194  if ( $database === null ) {
195  $this->currentDomain = new DatabaseDomain(
196  $this->currentDomain->getDatabase(),
197  null,
198  $domain->getTablePrefix()
199  );
200 
201  return true;
202  }
203 
204  if ( $database !== $this->getDBname() ) {
205  $sql = 'USE ' . $this->addIdentifierQuotes( $database );
206  list( $res, $err, $errno ) =
207  $this->executeQuery( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX );
208 
209  if ( $res === false ) {
210  $this->reportQueryError( $err, $errno, $sql, __METHOD__ );
211  return false; // unreachable
212  }
213  }
214 
215  // Update that domain fields on success (no exception thrown)
216  $this->currentDomain = $domain;
217 
218  return true;
219  }
220 
231  abstract protected function mysqlConnect( $server, $user, $password, $db );
232 
236  public function lastError() {
237  if ( $this->conn ) {
238  # Even if it's non-zero, it can still be invalid
239  AtEase::suppressWarnings();
240  $error = $this->mysqlError( $this->conn );
241  if ( !$error ) {
242  $error = $this->mysqlError();
243  }
244  AtEase::restoreWarnings();
245  } else {
246  $error = $this->mysqlError();
247  }
248  if ( $error ) {
249  $error .= ' (' . $this->getServerName() . ')';
250  }
251 
252  return $error;
253  }
254 
261  abstract protected function mysqlError( $conn = null );
262 
263  protected function wasQueryTimeout( $error, $errno ) {
264  // https://dev.mysql.com/doc/refman/8.0/en/client-error-reference.html
265  // https://phabricator.wikimedia.org/T170638
266  return in_array( $errno, [ 2062, 3024 ] );
267  }
268 
269  protected function isInsertSelectSafe( array $insertOptions, array $selectOptions ) {
270  $row = $this->getReplicationSafetyInfo();
271  // For row-based-replication, the resulting changes will be relayed, not the query
272  if ( $row->binlog_format === 'ROW' ) {
273  return true;
274  }
275  // LIMIT requires ORDER BY on a unique key or it is non-deterministic
276  if ( isset( $selectOptions['LIMIT'] ) ) {
277  return false;
278  }
279  // In MySQL, an INSERT SELECT is only replication safe with row-based
280  // replication or if innodb_autoinc_lock_mode is 0. When those
281  // conditions aren't met, use non-native mode.
282  // While we could try to determine if the insert is safe anyway by
283  // checking if the target table has an auto-increment column that
284  // isn't set in $varMap, that seems unlikely to be worth the extra
285  // complexity.
286  return (
287  in_array( 'NO_AUTO_COLUMNS', $insertOptions ) ||
288  (int)$row->innodb_autoinc_lock_mode === 0
289  );
290  }
291 
295  protected function getReplicationSafetyInfo() {
296  if ( $this->replicationInfoRow === null ) {
297  $this->replicationInfoRow = $this->selectRow(
298  false,
299  [
300  'innodb_autoinc_lock_mode' => '@@innodb_autoinc_lock_mode',
301  'binlog_format' => '@@binlog_format',
302  ],
303  [],
304  __METHOD__
305  );
306  }
307 
309  }
310 
324  public function estimateRowCount(
325  $tables,
326  $var = '*',
327  $conds = '',
328  $fname = __METHOD__,
329  $options = [],
330  $join_conds = []
331  ) {
332  $conds = $this->normalizeConditions( $conds, $fname );
333  $column = $this->extractSingleFieldFromList( $var );
334  if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
335  $conds[] = "$column IS NOT NULL";
336  }
337 
338  $options['EXPLAIN'] = true;
339  $res = $this->select( $tables, $var, $conds, $fname, $options, $join_conds );
340  if ( $res === false ) {
341  return false;
342  }
343  if ( !$res->numRows() ) {
344  return 0;
345  }
346 
347  $rows = 1;
348  foreach ( $res as $plan ) {
349  $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
350  }
351 
352  return (int)$rows;
353  }
354 
355  public function tableExists( $table, $fname = __METHOD__ ) {
356  // Split database and table into proper variables as Database::tableName() returns
357  // shared tables prefixed with their database, which do not work in SHOW TABLES statements
358  list( $database, , $prefix, $table ) = $this->qualifiedTableComponents( $table );
359  $tableName = "{$prefix}{$table}";
360 
361  if ( isset( $this->sessionTempTables[$tableName] ) ) {
362  return true; // already known to exist and won't show in SHOW TABLES anyway
363  }
364 
365  // We can't use buildLike() here, because it specifies an escape character
366  // other than the backslash, which is the only one supported by SHOW TABLES
367  $encLike = $this->escapeLikeInternal( $tableName, '\\' );
368 
369  // If the database has been specified (such as for shared tables), use "FROM"
370  if ( $database !== '' ) {
371  $encDatabase = $this->addIdentifierQuotes( $database );
372  $sql = "SHOW TABLES FROM $encDatabase LIKE '$encLike'";
373  } else {
374  $sql = "SHOW TABLES LIKE '$encLike'";
375  }
376 
377  $res = $this->query(
378  $sql,
379  $fname,
380  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
381  );
382 
383  return $res->numRows() > 0;
384  }
385 
391  public function fieldInfo( $table, $field ) {
392  $res = $this->query(
393  "SELECT * FROM " . $this->tableName( $table ) . " LIMIT 1",
394  __METHOD__,
395  self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
396  );
397  if ( !$res ) {
398  return false;
399  }
401  '@phan-var MysqliResultWrapper $res';
402  return $res->getInternalFieldInfo( $field );
403  }
404 
414  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
415  # https://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
416  $index = $this->indexName( $index );
417 
418  $res = $this->query(
419  'SHOW INDEX FROM ' . $this->tableName( $table ),
420  $fname,
421  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
422  );
423 
424  if ( !$res ) {
425  return null;
426  }
427 
428  $result = [];
429 
430  foreach ( $res as $row ) {
431  if ( $row->Key_name == $index ) {
432  $result[] = $row;
433  }
434  }
435 
436  return $result ?: false;
437  }
438 
439  protected function normalizeJoinType( string $joinType ) {
440  switch ( strtoupper( $joinType ) ) {
441  case 'STRAIGHT_JOIN':
442  case 'STRAIGHT JOIN':
443  return 'STRAIGHT_JOIN';
444 
445  default:
446  return parent::normalizeJoinType( $joinType );
447  }
448  }
449 
454  public function strencode( $s ) {
455  return $this->mysqlRealEscapeString( $s );
456  }
457 
462  abstract protected function mysqlRealEscapeString( $s );
463 
470  public function addIdentifierQuotes( $s ) {
471  // Characters in the range \u0001-\uFFFF are valid in a quoted identifier
472  // Remove NUL bytes and escape backticks by doubling
473  return '`' . str_replace( [ "\0", '`' ], [ '', '``' ], $s ) . '`';
474  }
475 
480  public function isQuotedIdentifier( $name ) {
481  return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
482  }
483 
484  protected function doGetLag() {
485  if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
486  return $this->getLagFromPtHeartbeat();
487  } else {
488  return $this->getLagFromSlaveStatus();
489  }
490  }
491 
495  protected function getLagDetectionMethod() {
497  }
498 
502  protected function getLagFromSlaveStatus() {
503  $res = $this->query(
504  'SHOW SLAVE STATUS',
505  __METHOD__,
506  self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
507  );
508  $row = $res ? $res->fetchObject() : false;
509  // If the server is not replicating, there will be no row
510  if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
511  // https://mariadb.com/kb/en/delayed-replication/
512  // https://dev.mysql.com/doc/refman/5.6/en/replication-delayed.html
513  return intval( $row->Seconds_Behind_Master + ( $row->SQL_Remaining_Delay ?? 0 ) );
514  }
515 
516  return false;
517  }
518 
522  protected function getLagFromPtHeartbeat() {
523  $options = $this->lagDetectionOptions;
524 
525  $currentTrxInfo = $this->getRecordedTransactionLagStatus();
526  if ( $currentTrxInfo ) {
527  // There is an active transaction and the initial lag was already queried
528  $staleness = microtime( true ) - $currentTrxInfo['since'];
529  if ( $staleness > self::LAG_STALE_WARN_THRESHOLD ) {
530  // Avoid returning higher and higher lag value due to snapshot age
531  // given that the isolation level will typically be REPEATABLE-READ
532  $this->queryLogger->warning(
533  "Using cached lag value for {db_server} due to active transaction",
534  $this->getLogContext( [
535  'method' => __METHOD__,
536  'age' => $staleness,
537  'exception' => new RuntimeException()
538  ] )
539  );
540  }
541 
542  return $currentTrxInfo['lag'];
543  }
544 
545  if ( isset( $options['conds'] ) ) {
546  // Best method for multi-DC setups: use logical channel names
547  $ago = $this->fetchSecondsSinceHeartbeat( $options['conds'] );
548  } else {
549  // Standard method: use primary server ID (works with stock pt-heartbeat)
550  $masterInfo = $this->getPrimaryServerInfo();
551  if ( !$masterInfo ) {
552  $this->queryLogger->error(
553  "Unable to query primary of {db_server} for server ID",
554  $this->getLogContext( [
555  'method' => __METHOD__
556  ] )
557  );
558 
559  return false; // could not get primary server ID
560  }
561 
562  $conds = [ 'server_id' => intval( $masterInfo['serverId'] ) ];
563  $ago = $this->fetchSecondsSinceHeartbeat( $conds );
564  }
565 
566  if ( $ago !== null ) {
567  return max( $ago, 0.0 );
568  }
569 
570  $this->queryLogger->error(
571  "Unable to find pt-heartbeat row for {db_server}",
572  $this->getLogContext( [
573  'method' => __METHOD__
574  ] )
575  );
576 
577  return false;
578  }
579 
580  protected function getPrimaryServerInfo() {
582  $key = $cache->makeGlobalKey(
583  'mysql',
584  'master-info',
585  // Using one key for all cluster replica DBs is preferable
586  $this->topologyRootMaster ?? $this->getServerName()
587  );
588  $fname = __METHOD__;
589 
590  return $cache->getWithSetCallback(
591  $key,
592  $cache::TTL_INDEFINITE,
593  function () use ( $cache, $key, $fname ) {
594  // Get and leave a lock key in place for a short period
595  if ( !$cache->lock( $key, 0, 10 ) ) {
596  return false; // avoid primary DB connection spike slams
597  }
598 
599  $conn = $this->getLazyMasterHandle();
600  if ( !$conn ) {
601  return false; // something is misconfigured
602  }
603 
604  $flags = self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
605  // Connect to and query the primary DB; catch errors to avoid outages
606  try {
607  $res = $conn->query( 'SELECT @@server_id AS id', $fname, $flags );
608  $row = $res ? $res->fetchObject() : false;
609  $id = $row ? (int)$row->id : 0;
610  } catch ( DBError $e ) {
611  $id = 0;
612  }
613 
614  // Cache the ID if it was retrieved
615  return $id ? [ 'serverId' => $id, 'asOf' => time() ] : false;
616  }
617  );
618  }
619 
620  protected function getMasterServerInfo() {
621  wfDeprecated( __METHOD__, '1.37' );
622  return $this->getPrimaryServerInfo();
623  }
624 
630  protected function fetchSecondsSinceHeartbeat( array $conds ) {
631  $whereSQL = $this->makeList( $conds, self::LIST_AND );
632  // User mysql server time so that query time and trip time are not counted.
633  // Use ORDER BY for channel based queries since that field might not be UNIQUE.
634  $res = $this->query(
635  "SELECT TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6)) AS us_ago " .
636  "FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1",
637  __METHOD__,
638  self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
639  );
640  $row = $res ? $res->fetchObject() : false;
641 
642  return $row ? ( $row->us_ago / 1e6 ) : null;
643  }
644 
645  protected function getApproximateLagStatus() {
646  if ( $this->getLagDetectionMethod() === 'pt-heartbeat' ) {
647  // Disable caching since this is fast enough and we don't wan't
648  // to be *too* pessimistic by having both the cache TTL and the
649  // pt-heartbeat interval count as lag in getSessionLagStatus()
650  return parent::getApproximateLagStatus();
651  }
652 
653  $key = $this->srvCache->makeGlobalKey( 'mysql-lag', $this->getServerName() );
654  $approxLag = $this->srvCache->get( $key );
655  if ( !$approxLag ) {
656  $approxLag = parent::getApproximateLagStatus();
657  $this->srvCache->set( $key, $approxLag, 1 );
658  }
659 
660  return $approxLag;
661  }
662 
663  public function primaryPosWait( DBPrimaryPos $pos, $timeout ) {
664  if ( !( $pos instanceof MySQLPrimaryPos ) ) {
665  throw new InvalidArgumentException( "Position not an instance of MySQLPrimaryPos" );
666  }
667 
668  if ( $this->topologyRole === self::ROLE_STATIC_CLONE ) {
669  $this->queryLogger->debug(
670  "Bypassed replication wait; database has a static dataset",
671  $this->getLogContext( [ 'method' => __METHOD__, 'raw_pos' => $pos ] )
672  );
673 
674  return 0; // this is a copy of a read-only dataset with no primary DB
675  } elseif ( $this->lastKnownReplicaPos && $this->lastKnownReplicaPos->hasReached( $pos ) ) {
676  $this->queryLogger->debug(
677  "Bypassed replication wait; replication known to have reached {raw_pos}",
678  $this->getLogContext( [ 'method' => __METHOD__, 'raw_pos' => $pos ] )
679  );
680 
681  return 0; // already reached this point for sure
682  }
683 
684  // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
685  if ( $pos->getGTIDs() ) {
686  // Get the GTIDs from this replica server too see the domains (channels)
687  $refPos = $this->getReplicaPos();
688  if ( !$refPos ) {
689  $this->queryLogger->error(
690  "Could not get replication position on replica DB to compare to {raw_pos}",
691  $this->getLogContext( [ 'method' => __METHOD__, 'raw_pos' => $pos ] )
692  );
693 
694  return -1; // this is the primary DB itself?
695  }
696  // GTIDs with domains (channels) that are active and are present on the replica
697  $gtidsWait = $pos::getRelevantActiveGTIDs( $pos, $refPos );
698  if ( !$gtidsWait ) {
699  $this->queryLogger->error(
700  "No active GTIDs in {raw_pos} share a domain with those in {current_pos}",
701  $this->getLogContext( [
702  'method' => __METHOD__,
703  'raw_pos' => $pos,
704  'current_pos' => $refPos
705  ] )
706  );
707 
708  return -1; // $pos is from the wrong cluster?
709  }
710  // Wait on the GTID set
711  $gtidArg = $this->addQuotes( implode( ',', $gtidsWait ) );
712  if ( strpos( $gtidArg, ':' ) !== false ) {
713  // MySQL GTIDs, e.g "source_id:transaction_id"
714  $sql = "SELECT WAIT_FOR_EXECUTED_GTID_SET($gtidArg, $timeout)";
715  } else {
716  // MariaDB GTIDs, e.g."domain:server:sequence"
717  $sql = "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)";
718  }
719  $waitPos = implode( ',', $gtidsWait );
720  } else {
721  // Wait on the binlog coordinates
722  $encFile = $this->addQuotes( $pos->getLogFile() );
723  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
724  $encPos = intval( $pos->getLogPosition()[$pos::CORD_EVENT] );
725  $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
726  $waitPos = $pos->__toString();
727  }
728 
729  $start = microtime( true );
730  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
731  $res = $this->query( $sql, __METHOD__, $flags );
732  $row = $this->fetchRow( $res );
733  $seconds = max( microtime( true ) - $start, 0 );
734 
735  // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
736  $status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
737  if ( $status === null ) {
738  $this->replLogger->error(
739  "An error occurred while waiting for replication to reach {wait_pos}",
740  $this->getLogContext( [
741  'raw_pos' => $pos,
742  'wait_pos' => $waitPos,
743  'sql' => $sql,
744  'seconds_waited' => $seconds,
745  'exception' => new RuntimeException()
746  ] )
747  );
748  } elseif ( $status < 0 ) {
749  $this->replLogger->error(
750  "Timed out waiting for replication to reach {wait_pos}",
751  $this->getLogContext( [
752  'raw_pos' => $pos,
753  'wait_pos' => $waitPos,
754  'timeout' => $timeout,
755  'sql' => $sql,
756  'seconds_waited' => $seconds,
757  'exception' => new RuntimeException()
758  ] )
759  );
760  } elseif ( $status >= 0 ) {
761  $this->replLogger->debug(
762  "Replication has reached {wait_pos}",
763  $this->getLogContext( [
764  'raw_pos' => $pos,
765  'wait_pos' => $waitPos,
766  'seconds_waited' => $seconds,
767  ] )
768  );
769  // Remember that this position was reached to save queries next time
770  $this->lastKnownReplicaPos = $pos;
771  }
772 
773  return $status;
774  }
775 
781  public function getReplicaPos() {
782  $now = microtime( true ); // as-of-time *before* fetching GTID variables
783 
784  if ( $this->useGTIDs() ) {
785  // Try to use GTIDs, fallbacking to binlog positions if not possible
786  $data = $this->getServerGTIDs( __METHOD__ );
787  // Use gtid_slave_pos for MariaDB and gtid_executed for MySQL
788  foreach ( [ 'gtid_slave_pos', 'gtid_executed' ] as $name ) {
789  if ( isset( $data[$name] ) && strlen( $data[$name] ) ) {
790  return new MySQLPrimaryPos( $data[$name], $now );
791  }
792  }
793  }
794 
795  $data = $this->getServerRoleStatus( 'SLAVE', __METHOD__ );
796  if ( $data && strlen( $data['Relay_Master_Log_File'] ) ) {
797  return new MySQLPrimaryPos(
798  "{$data['Relay_Master_Log_File']}/{$data['Exec_Master_Log_Pos']}",
799  $now
800  );
801  }
802 
803  return false;
804  }
805 
811  public function getPrimaryPos() {
812  $now = microtime( true ); // as-of-time *before* fetching GTID variables
813 
814  $pos = false;
815  if ( $this->useGTIDs() ) {
816  // Try to use GTIDs, fallbacking to binlog positions if not possible
817  $data = $this->getServerGTIDs( __METHOD__ );
818  // Use gtid_binlog_pos for MariaDB and gtid_executed for MySQL
819  foreach ( [ 'gtid_binlog_pos', 'gtid_executed' ] as $name ) {
820  if ( isset( $data[$name] ) && strlen( $data[$name] ) ) {
821  $pos = new MySQLPrimaryPos( $data[$name], $now );
822  break;
823  }
824  }
825  // Filter domains that are inactive or not relevant to the session
826  if ( $pos ) {
827  $pos->setActiveOriginServerId( $this->getServerId() );
828  $pos->setActiveOriginServerUUID( $this->getServerUUID() );
829  if ( isset( $data['gtid_domain_id'] ) ) {
830  $pos->setActiveDomain( $data['gtid_domain_id'] );
831  }
832  }
833  }
834 
835  if ( !$pos ) {
836  $data = $this->getServerRoleStatus( 'MASTER', __METHOD__ );
837  if ( $data && strlen( $data['File'] ) ) {
838  $pos = new MySQLPrimaryPos( "{$data['File']}/{$data['Position']}", $now );
839  }
840  }
841 
842  return $pos;
843  }
844 
845  public function getMasterPos() {
846  wfDeprecated( __METHOD__, '1.37' );
847  return $this->getPrimaryPos();
848  }
849 
854  public function getTopologyBasedServerId() {
855  return $this->getServerId();
856  }
857 
862  protected function getServerId() {
863  $fname = __METHOD__;
864  return $this->srvCache->getWithSetCallback(
865  $this->srvCache->makeGlobalKey( 'mysql-server-id', $this->getServerName() ),
866  self::SERVER_ID_CACHE_TTL,
867  function () use ( $fname ) {
868  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
869  $res = $this->query( "SELECT @@server_id AS id", $fname, $flags );
870 
871  return $res->fetchObject()->id;
872  }
873  );
874  }
875 
880  protected function getServerUUID() {
881  $fname = __METHOD__;
882  return $this->srvCache->getWithSetCallback(
883  $this->srvCache->makeGlobalKey( 'mysql-server-uuid', $this->getServerName() ),
884  self::SERVER_ID_CACHE_TTL,
885  function () use ( $fname ) {
886  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
887  $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'server_uuid'", $fname, $flags );
888  $row = $res->fetchObject();
889 
890  return $row ? $row->Value : null;
891  }
892  );
893  }
894 
899  protected function getServerGTIDs( $fname = __METHOD__ ) {
900  $map = [];
901 
902  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
903 
904  // Get global-only variables like gtid_executed
905  $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_%'", $fname, $flags );
906  foreach ( $res as $row ) {
907  $map[$row->Variable_name] = $row->Value;
908  }
909  // Get session-specific (e.g. gtid_domain_id since that is were writes will log)
910  $res = $this->query( "SHOW SESSION VARIABLES LIKE 'gtid_%'", $fname, $flags );
911  foreach ( $res as $row ) {
912  $map[$row->Variable_name] = $row->Value;
913  }
914 
915  return $map;
916  }
917 
923  protected function getServerRoleStatus( $role, $fname = __METHOD__ ) {
924  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
925  $res = $this->query( "SHOW $role STATUS", $fname, $flags );
926 
927  return $res->fetchRow() ?: [];
928  }
929 
930  public function serverIsReadOnly() {
931  // Avoid SHOW to avoid internal temporary tables
932  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
933  $res = $this->query( "SELECT @@GLOBAL.read_only AS Value", __METHOD__, $flags );
934  $row = $res->fetchObject();
935 
936  return $row ? (bool)$row->Value : false;
937  }
938 
943  public function useIndexClause( $index ) {
944  return "FORCE INDEX (" . $this->indexName( $index ) . ")";
945  }
946 
951  public function ignoreIndexClause( $index ) {
952  return "IGNORE INDEX (" . $this->indexName( $index ) . ")";
953  }
954 
958  public function getSoftwareLink() {
959  list( $variant ) = $this->getMySqlServerVariant();
960  if ( $variant === 'MariaDB' ) {
961  return '[{{int:version-db-mariadb-url}} MariaDB]';
962  }
963 
964  return '[{{int:version-db-mysql-url}} MySQL]';
965  }
966 
970  protected function getMySqlServerVariant() {
971  $version = $this->getServerVersion();
972 
973  // MariaDB includes its name in its version string; this is how MariaDB's version of
974  // the mysql command-line client identifies MariaDB servers.
975  // https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_version
976  // https://mariadb.com/kb/en/version/
977  $parts = explode( '-', $version, 2 );
978  $number = $parts[0];
979  $suffix = $parts[1] ?? '';
980  if ( strpos( $suffix, 'MariaDB' ) !== false || strpos( $suffix, '-maria-' ) !== false ) {
981  $vendor = 'MariaDB';
982  } else {
983  $vendor = 'MySQL';
984  }
985 
986  return [ $vendor, $number ];
987  }
988 
992  public function getServerVersion() {
994  $fname = __METHOD__;
995 
996  return $cache->getWithSetCallback(
997  $cache->makeGlobalKey( 'mysql-server-version', $this->getServerName() ),
998  $cache::TTL_HOUR,
999  function () use ( $fname ) {
1000  // Not using mysql_get_server_info() or similar for consistency: in the handshake,
1001  // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
1002  // it off (see RPL_VERSION_HACK in include/mysql_com.h).
1003  return $this->selectField( '', 'VERSION()', '', $fname );
1004  }
1005  );
1006  }
1007 
1011  public function setSessionOptions( array $options ) {
1012  if ( isset( $options['connTimeout'] ) ) {
1013  $timeout = (int)$options['connTimeout'];
1014  $this->query( "SET net_read_timeout=$timeout", __METHOD__, self::QUERY_CHANGE_TRX );
1015  $this->query( "SET net_write_timeout=$timeout", __METHOD__, self::QUERY_CHANGE_TRX );
1016  }
1017  }
1018 
1024  public function streamStatementEnd( &$sql, &$newLine ) {
1025  if ( preg_match( '/^DELIMITER\s+(\S+)/i', $newLine, $m ) ) {
1026  $this->delimiter = $m[1];
1027  $newLine = '';
1028  }
1029 
1030  return parent::streamStatementEnd( $sql, $newLine );
1031  }
1032 
1033  public function doLockIsFree( string $lockName, string $method ) {
1034  $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1035 
1036  $res = $this->query(
1037  "SELECT IS_FREE_LOCK($encName) AS unlocked",
1038  $method,
1039  self::QUERY_CHANGE_LOCKS
1040  );
1041  $row = $res->fetchObject();
1042 
1043  return ( $row->unlocked == 1 );
1044  }
1045 
1046  public function doLock( string $lockName, string $method, int $timeout ) {
1047  $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1048  // Unlike NOW(), SYSDATE() gets the time at invocation rather than query start.
1049  // The precision argument is silently ignored for MySQL < 5.6 and MariaDB < 5.3.
1050  // https://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_sysdate
1051  // https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html
1052  $res = $this->query(
1053  "SELECT IF(GET_LOCK($encName,$timeout),UNIX_TIMESTAMP(SYSDATE(6)),NULL) AS acquired",
1054  $method,
1055  self::QUERY_CHANGE_LOCKS
1056  );
1057  $row = $res->fetchObject();
1058 
1059  return ( $row->acquired !== null ) ? (float)$row->acquired : null;
1060  }
1061 
1062  public function doUnlock( string $lockName, string $method ) {
1063  $encName = $this->addQuotes( $this->makeLockName( $lockName ) );
1064 
1065  $res = $this->query(
1066  "SELECT RELEASE_LOCK($encName) AS released",
1067  $method,
1068  self::QUERY_CHANGE_LOCKS
1069  );
1070  $row = $res->fetchObject();
1071 
1072  return ( $row->released == 1 );
1073  }
1074 
1075  private function makeLockName( $lockName ) {
1076  // https://dev.mysql.com/doc/refman/5.7/en/locking-functions.html#function_get-lock
1077  // MySQL 5.7+ enforces a 64 char length limit.
1078  return ( strlen( $lockName ) > 64 ) ? sha1( $lockName ) : $lockName;
1079  }
1080 
1081  public function namedLocksEnqueue() {
1082  return true;
1083  }
1084 
1086  return false; // tied to TCP connection
1087  }
1088 
1089  protected function doLockTables( array $read, array $write, $method ) {
1090  $items = [];
1091  foreach ( $write as $table ) {
1092  $items[] = $this->tableName( $table ) . ' WRITE';
1093  }
1094  foreach ( $read as $table ) {
1095  $items[] = $this->tableName( $table ) . ' READ';
1096  }
1097 
1098  $this->query(
1099  "LOCK TABLES " . implode( ',', $items ),
1100  $method,
1101  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_ROWS
1102  );
1103 
1104  return true;
1105  }
1106 
1107  protected function doUnlockTables( $method ) {
1108  $this->query(
1109  "UNLOCK TABLES",
1110  $method,
1111  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_ROWS
1112  );
1113 
1114  return true;
1115  }
1116 
1120  public function setBigSelects( $value = true ) {
1121  if ( $value === 'default' ) {
1122  if ( $this->defaultBigSelects === null ) {
1123  # Function hasn't been called before so it must already be set to the default
1124  return;
1125  } else {
1126  $value = $this->defaultBigSelects;
1127  }
1128  } elseif ( $this->defaultBigSelects === null ) {
1129  $this->defaultBigSelects =
1130  (bool)$this->selectField( false, '@@sql_big_selects', '', __METHOD__ );
1131  }
1132 
1133  $this->query(
1134  "SET sql_big_selects=" . ( $value ? '1' : '0' ),
1135  __METHOD__,
1136  self::QUERY_CHANGE_TRX
1137  );
1138  }
1139 
1150  public function deleteJoin(
1151  $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
1152  ) {
1153  if ( !$conds ) {
1154  throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
1155  }
1156 
1157  $delTable = $this->tableName( $delTable );
1158  $joinTable = $this->tableName( $joinTable );
1159  $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
1160 
1161  if ( $conds != '*' ) {
1162  $sql .= ' AND ' . $this->makeList( $conds, self::LIST_AND );
1163  }
1164 
1165  $this->query( $sql, $fname, self::QUERY_CHANGE_ROWS );
1166  }
1167 
1168  protected function doUpsert(
1169  string $table,
1170  array $rows,
1171  array $identityKey,
1172  array $set,
1173  string $fname
1174  ) {
1175  $encTable = $this->tableName( $table );
1176  list( $sqlColumns, $sqlTuples ) = $this->makeInsertLists( $rows );
1177  $sqlColumnAssignments = $this->makeList( $set, self::LIST_SET );
1178 
1179  $sql =
1180  "INSERT INTO $encTable ($sqlColumns) VALUES $sqlTuples " .
1181  "ON DUPLICATE KEY UPDATE $sqlColumnAssignments";
1182 
1183  $this->query( $sql, $fname, self::QUERY_CHANGE_ROWS );
1184  }
1185 
1186  protected function doReplace( $table, array $identityKey, array $rows, $fname ) {
1187  $encTable = $this->tableName( $table );
1188  list( $sqlColumns, $sqlTuples ) = $this->makeInsertLists( $rows );
1189 
1190  $sql = "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples";
1191 
1192  $this->query( $sql, $fname, self::QUERY_CHANGE_ROWS );
1193  }
1194 
1200  public function wasDeadlock() {
1201  return $this->lastErrno() == 1213;
1202  }
1203 
1209  public function wasLockTimeout() {
1210  return $this->lastErrno() == 1205;
1211  }
1212 
1218  public function wasReadOnlyError() {
1219  return $this->lastErrno() == 1223 ||
1220  ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
1221  }
1222 
1223  protected function isConnectionError( $errno ) {
1224  return $errno == 2013 || $errno == 2006;
1225  }
1226 
1227  protected function wasKnownStatementRollbackError() {
1228  $errno = $this->lastErrno();
1229 
1230  if ( $errno === 1205 ) { // lock wait timeout
1231  // Note that this is uncached to avoid stale values of SET is used
1232  $res = $this->query(
1233  "SELECT @@innodb_rollback_on_timeout AS Value",
1234  __METHOD__,
1235  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1236  );
1237  $row = $res ? $res->fetchObject() : false;
1238  // https://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html
1239  // https://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html
1240  return ( $row && !$row->Value );
1241  }
1242 
1243  // See https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html
1244  return in_array( $errno, [ 1022, 1062, 1216, 1217, 1137, 1146, 1051, 1054 ], true );
1245  }
1246 
1254  public function duplicateTableStructure(
1255  $oldName, $newName, $temporary = false, $fname = __METHOD__
1256  ) {
1257  $tmp = $temporary ? 'TEMPORARY ' : '';
1258  $newName = $this->addIdentifierQuotes( $newName );
1259  $oldName = $this->addIdentifierQuotes( $oldName );
1260 
1261  return $this->query(
1262  "CREATE $tmp TABLE $newName (LIKE $oldName)",
1263  $fname,
1264  self::QUERY_PSEUDO_PERMANENT | self::QUERY_CHANGE_SCHEMA
1265  );
1266  }
1267 
1275  public function listTables( $prefix = null, $fname = __METHOD__ ) {
1276  $result = $this->query(
1277  "SHOW TABLES",
1278  $fname,
1279  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1280  );
1281 
1282  $endArray = [];
1283 
1284  foreach ( $result as $table ) {
1285  $vars = get_object_vars( $table );
1286  $table = array_pop( $vars );
1287 
1288  if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
1289  $endArray[] = $table;
1290  }
1291  }
1292 
1293  return $endArray;
1294  }
1295 
1305  public function listViews( $prefix = null, $fname = __METHOD__ ) {
1306  // The name of the column containing the name of the VIEW
1307  $propertyName = 'Tables_in_' . $this->getDBname();
1308 
1309  // Query for the VIEWS
1310  $res = $this->query(
1311  'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"',
1312  $fname,
1313  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
1314  );
1315 
1316  $allViews = [];
1317  foreach ( $res as $row ) {
1318  array_push( $allViews, $row->$propertyName );
1319  }
1320 
1321  if ( $prefix === null || $prefix === '' ) {
1322  return $allViews;
1323  }
1324 
1325  $filteredViews = [];
1326  foreach ( $allViews as $viewName ) {
1327  // Does the name of this VIEW start with the table-prefix?
1328  if ( strpos( $viewName, $prefix ) === 0 ) {
1329  array_push( $filteredViews, $viewName );
1330  }
1331  }
1332 
1333  return $filteredViews;
1334  }
1335 
1344  public function isView( $name, $prefix = null ) {
1345  return in_array( $name, $this->listViews( $prefix, __METHOD__ ) );
1346  }
1347 
1348  protected function isTransactableQuery( $sql ) {
1349  return parent::isTransactableQuery( $sql ) &&
1350  !preg_match( '/^SELECT\s+(GET|RELEASE|IS_FREE)_LOCK\‍(/', $sql );
1351  }
1352 
1353  public function buildStringCast( $field ) {
1354  return "CAST( $field AS BINARY )";
1355  }
1356 
1361  public function buildIntegerCast( $field ) {
1362  return 'CAST( ' . $field . ' AS SIGNED )';
1363  }
1364 
1365  public function selectSQLText(
1366  $table,
1367  $vars,
1368  $conds = '',
1369  $fname = __METHOD__,
1370  $options = [],
1371  $join_conds = []
1372  ) {
1373  $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
1374  // https://dev.mysql.com/doc/refman/5.7/en/optimizer-hints.html
1375  // https://mariadb.com/kb/en/library/aborting-statements/
1376  $timeoutMsec = intval( $options['MAX_EXECUTION_TIME'] ?? 0 );
1377  if ( $timeoutMsec > 0 ) {
1378  list( $vendor, $number ) = $this->getMySqlServerVariant();
1379  if ( $vendor === 'MariaDB' && version_compare( $number, '10.1.2', '>=' ) ) {
1380  $timeoutSec = $timeoutMsec / 1000;
1381  $sql = "SET STATEMENT max_statement_time=$timeoutSec FOR $sql";
1382  } elseif ( $vendor === 'MySQL' && version_compare( $number, '5.7.0', '>=' ) ) {
1383  $sql = preg_replace(
1384  '/^SELECT(?=\s)/',
1385  "SELECT /*+ MAX_EXECUTION_TIME($timeoutMsec)*/",
1386  $sql
1387  );
1388  }
1389  }
1390 
1391  return $sql;
1392  }
1393 
1394  /*
1395  * @return bool Whether GTID support is used (mockable for testing)
1396  */
1397  protected function useGTIDs() {
1398  return $this->useGTIDs;
1399  }
1400 }
1401 
1405 class_alias( DatabaseMysqlBase::class, 'DatabaseMysqlBase' );
Wikimedia\Rdbms\DatabaseMysqlBase\fetchSecondsSinceHeartbeat
fetchSecondsSinceHeartbeat(array $conds)
Definition: DatabaseMysqlBase.php:630
Wikimedia\Rdbms\DatabaseMysqlBase\doLockTables
doLockTables(array $read, array $write, $method)
Helper function for lockTables() that handles the actual table locking.
Definition: DatabaseMysqlBase.php:1089
Wikimedia\Rdbms\DatabaseMysqlBase\addIdentifierQuotes
addIdentifierQuotes( $s)
MySQL uses backticks for identifier quoting instead of the sql standard "double quotes".
Definition: DatabaseMysqlBase.php:470
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:52
Wikimedia\Rdbms\DatabaseMysqlBase\wasQueryTimeout
wasQueryTimeout( $error, $errno)
Checks whether the cause of the error is detected to be a timeout.
Definition: DatabaseMysqlBase.php:263
Wikimedia\Rdbms\DatabaseMysqlBase\$sslKeyPath
string null $sslKeyPath
Definition: DatabaseMysqlBase.php:53
Wikimedia\Rdbms\DatabaseMysqlBase\getLagDetectionMethod
getLagDetectionMethod()
Definition: DatabaseMysqlBase.php:495
Wikimedia\Rdbms\DatabaseMysqlBase\wasKnownStatementRollbackError
wasKnownStatementRollbackError()
Definition: DatabaseMysqlBase.php:1227
Wikimedia\Rdbms\DatabaseMysqlBase\getServerId
getServerId()
Definition: DatabaseMysqlBase.php:862
Wikimedia\Rdbms\DatabaseMysqlBase\indexInfo
indexInfo( $table, $index, $fname=__METHOD__)
Get information about an index into an object Returns false if the index does not exist.
Definition: DatabaseMysqlBase.php:414
Wikimedia\Rdbms\DatabaseMysqlBase\doLockIsFree
doLockIsFree(string $lockName, string $method)
Definition: DatabaseMysqlBase.php:1033
Wikimedia\Rdbms\DatabaseMysqlBase\setSessionOptions
setSessionOptions(array $options)
Definition: DatabaseMysqlBase.php:1011
Wikimedia\Rdbms\DatabaseMysqlBase\doReplace
doReplace( $table, array $identityKey, array $rows, $fname)
Definition: DatabaseMysqlBase.php:1186
Wikimedia\Rdbms\DatabaseMysqlBase\mysqlRealEscapeString
mysqlRealEscapeString( $s)
Wikimedia\Rdbms\DatabaseMysqlBase\$sslCAPath
string null $sslCAPath
Definition: DatabaseMysqlBase.php:59
if
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:92
Wikimedia\Rdbms\DatabaseMysqlBase\getApproximateLagStatus
getApproximateLagStatus()
Get a replica DB lag estimate for this server at the start of a transaction.
Definition: DatabaseMysqlBase.php:645
Wikimedia\Rdbms\DatabaseMysqlBase\getServerUUID
getServerUUID()
Definition: DatabaseMysqlBase.php:880
Wikimedia\Rdbms\DatabaseMysqlBase\wasLockTimeout
wasLockTimeout()
Determines if the last failure was due to a lock timeout.
Definition: DatabaseMysqlBase.php:1209
LIST_AND
const LIST_AND
Definition: Defines.php:43
Wikimedia\Rdbms\DatabaseMysqlBase\getPrimaryPos
getPrimaryPos()
Get the position of the primary DB from SHOW MASTER STATUS.
Definition: DatabaseMysqlBase.php:811
Wikimedia\Rdbms\Database\indexName
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
Definition: Database.php:3418
Wikimedia\Rdbms\Database\executeQuery
executeQuery( $sql, $fname, $flags)
Execute a query, retrying it if there is a recoverable connection loss.
Definition: Database.php:1329
Wikimedia\Rdbms\DatabaseDomain\getTablePrefix
getTablePrefix()
Definition: DatabaseDomain.php:193
Wikimedia\Rdbms\DatabaseMysqlBase\isConnectionError
isConnectionError( $errno)
Do not use this method outside of Database/DBError classes.
Definition: DatabaseMysqlBase.php:1223
Wikimedia\Rdbms\DatabaseMysqlBase\$sslCertPath
string null $sslCertPath
Definition: DatabaseMysqlBase.php:55
Wikimedia\Rdbms\DatabaseMysqlBase\$useGTIDs
bool $useGTIDs
bool Whether to use GTID methods
Definition: DatabaseMysqlBase.php:51
Wikimedia\Rdbms
Definition: ChronologyProtector.php:24
Wikimedia\Rdbms\Database\normalizeConditions
normalizeConditions( $conds, $fname)
Definition: Database.php:2244
Wikimedia\Rdbms\Database\$server
string null $server
Server that this instance is currently connected to.
Definition: Database.php:83
Wikimedia\Rdbms\DatabaseMysqlBase\selectSQLText
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Take the same arguments as IDatabase::select() and return the SQL it would use.This can be useful for...
Definition: DatabaseMysqlBase.php:1365
LIST_SET
const LIST_SET
Definition: Defines.php:44
Wikimedia\Rdbms\DatabaseMysqlBase\doGetLag
doGetLag()
Get the amount of replication lag for this database server.
Definition: DatabaseMysqlBase.php:484
Wikimedia\Rdbms\Database\extractSingleFieldFromList
extractSingleFieldFromList( $var)
Definition: Database.php:2439
Wikimedia\Rdbms\DatabaseMysqlBase\ignoreIndexClause
ignoreIndexClause( $index)
Definition: DatabaseMysqlBase.php:951
Wikimedia\Rdbms\DatabaseDomain\getDatabase
getDatabase()
Definition: DatabaseDomain.php:179
$res
$res
Definition: testCompression.php:57
Wikimedia\Rdbms\DatabaseMysqlBase\fieldInfo
fieldInfo( $table, $field)
Definition: DatabaseMysqlBase.php:391
Wikimedia\Rdbms\DBError
Database error base class.
Definition: DBError.php:32
Wikimedia\Rdbms\DatabaseMysqlBase\setBigSelects
setBigSelects( $value=true)
Definition: DatabaseMysqlBase.php:1120
Wikimedia\Rdbms\DatabaseMysqlBase\__construct
__construct(array $params)
Additional $params include:
Definition: DatabaseMysqlBase.php:103
Wikimedia\Rdbms\DatabaseMysqlBase\buildIntegerCast
buildIntegerCast( $field)
Definition: DatabaseMysqlBase.php:1361
Wikimedia\Rdbms\DatabaseMysqlBase\wasDeadlock
wasDeadlock()
Determines if the last failure was due to a deadlock.
Definition: DatabaseMysqlBase.php:1200
Wikimedia\Rdbms\DBPrimaryPos\__toString
__toString()
Wikimedia\Rdbms\DBPrimaryPos
An object representing a primary or replica DB position in a replicated setup.
Definition: DBPrimaryPos.php:15
Wikimedia\Rdbms\DatabaseMysqlBase\open
open( $server, $user, $password, $db, $schema, $tablePrefix)
Open a new connection to the database (closing any existing one)
Definition: DatabaseMysqlBase.php:128
Wikimedia\Rdbms\DatabaseMysqlBase\doUnlock
doUnlock(string $lockName, string $method)
Definition: DatabaseMysqlBase.php:1062
Wikimedia\Rdbms\DatabaseMysqlBase\normalizeJoinType
normalizeJoinType(string $joinType)
Validate and normalize a join type.
Definition: DatabaseMysqlBase.php:439
Wikimedia\Rdbms\DatabaseMysqlBase\$lastKnownReplicaPos
MySQLPrimaryPos $lastKnownReplicaPos
Definition: DatabaseMysqlBase.php:45
Wikimedia\Rdbms\Database\close
close( $fname=__METHOD__, $owner=null)
Close the database connection.
Definition: Database.php:965
Wikimedia\Rdbms\DatabaseMysqlBase\doUnlockTables
doUnlockTables( $method)
Helper function for unlockTables() that handles the actual table unlocking.
Definition: DatabaseMysqlBase.php:1107
Wikimedia\Rdbms\DatabaseMysqlBase\SERVER_ID_CACHE_TTL
const SERVER_ID_CACHE_TTL
Definition: DatabaseMysqlBase.php:79
Wikimedia\Rdbms\DatabaseMysqlBase\$sslCiphers
string null $sslCiphers
Open SSL cipher list string.
Definition: DatabaseMysqlBase.php:65
Wikimedia\Rdbms\DatabaseMysqlBase\mysqlConnect
mysqlConnect( $server, $user, $password, $db)
Open a connection to a MySQL server.
Wikimedia\Rdbms\DatabaseMysqlBase\buildStringCast
buildStringCast( $field)
Field or column to cast string 1.28Stability: stableto override
Definition: DatabaseMysqlBase.php:1353
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:997
Wikimedia\Rdbms\DatabaseMysqlBase\duplicateTableStructure
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Definition: DatabaseMysqlBase.php:1254
Wikimedia\Rdbms\Database\escapeLikeInternal
escapeLikeInternal( $s, $escapeChar='`')
Definition: Database.php:3469
Wikimedia\Rdbms\DatabaseMysqlBase\getLagFromPtHeartbeat
getLagFromPtHeartbeat()
Definition: DatabaseMysqlBase.php:522
Wikimedia\Rdbms\Database\reportQueryError
reportQueryError( $error, $errno, $sql, $fname, $ignore=false)
Report a query error.
Definition: Database.php:1760
Wikimedia\Rdbms\DatabaseMysqlBase\estimateRowCount
estimateRowCount( $tables, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Estimate rows in dataset Returns estimated count, based on EXPLAIN output Takes same arguments as Dat...
Definition: DatabaseMysqlBase.php:324
Wikimedia\Rdbms\DatabaseMysqlBase\getSoftwareLink
getSoftwareLink()
Definition: DatabaseMysqlBase.php:958
Wikimedia\Rdbms\Database\makeInsertLists
makeInsertLists(array $rows)
Make SQL lists of columns, row tuples for INSERT/VALUES expressions.
Definition: Database.php:2576
Wikimedia\Rdbms\DatabaseMysqlBase\listTables
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
Definition: DatabaseMysqlBase.php:1275
Wikimedia\Rdbms\DatabaseMysqlBase\lastError
lastError()
Definition: DatabaseMysqlBase.php:236
Wikimedia\Rdbms\DatabaseMysqlBase\getType
getType()
Definition: DatabaseMysqlBase.php:124
Wikimedia\Rdbms\DatabaseMysqlBase\$sslCAFile
string null $sslCAFile
Definition: DatabaseMysqlBase.php:57
Wikimedia\Rdbms\DatabaseMysqlBase\getReplicationSafetyInfo
getReplicationSafetyInfo()
Definition: DatabaseMysqlBase.php:295
Wikimedia\Rdbms\DatabaseMysqlBase\streamStatementEnd
streamStatementEnd(&$sql, &$newLine)
Definition: DatabaseMysqlBase.php:1024
Wikimedia\Rdbms\DatabaseMysqlBase\getTopologyBasedServerId
getTopologyBasedServerId()
Get a non-recycled ID that uniquely identifies this server within the replication topology....
Definition: DatabaseMysqlBase.php:854
Wikimedia\Rdbms\Database\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)
Definition: Database.php:2102
Wikimedia\Rdbms\Database\installErrorHandler
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition: Database.php:902
Wikimedia\Rdbms\Database\restoreErrorHandler
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition: Database.php:913
Wikimedia\Rdbms\DatabaseMysqlBase\doUpsert
doUpsert(string $table, array $rows, array $identityKey, array $set, string $fname)
Definition: DatabaseMysqlBase.php:1168
Wikimedia\Rdbms\DatabaseMysqlBase\getPrimaryServerInfo
getPrimaryServerInfo()
Definition: DatabaseMysqlBase.php:580
$s
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
Definition: mergeMessageFileList.php:206
Wikimedia\Rdbms\DatabaseMysqlBase\doLock
doLock(string $lockName, string $method, int $timeout)
Definition: DatabaseMysqlBase.php:1046
Wikimedia\Rdbms\DatabaseMysqlBase\wasReadOnlyError
wasReadOnlyError()
Determines if the last failure was due to the database being read-only.
Definition: DatabaseMysqlBase.php:1218
Wikimedia\Rdbms\DatabaseMysqlBase\$defaultBigSelects
bool null $defaultBigSelects
Definition: DatabaseMysqlBase.php:71
Wikimedia\Rdbms\Database\getLogContext
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition: Database.php:954
Wikimedia\Rdbms\DatabaseMysqlBase\primaryPosWait
primaryPosWait(DBPrimaryPos $pos, $timeout)
Wait for the replica DB to catch up to a given primary DB position.Note that this does not start any ...
Definition: DatabaseMysqlBase.php:663
Wikimedia\Rdbms\Database\fetchRow
fetchRow(IResultWrapper $res)
Fetch the next row from the given result object, in associative array form.
Definition: Database.php:863
Wikimedia\Rdbms\Database\query
query( $sql, $fname=__METHOD__, $flags=self::QUERY_NORMAL)
Run an SQL query and return the result.
Definition: Database.php:1292
Wikimedia\Rdbms\DatabaseMysqlBase\getMasterPos
getMasterPos()
Definition: DatabaseMysqlBase.php:845
Wikimedia\Rdbms\Database\tableName
tableName( $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.This does two important things: it quo...
Definition: Database.php:3087
Wikimedia\Rdbms\DatabaseMysqlBase\getReplicaPos
getReplicaPos()
Get the position of the primary DB from SHOW SLAVE STATUS.
Definition: DatabaseMysqlBase.php:781
Wikimedia\Rdbms\DatabaseMysqlBase\getLagFromSlaveStatus
getLagFromSlaveStatus()
Definition: DatabaseMysqlBase.php:502
Wikimedia\Rdbms\DatabaseMysqlBase\getServerGTIDs
getServerGTIDs( $fname=__METHOD__)
Definition: DatabaseMysqlBase.php:899
Wikimedia\Rdbms\Database\getRecordedTransactionLagStatus
getRecordedTransactionLagStatus()
Get the replica DB lag when the current transaction started.
Definition: Database.php:5267
Wikimedia\Rdbms\DatabaseMysqlBase\$utf8Mode
bool $utf8Mode
Use experimental UTF-8 transmission encoding.
Definition: DatabaseMysqlBase.php:69
Wikimedia\Rdbms\Database\$user
string null $user
User that this instance is currently connected under the name of.
Definition: Database.php:85
Wikimedia\Rdbms\DBUnexpectedError
Definition: DBUnexpectedError.php:29
Wikimedia\Rdbms\Database\selectField
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
Definition: Database.php:1835
Wikimedia\Rdbms\DatabaseMysqlBase\$replicationInfoRow
stdClass null $replicationInfoRow
Definition: DatabaseMysqlBase.php:76
Wikimedia\Rdbms\DatabaseMysqlBase\serverIsReadOnly
serverIsReadOnly()
bool Whether the DB is marked as read-only server-side If an error occurs, {query} 1....
Definition: DatabaseMysqlBase.php:930
Wikimedia\Rdbms\DatabaseMysqlBase\$sqlMode
string $sqlMode
sql_mode value to send on connection
Definition: DatabaseMysqlBase.php:67
Wikimedia\Rdbms\DatabaseMysqlBase\isTransactableQuery
isTransactableQuery( $sql)
Determine whether a SQL statement is sensitive to isolation level.
Definition: DatabaseMysqlBase.php:1348
Wikimedia\Rdbms\IDatabase\lastErrno
lastErrno()
Get the last error number.
Wikimedia\Rdbms\DatabaseMysqlBase\makeLockName
makeLockName( $lockName)
Definition: DatabaseMysqlBase.php:1075
Wikimedia\Rdbms\Database\newExceptionAfterConnectError
newExceptionAfterConnectError( $error)
Definition: Database.php:1813
$cache
$cache
Definition: mcc.php:33
Wikimedia\Rdbms\Database\$srvCache
BagOStuff $srvCache
APC cache.
Definition: Database.php:54
Wikimedia\Rdbms\DatabaseMysqlBase\strencode
strencode( $s)
Definition: DatabaseMysqlBase.php:454
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:34
Wikimedia\Rdbms\DatabaseMysqlBase\deleteJoin
deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
Definition: DatabaseMysqlBase.php:1150
Wikimedia\Rdbms\DatabaseMysqlBase\useIndexClause
useIndexClause( $index)
Definition: DatabaseMysqlBase.php:943
Wikimedia\Rdbms\DatabaseMysqlBase\getServerRoleStatus
getServerRoleStatus( $role, $fname=__METHOD__)
Definition: DatabaseMysqlBase.php:923
Wikimedia\Rdbms\DatabaseDomain\getSchema
getSchema()
Definition: DatabaseDomain.php:186
Wikimedia\Rdbms\Database\getServerName
getServerName()
Get the readable name for the server.
Definition: Database.php:3079
Wikimedia\Rdbms\DatabaseMysqlBase\tableLocksHaveTransactionScope
tableLocksHaveTransactionScope()
Checks if table locks acquired by lockTables() are transaction-bound in their scope.
Definition: DatabaseMysqlBase.php:1085
Wikimedia\Rdbms\DatabaseMysqlBase\listViews
listViews( $prefix=null, $fname=__METHOD__)
Lists VIEWs in the database.
Definition: DatabaseMysqlBase.php:1305
Wikimedia\Rdbms\Database\$flags
int $flags
Current bit field of class DBO_* constants.
Definition: Database.php:106
Wikimedia\Rdbms\DatabaseMysqlBase\isView
isView( $name, $prefix=null)
Differentiates between a TABLE and a VIEW.
Definition: DatabaseMysqlBase.php:1344
Wikimedia\Rdbms\Database\select
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
Definition: Database.php:2003
Wikimedia\Rdbms\DatabaseMysqlBase\$lagDetectionOptions
array $lagDetectionOptions
Method to detect replica DB lag.
Definition: DatabaseMysqlBase.php:49
Wikimedia\Rdbms\Database\addQuotes
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.string
Definition: Database.php:3426
Wikimedia\Rdbms\DatabaseMysqlBase\$insertSelectIsSafe
bool null $insertSelectIsSafe
Definition: DatabaseMysqlBase.php:74
Wikimedia\Rdbms\Database\$password
string null $password
Password used to establish the current connection.
Definition: Database.php:87
Wikimedia\Rdbms\Database\qualifiedTableComponents
qualifiedTableComponents( $name)
Get the table components needed for a query given the currently selected database.
Definition: Database.php:3145
Wikimedia\Rdbms\DatabaseDomain
Class to handle database/schema/prefix specifications for IDatabase.
Definition: DatabaseDomain.php:40
Wikimedia\Rdbms\DatabaseMysqlBase\doSelectDomain
doSelectDomain(DatabaseDomain $domain)
Definition: DatabaseMysqlBase.php:184
Wikimedia\Rdbms\DatabaseMysqlBase\getServerVersion
getServerVersion()
Definition: DatabaseMysqlBase.php:992
Wikimedia\Rdbms\DatabaseMysqlBase\getMySqlServerVariant
getMySqlServerVariant()
Definition: DatabaseMysqlBase.php:970
Wikimedia\Rdbms\Database\makeList
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Definition: Database.php:2655
Wikimedia\Rdbms\DatabaseMysqlBase\useGTIDs
useGTIDs()
Definition: DatabaseMysqlBase.php:1397
Wikimedia\Rdbms\DatabaseMysqlBase\mysqlError
mysqlError( $conn=null)
Returns the text of the error message from previous MySQL operation.
Wikimedia\Rdbms\DatabaseMysqlBase\getMasterServerInfo
getMasterServerInfo()
Definition: DatabaseMysqlBase.php:620
Wikimedia\Rdbms\Database\$conn
object resource null $conn
Database connection.
Definition: Database.php:77
Wikimedia\Rdbms\DatabaseMysqlBase
Database abstraction object for MySQL.
Definition: DatabaseMysqlBase.php:43
Wikimedia\Rdbms\MySQLPrimaryPos
DBPrimaryPos class for MySQL/MariaDB.
Definition: MySQLPrimaryPos.php:20
Wikimedia\Rdbms\DatabaseMysqlBase\isQuotedIdentifier
isQuotedIdentifier( $name)
Definition: DatabaseMysqlBase.php:480
Wikimedia\Rdbms\Database\getDBname
getDBname()
Get the current database name; null if there isn't one.
Definition: Database.php:3071
Wikimedia\Rdbms\DatabaseMysqlBase\namedLocksEnqueue
namedLocksEnqueue()
Check to see if a named lock used by lock() use blocking queues.bool 1.26Stability: stableto override
Definition: DatabaseMysqlBase.php:1081
Wikimedia\Rdbms\DatabaseMysqlBase\tableExists
tableExists( $table, $fname=__METHOD__)
Query whether a given table exists.
Definition: DatabaseMysqlBase.php:355
Wikimedia\Rdbms\DatabaseMysqlBase\$lagDetectionMethod
string $lagDetectionMethod
Method to detect replica DB lag.
Definition: DatabaseMysqlBase.php:47
Wikimedia\Rdbms\DatabaseMysqlBase\isInsertSelectSafe
isInsertSelectSafe(array $insertOptions, array $selectOptions)
Definition: DatabaseMysqlBase.php:269