MediaWiki  1.33.0
Database.php
Go to the documentation of this file.
1 <?php
26 namespace Wikimedia\Rdbms;
27 
28 use Psr\Log\LoggerAwareInterface;
29 use Psr\Log\LoggerInterface;
30 use Psr\Log\NullLogger;
31 use Wikimedia\ScopedCallback;
32 use Wikimedia\Timestamp\ConvertibleTimestamp;
36 use LogicException;
37 use InvalidArgumentException;
38 use UnexpectedValueException;
39 use Exception;
40 use RuntimeException;
41 
48 abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAwareInterface {
50  const DEADLOCK_TRIES = 4;
52  const DEADLOCK_DELAY_MIN = 500000;
54  const DEADLOCK_DELAY_MAX = 1500000;
55 
57  const PING_TTL = 1.0;
58  const PING_QUERY = 'SELECT 1 AS ping';
59 
60  const TINY_WRITE_SEC = 0.010;
61  const SLOW_WRITE_SEC = 0.500;
62  const SMALL_WRITE_ROWS = 100;
63 
65  const ATTR_DB_LEVEL_LOCKING = 'db-level-locking';
67  const ATTR_SCHEMAS_AS_TABLE_GROUPS = 'supports-schemas';
68 
70  const NEW_UNCONNECTED = 0;
72  const NEW_CONNECTED = 1;
73 
75  protected $lastQuery = '';
77  protected $lastWriteTime = false;
79  protected $phpError = false;
81  protected $server;
83  protected $user;
85  protected $password;
87  protected $tableAliases = [];
89  protected $indexAliases = [];
91  protected $cliMode;
93  protected $agent;
95  protected $connectionParams = [];
97  protected $srvCache;
99  protected $connLogger;
101  protected $queryLogger;
103  protected $errorLogger;
106 
108  protected $conn = null;
110  protected $opened = false;
111 
113  protected $trxIdleCallbacks = [];
115  protected $trxPreCommitCallbacks = [];
117  protected $trxEndCallbacks = [];
119  protected $trxRecurringCallbacks = [];
121  protected $trxEndCallbacksSuppressed = false;
122 
124  protected $flags;
126  protected $lbInfo = [];
128  protected $schemaVars = false;
130  protected $sessionVars = [];
132  protected $preparedArgs;
134  protected $htmlErrors;
136  protected $delimiter = ';';
138  protected $currentDomain;
140  protected $affectedRowCount;
141 
145  protected $trxStatus = self::STATUS_TRX_NONE;
149  protected $trxStatusCause;
161  protected $trxLevel = 0;
168  protected $trxShortId = '';
177  private $trxTimestamp = null;
179  private $trxReplicaLag = null;
187  private $trxFname = null;
194  private $trxDoneWrites = false;
201  private $trxAutomatic = false;
207  private $trxAtomicCounter = 0;
213  private $trxAtomicLevels = [];
219  private $trxAutomaticAtomic = false;
225  private $trxWriteCallers = [];
229  private $trxWriteDuration = 0.0;
233  private $trxWriteQueryCount = 0;
241  private $trxWriteAdjDuration = 0.0;
249  private $rttEstimate = 0.0;
250 
252  private $namedLocksHeld = [];
254  protected $sessionTempTables = [];
255 
258 
260  protected $lastPing = 0.0;
261 
263  private $priorFlags = [];
264 
266  protected $profiler;
268  protected $trxProfiler;
269 
272 
274  private static $NOT_APPLICABLE = 'n/a';
276  private static $SAVEPOINT_PREFIX = 'wikimedia_rdbms_atomic';
277 
279  const STATUS_TRX_ERROR = 1;
281  const STATUS_TRX_OK = 2;
283  const STATUS_TRX_NONE = 3;
284 
286  const TEMP_NORMAL = 1;
288  const TEMP_PSEUDO_PERMANENT = 2;
289 
294  protected function __construct( array $params ) {
295  foreach ( [ 'host', 'user', 'password', 'dbname', 'schema', 'tablePrefix' ] as $name ) {
296  $this->connectionParams[$name] = $params[$name];
297  }
298 
299  $this->cliMode = $params['cliMode'];
300  // Agent name is added to SQL queries in a comment, so make sure it can't break out
301  $this->agent = str_replace( '/', '-', $params['agent'] );
302 
303  $this->flags = $params['flags'];
304  if ( $this->flags & self::DBO_DEFAULT ) {
305  if ( $this->cliMode ) {
306  $this->flags &= ~self::DBO_TRX;
307  } else {
308  $this->flags |= self::DBO_TRX;
309  }
310  }
311  // Disregard deprecated DBO_IGNORE flag (T189999)
312  $this->flags &= ~self::DBO_IGNORE;
313 
314  $this->sessionVars = $params['variables'];
315 
316  $this->srvCache = $params['srvCache'] ?? new HashBagOStuff();
317 
318  $this->profiler = is_callable( $params['profiler'] ) ? $params['profiler'] : null;
319  $this->trxProfiler = $params['trxProfiler'];
320  $this->connLogger = $params['connLogger'];
321  $this->queryLogger = $params['queryLogger'];
322  $this->errorLogger = $params['errorLogger'];
323  $this->deprecationLogger = $params['deprecationLogger'];
324 
325  if ( isset( $params['nonNativeInsertSelectBatchSize'] ) ) {
326  $this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'];
327  }
328 
329  // Set initial dummy domain until open() sets the final DB/prefix
330  $this->currentDomain = new DatabaseDomain(
331  $params['dbname'] != '' ? $params['dbname'] : null,
332  $params['schema'] != '' ? $params['schema'] : null,
333  $params['tablePrefix']
334  );
335  }
336 
345  final public function initConnection() {
346  if ( $this->isOpen() ) {
347  throw new LogicException( __METHOD__ . ': already connected.' );
348  }
349  // Establish the connection
350  $this->doInitConnection();
351  }
352 
360  protected function doInitConnection() {
361  if ( strlen( $this->connectionParams['user'] ) ) {
362  $this->open(
363  $this->connectionParams['host'],
364  $this->connectionParams['user'],
365  $this->connectionParams['password'],
366  $this->connectionParams['dbname'],
367  $this->connectionParams['schema'],
368  $this->connectionParams['tablePrefix']
369  );
370  } else {
371  throw new InvalidArgumentException( "No database user provided." );
372  }
373  }
374 
387  abstract protected function open( $server, $user, $password, $dbName, $schema, $tablePrefix );
388 
434  final public static function factory( $dbType, $p = [], $connect = self::NEW_CONNECTED ) {
435  $class = self::getClass( $dbType, $p['driver'] ?? null );
436 
437  if ( class_exists( $class ) && is_subclass_of( $class, IDatabase::class ) ) {
438  // Resolve some defaults for b/c
439  $p['host'] = $p['host'] ?? false;
440  $p['user'] = $p['user'] ?? false;
441  $p['password'] = $p['password'] ?? false;
442  $p['dbname'] = $p['dbname'] ?? false;
443  $p['flags'] = $p['flags'] ?? 0;
444  $p['variables'] = $p['variables'] ?? [];
445  $p['tablePrefix'] = $p['tablePrefix'] ?? '';
446  $p['schema'] = $p['schema'] ?? null;
447  $p['cliMode'] = $p['cliMode'] ?? ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
448  $p['agent'] = $p['agent'] ?? '';
449  if ( !isset( $p['connLogger'] ) ) {
450  $p['connLogger'] = new NullLogger();
451  }
452  if ( !isset( $p['queryLogger'] ) ) {
453  $p['queryLogger'] = new NullLogger();
454  }
455  $p['profiler'] = $p['profiler'] ?? null;
456  if ( !isset( $p['trxProfiler'] ) ) {
457  $p['trxProfiler'] = new TransactionProfiler();
458  }
459  if ( !isset( $p['errorLogger'] ) ) {
460  $p['errorLogger'] = function ( Exception $e ) {
461  trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
462  };
463  }
464  if ( !isset( $p['deprecationLogger'] ) ) {
465  $p['deprecationLogger'] = function ( $msg ) {
466  trigger_error( $msg, E_USER_DEPRECATED );
467  };
468  }
469 
471  $conn = new $class( $p );
472  if ( $connect == self::NEW_CONNECTED ) {
473  $conn->initConnection();
474  }
475  } else {
476  $conn = null;
477  }
478 
479  return $conn;
480  }
481 
489  final public static function attributesFromType( $dbType, $driver = null ) {
490  static $defaults = [
491  self::ATTR_DB_LEVEL_LOCKING => false,
492  self::ATTR_SCHEMAS_AS_TABLE_GROUPS => false
493  ];
494 
495  $class = self::getClass( $dbType, $driver );
496 
497  return call_user_func( [ $class, 'getAttributes' ] ) + $defaults;
498  }
499 
506  private static function getClass( $dbType, $driver = null ) {
507  // For database types with built-in support, the below maps type to IDatabase
508  // implementations. For types with multipe driver implementations (PHP extensions),
509  // an array can be used, keyed by extension name. In case of an array, the
510  // optional 'driver' parameter can be used to force a specific driver. Otherwise,
511  // we auto-detect the first available driver. For types without built-in support,
512  // an class named "Database<Type>" us used, eg. DatabaseFoo for type 'foo'.
513  static $builtinTypes = [
514  'mssql' => DatabaseMssql::class,
515  'mysql' => [ 'mysqli' => DatabaseMysqli::class ],
516  'sqlite' => DatabaseSqlite::class,
517  'postgres' => DatabasePostgres::class,
518  ];
519 
520  $dbType = strtolower( $dbType );
521  $class = false;
522 
523  if ( isset( $builtinTypes[$dbType] ) ) {
524  $possibleDrivers = $builtinTypes[$dbType];
525  if ( is_string( $possibleDrivers ) ) {
526  $class = $possibleDrivers;
527  } elseif ( (string)$driver !== '' ) {
528  if ( !isset( $possibleDrivers[$driver] ) ) {
529  throw new InvalidArgumentException( __METHOD__ .
530  " type '$dbType' does not support driver '{$driver}'" );
531  }
532 
533  $class = $possibleDrivers[$driver];
534  } else {
535  foreach ( $possibleDrivers as $posDriver => $possibleClass ) {
536  if ( extension_loaded( $posDriver ) ) {
537  $class = $possibleClass;
538  break;
539  }
540  }
541  }
542  } else {
543  $class = 'Database' . ucfirst( $dbType );
544  }
545 
546  if ( $class === false ) {
547  throw new InvalidArgumentException( __METHOD__ .
548  " no viable database extension found for type '$dbType'" );
549  }
550 
551  return $class;
552  }
553 
558  protected static function getAttributes() {
559  return [];
560  }
561 
569  public function setLogger( LoggerInterface $logger ) {
570  $this->queryLogger = $logger;
571  }
572 
573  public function getServerInfo() {
574  return $this->getServerVersion();
575  }
576 
577  public function bufferResults( $buffer = null ) {
578  $res = !$this->getFlag( self::DBO_NOBUFFER );
579  if ( $buffer !== null ) {
580  $buffer
581  ? $this->clearFlag( self::DBO_NOBUFFER )
582  : $this->setFlag( self::DBO_NOBUFFER );
583  }
584 
585  return $res;
586  }
587 
588  public function trxLevel() {
589  return $this->trxLevel;
590  }
591 
592  public function trxTimestamp() {
593  return $this->trxLevel ? $this->trxTimestamp : null;
594  }
595 
600  public function trxStatus() {
601  return $this->trxStatus;
602  }
603 
604  public function tablePrefix( $prefix = null ) {
605  $old = $this->currentDomain->getTablePrefix();
606  if ( $prefix !== null ) {
607  $this->currentDomain = new DatabaseDomain(
608  $this->currentDomain->getDatabase(),
609  $this->currentDomain->getSchema(),
610  $prefix
611  );
612  }
613 
614  return $old;
615  }
616 
617  public function dbSchema( $schema = null ) {
618  if ( strlen( $schema ) && $this->getDBname() === null ) {
619  throw new DBUnexpectedError( $this, "Cannot set schema to '$schema'; no database set." );
620  }
621 
622  $old = $this->currentDomain->getSchema();
623  if ( $schema !== null ) {
624  $this->currentDomain = new DatabaseDomain(
625  $this->currentDomain->getDatabase(),
626  // DatabaseDomain uses null for unspecified schemas
627  strlen( $schema ) ? $schema : null,
628  $this->currentDomain->getTablePrefix()
629  );
630  }
631 
632  return (string)$old;
633  }
634 
638  protected function relationSchemaQualifier() {
639  return $this->dbSchema();
640  }
641 
642  public function getLBInfo( $name = null ) {
643  if ( is_null( $name ) ) {
644  return $this->lbInfo;
645  }
646 
647  if ( array_key_exists( $name, $this->lbInfo ) ) {
648  return $this->lbInfo[$name];
649  }
650 
651  return null;
652  }
653 
654  public function setLBInfo( $name, $value = null ) {
655  if ( is_null( $value ) ) {
656  $this->lbInfo = $name;
657  } else {
658  $this->lbInfo[$name] = $value;
659  }
660  }
661 
662  public function setLazyMasterHandle( IDatabase $conn ) {
663  $this->lazyMasterHandle = $conn;
664  }
665 
671  protected function getLazyMasterHandle() {
673  }
674 
675  public function implicitGroupby() {
676  return true;
677  }
678 
679  public function implicitOrderby() {
680  return true;
681  }
682 
683  public function lastQuery() {
684  return $this->lastQuery;
685  }
686 
687  public function doneWrites() {
688  return (bool)$this->lastWriteTime;
689  }
690 
691  public function lastDoneWrites() {
692  return $this->lastWriteTime ?: false;
693  }
694 
695  public function writesPending() {
696  return $this->trxLevel && $this->trxDoneWrites;
697  }
698 
699  public function writesOrCallbacksPending() {
700  return $this->trxLevel && (
701  $this->trxDoneWrites ||
702  $this->trxIdleCallbacks ||
703  $this->trxPreCommitCallbacks ||
705  );
706  }
707 
708  public function preCommitCallbacksPending() {
709  return $this->trxLevel && $this->trxPreCommitCallbacks;
710  }
711 
715  final protected function getTransactionRoundId() {
716  // If transaction round participation is enabled, see if one is active
717  if ( $this->getFlag( self::DBO_TRX ) ) {
718  $id = $this->getLBInfo( 'trxRoundId' );
719 
720  return is_string( $id ) ? $id : null;
721  }
722 
723  return null;
724  }
725 
726  public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
727  if ( !$this->trxLevel ) {
728  return false;
729  } elseif ( !$this->trxDoneWrites ) {
730  return 0.0;
731  }
732 
733  switch ( $type ) {
734  case self::ESTIMATE_DB_APPLY:
735  return $this->pingAndCalculateLastTrxApplyTime();
736  default: // everything
738  }
739  }
740 
744  private function pingAndCalculateLastTrxApplyTime() {
745  $this->ping( $rtt );
746 
747  $rttAdjTotal = $this->trxWriteAdjQueryCount * $rtt;
748  $applyTime = max( $this->trxWriteAdjDuration - $rttAdjTotal, 0 );
749  // For omitted queries, make them count as something at least
750  $omitted = $this->trxWriteQueryCount - $this->trxWriteAdjQueryCount;
751  $applyTime += self::TINY_WRITE_SEC * $omitted;
752 
753  return $applyTime;
754  }
755 
756  public function pendingWriteCallers() {
757  return $this->trxLevel ? $this->trxWriteCallers : [];
758  }
759 
760  public function pendingWriteRowsAffected() {
762  }
763 
772  public function pendingWriteAndCallbackCallers() {
773  $fnames = $this->pendingWriteCallers();
774  foreach ( [
775  $this->trxIdleCallbacks,
776  $this->trxPreCommitCallbacks,
777  $this->trxEndCallbacks
778  ] as $callbacks ) {
779  foreach ( $callbacks as $callback ) {
780  $fnames[] = $callback[1];
781  }
782  }
783 
784  return $fnames;
785  }
786 
790  private function flatAtomicSectionList() {
791  return array_reduce( $this->trxAtomicLevels, function ( $accum, $v ) {
792  return $accum === null ? $v[0] : "$accum, " . $v[0];
793  } );
794  }
795 
796  public function isOpen() {
797  return $this->opened;
798  }
799 
800  public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
801  if ( ( $flag & self::DBO_IGNORE ) ) {
802  throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
803  }
804 
805  if ( $remember === self::REMEMBER_PRIOR ) {
806  array_push( $this->priorFlags, $this->flags );
807  }
808  $this->flags |= $flag;
809  }
810 
811  public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
812  if ( ( $flag & self::DBO_IGNORE ) ) {
813  throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
814  }
815 
816  if ( $remember === self::REMEMBER_PRIOR ) {
817  array_push( $this->priorFlags, $this->flags );
818  }
819  $this->flags &= ~$flag;
820  }
821 
822  public function restoreFlags( $state = self::RESTORE_PRIOR ) {
823  if ( !$this->priorFlags ) {
824  return;
825  }
826 
827  if ( $state === self::RESTORE_INITIAL ) {
828  $this->flags = reset( $this->priorFlags );
829  $this->priorFlags = [];
830  } else {
831  $this->flags = array_pop( $this->priorFlags );
832  }
833  }
834 
835  public function getFlag( $flag ) {
836  return (bool)( $this->flags & $flag );
837  }
838 
844  public function getProperty( $name ) {
845  return $this->$name;
846  }
847 
848  public function getDomainID() {
849  return $this->currentDomain->getId();
850  }
851 
852  final public function getWikiID() {
853  return $this->getDomainID();
854  }
855 
863  abstract function indexInfo( $table, $index, $fname = __METHOD__ );
864 
871  abstract function strencode( $s );
872 
876  protected function installErrorHandler() {
877  $this->phpError = false;
878  $this->htmlErrors = ini_set( 'html_errors', '0' );
879  set_error_handler( [ $this, 'connectionErrorLogger' ] );
880  }
881 
887  protected function restoreErrorHandler() {
888  restore_error_handler();
889  if ( $this->htmlErrors !== false ) {
890  ini_set( 'html_errors', $this->htmlErrors );
891  }
892 
893  return $this->getLastPHPError();
894  }
895 
899  protected function getLastPHPError() {
900  if ( $this->phpError ) {
901  $error = preg_replace( '!\[<a.*</a>\]!', '', $this->phpError );
902  $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
903 
904  return $error;
905  }
906 
907  return false;
908  }
909 
917  public function connectionErrorLogger( $errno, $errstr ) {
918  $this->phpError = $errstr;
919  }
920 
927  protected function getLogContext( array $extras = [] ) {
928  return array_merge(
929  [
930  'db_server' => $this->server,
931  'db_name' => $this->getDBname(),
932  'db_user' => $this->user,
933  ],
934  $extras
935  );
936  }
937 
938  final public function close() {
939  $exception = null; // error to throw after disconnecting
940 
941  $wasOpen = $this->opened;
942  // This should mostly do nothing if the connection is already closed
943  if ( $this->conn ) {
944  // Roll back any dangling transaction first
945  if ( $this->trxLevel ) {
946  if ( $this->trxAtomicLevels ) {
947  // Cannot let incomplete atomic sections be committed
948  $levels = $this->flatAtomicSectionList();
949  $exception = new DBUnexpectedError(
950  $this,
951  __METHOD__ . ": atomic sections $levels are still open."
952  );
953  } elseif ( $this->trxAutomatic ) {
954  // Only the connection manager can commit non-empty DBO_TRX transactions
955  // (empty ones we can silently roll back)
956  if ( $this->writesOrCallbacksPending() ) {
957  $exception = new DBUnexpectedError(
958  $this,
959  __METHOD__ .
960  ": mass commit/rollback of peer transaction required (DBO_TRX set)."
961  );
962  }
963  } else {
964  // Manual transactions should have been committed or rolled
965  // back, even if empty.
966  $exception = new DBUnexpectedError(
967  $this,
968  __METHOD__ . ": transaction is still open (from {$this->trxFname})."
969  );
970  }
971 
972  if ( $this->trxEndCallbacksSuppressed ) {
973  $exception = $exception ?: new DBUnexpectedError(
974  $this,
975  __METHOD__ . ': callbacks are suppressed; cannot properly commit.'
976  );
977  }
978 
979  // Rollback the changes and run any callbacks as needed
980  $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
981  }
982 
983  // Close the actual connection in the binding handle
984  $closed = $this->closeConnection();
985  } else {
986  $closed = true; // already closed; nothing to do
987  }
988 
989  $this->conn = false;
990  $this->opened = false;
991 
992  // Throw any unexpected errors after having disconnected
993  if ( $exception instanceof Exception ) {
994  throw $exception;
995  }
996 
997  // Note that various subclasses call close() at the start of open(), which itself is
998  // called by replaceLostConnection(). In that case, just because onTransactionResolution()
999  // callbacks are pending does not mean that an exception should be thrown. Rather, they
1000  // will be executed after the reconnection step.
1001  if ( $wasOpen ) {
1002  // Sanity check that no callbacks are dangling
1003  $fnames = $this->pendingWriteAndCallbackCallers();
1004  if ( $fnames ) {
1005  throw new RuntimeException(
1006  "Transaction callbacks are still pending:\n" . implode( ', ', $fnames )
1007  );
1008  }
1009  }
1010 
1011  return $closed;
1012  }
1013 
1022  protected function assertHasConnectionHandle() {
1023  if ( !$this->isOpen() ) {
1024  throw new DBUnexpectedError( $this, "DB connection was already closed." );
1025  }
1026  }
1027 
1033  protected function assertIsWritableMaster() {
1034  if ( $this->getLBInfo( 'replica' ) === true ) {
1035  throw new DBReadOnlyRoleError(
1036  $this,
1037  'Write operations are not allowed on replica database connections.'
1038  );
1039  }
1040  $reason = $this->getReadOnlyReason();
1041  if ( $reason !== false ) {
1042  throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
1043  }
1044  }
1045 
1051  abstract protected function closeConnection();
1052 
1058  public function reportConnectionError( $error = 'Unknown error' ) {
1059  call_user_func( $this->deprecationLogger, 'Use of ' . __METHOD__ . ' is deprecated.' );
1060  throw new DBConnectionError( $this, $this->lastError() ?: $error );
1061  }
1062 
1081  abstract protected function doQuery( $sql );
1082 
1099  protected function isWriteQuery( $sql ) {
1100  // BEGIN and COMMIT queries are considered read queries here.
1101  // Database backends and drivers (MySQL, MariaDB, php-mysqli) generally
1102  // treat these as write queries, in that their results have "affected rows"
1103  // as meta data as from writes, instead of "num rows" as from reads.
1104  // But, we treat them as read queries because when reading data (from
1105  // either replica or master) we use transactions to enable repeatable-read
1106  // snapshots, which ensures we get consistent results from the same snapshot
1107  // for all queries within a request. Use cases:
1108  // - Treating these as writes would trigger ChronologyProtector (see method doc).
1109  // - We use this method to reject writes to replicas, but we need to allow
1110  // use of transactions on replicas for read snapshots. This fine given
1111  // that transactions by themselves don't make changes, only actual writes
1112  // within the transaction matter, which we still detect.
1113  return !preg_match(
1114  '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SAVEPOINT|RELEASE|SET|SHOW|EXPLAIN|\(SELECT)\b/i',
1115  $sql
1116  );
1117  }
1118 
1123  protected function getQueryVerb( $sql ) {
1124  return preg_match( '/^\s*([a-z]+)/i', $sql, $m ) ? strtoupper( $m[1] ) : null;
1125  }
1126 
1140  protected function isTransactableQuery( $sql ) {
1141  return !in_array(
1142  $this->getQueryVerb( $sql ),
1143  [ 'BEGIN', 'ROLLBACK', 'COMMIT', 'SET', 'SHOW', 'CREATE', 'ALTER' ],
1144  true
1145  );
1146  }
1147 
1153  protected function registerTempTableWrite( $sql, $pseudoPermanent ) {
1154  static $qt = '[`"\']?(\w+)[`"\']?'; // quoted table
1155 
1156  if ( preg_match(
1157  '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?' . $qt . '/i',
1158  $sql,
1159  $matches
1160  ) ) {
1161  $type = $pseudoPermanent ? self::TEMP_PSEUDO_PERMANENT : self::TEMP_NORMAL;
1162  $this->sessionTempTables[$matches[1]] = $type;
1163 
1164  return $type;
1165  } elseif ( preg_match(
1166  '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?' . $qt . '/i',
1167  $sql,
1168  $matches
1169  ) ) {
1170  $type = $this->sessionTempTables[$matches[1]] ?? null;
1171  unset( $this->sessionTempTables[$matches[1]] );
1172 
1173  return $type;
1174  } elseif ( preg_match(
1175  '/^TRUNCATE\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?' . $qt . '/i',
1176  $sql,
1177  $matches
1178  ) ) {
1179  return $this->sessionTempTables[$matches[1]] ?? null;
1180  } elseif ( preg_match(
1181  '/^(?:(?:INSERT|REPLACE)\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+' . $qt . '/i',
1182  $sql,
1183  $matches
1184  ) ) {
1185  return $this->sessionTempTables[$matches[1]] ?? null;
1186  }
1187 
1188  return null;
1189  }
1190 
1191  public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
1192  $this->assertTransactionStatus( $sql, $fname );
1193  $this->assertHasConnectionHandle();
1194 
1195  $flags = (int)$flags; // b/c; this field used to be a bool
1196  $ignoreErrors = $this->hasFlags( $flags, self::QUERY_SILENCE_ERRORS );
1197 
1198  $priorTransaction = $this->trxLevel;
1199  $priorWritesPending = $this->writesOrCallbacksPending();
1200  $this->lastQuery = $sql;
1201 
1202  if ( $this->isWriteQuery( $sql ) ) {
1203  # In theory, non-persistent writes are allowed in read-only mode, but due to things
1204  # like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway...
1205  $this->assertIsWritableMaster();
1206  # Do not treat temporary table writes as "meaningful writes" that need committing.
1207  # Profile them as reads. Integration tests can override this behavior via $flags.
1208  $pseudoPermanent = $this->hasFlags( $flags, self::QUERY_PSEUDO_PERMANENT );
1209  $tableType = $this->registerTempTableWrite( $sql, $pseudoPermanent );
1210  $isEffectiveWrite = ( $tableType !== self::TEMP_NORMAL );
1211  # DBConnRef uses QUERY_REPLICA_ROLE to enforce the replica role for raw SQL queries
1212  if ( $isEffectiveWrite && $this->hasFlags( $flags, self::QUERY_REPLICA_ROLE ) ) {
1213  throw new DBReadOnlyRoleError( $this, "Cannot write; target role is DB_REPLICA" );
1214  }
1215  } else {
1216  $isEffectiveWrite = false;
1217  }
1218 
1219  # Add trace comment to the begin of the sql string, right after the operator.
1220  # Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598)
1221  $commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
1222 
1223  # Send the query to the server and fetch any corresponding errors
1224  $ret = $this->attemptQuery( $sql, $commentedSql, $isEffectiveWrite, $fname );
1225  $lastError = $this->lastError();
1226  $lastErrno = $this->lastErrno();
1227 
1228  $recoverableSR = false; // recoverable statement rollback?
1229  $recoverableCL = false; // recoverable connection loss?
1230 
1231  if ( $ret === false && $this->wasConnectionLoss() ) {
1232  # Check if no meaningful session state was lost
1233  $recoverableCL = $this->canRecoverFromDisconnect( $sql, $priorWritesPending );
1234  # Update session state tracking and try to restore the connection
1235  $reconnected = $this->replaceLostConnection( __METHOD__ );
1236  # Silently resend the query to the server if it is safe and possible
1237  if ( $recoverableCL && $reconnected ) {
1238  $ret = $this->attemptQuery( $sql, $commentedSql, $isEffectiveWrite, $fname );
1239  $lastError = $this->lastError();
1240  $lastErrno = $this->lastErrno();
1241 
1242  if ( $ret === false && $this->wasConnectionLoss() ) {
1243  # Query probably causes disconnects; reconnect and do not re-run it
1244  $this->replaceLostConnection( __METHOD__ );
1245  } else {
1246  $recoverableCL = false; // connection does not need recovering
1247  $recoverableSR = $this->wasKnownStatementRollbackError();
1248  }
1249  }
1250  } else {
1251  $recoverableSR = $this->wasKnownStatementRollbackError();
1252  }
1253 
1254  if ( $ret === false ) {
1255  if ( $priorTransaction ) {
1256  if ( $recoverableSR ) {
1257  # We're ignoring an error that caused just the current query to be aborted.
1258  # But log the cause so we can log a deprecation notice if a caller actually
1259  # does ignore it.
1260  $this->trxStatusIgnoredCause = [ $lastError, $lastErrno, $fname ];
1261  } elseif ( !$recoverableCL ) {
1262  # Either the query was aborted or all queries after BEGIN where aborted.
1263  # In the first case, the only options going forward are (a) ROLLBACK, or
1264  # (b) ROLLBACK TO SAVEPOINT (if one was set). If the later case, the only
1265  # option is ROLLBACK, since the snapshots would have been released.
1266  $this->trxStatus = self::STATUS_TRX_ERROR;
1267  $this->trxStatusCause =
1268  $this->getQueryExceptionAndLog( $lastError, $lastErrno, $sql, $fname );
1269  $ignoreErrors = false; // cannot recover
1270  $this->trxStatusIgnoredCause = null;
1271  }
1272  }
1273 
1274  $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $ignoreErrors );
1275  }
1276 
1277  return $this->resultObject( $ret );
1278  }
1279 
1290  private function attemptQuery( $sql, $commentedSql, $isEffectiveWrite, $fname ) {
1291  $this->beginIfImplied( $sql, $fname );
1292 
1293  # Keep track of whether the transaction has write queries pending
1294  if ( $isEffectiveWrite ) {
1295  $this->lastWriteTime = microtime( true );
1296  if ( $this->trxLevel && !$this->trxDoneWrites ) {
1297  $this->trxDoneWrites = true;
1298  $this->trxProfiler->transactionWritingIn(
1299  $this->server, $this->getDomainID(), $this->trxShortId );
1300  }
1301  }
1302 
1303  if ( $this->getFlag( self::DBO_DEBUG ) ) {
1304  $this->queryLogger->debug( "{$this->getDomainID()} {$commentedSql}" );
1305  }
1306 
1307  $isMaster = !is_null( $this->getLBInfo( 'master' ) );
1308  # generalizeSQL() will probably cut down the query to reasonable
1309  # logging size most of the time. The substr is really just a sanity check.
1310  if ( $isMaster ) {
1311  $queryProf = 'query-m: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
1312  } else {
1313  $queryProf = 'query: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
1314  }
1315 
1316  # Include query transaction state
1317  $queryProf .= $this->trxShortId ? " [TRX#{$this->trxShortId}]" : "";
1318 
1319  $startTime = microtime( true );
1320  $ps = $this->profiler ? ( $this->profiler )( $queryProf ) : null;
1321  $this->affectedRowCount = null;
1322  $ret = $this->doQuery( $commentedSql );
1323  $this->affectedRowCount = $this->affectedRows();
1324  unset( $ps ); // profile out (if set)
1325  $queryRuntime = max( microtime( true ) - $startTime, 0.0 );
1326 
1327  if ( $ret !== false ) {
1328  $this->lastPing = $startTime;
1329  if ( $isEffectiveWrite && $this->trxLevel ) {
1330  $this->updateTrxWriteQueryTime( $sql, $queryRuntime, $this->affectedRows() );
1331  $this->trxWriteCallers[] = $fname;
1332  }
1333  }
1334 
1335  if ( $sql === self::PING_QUERY ) {
1336  $this->rttEstimate = $queryRuntime;
1337  }
1338 
1339  $this->trxProfiler->recordQueryCompletion(
1340  $queryProf,
1341  $startTime,
1342  $isEffectiveWrite,
1343  $isEffectiveWrite ? $this->affectedRows() : $this->numRows( $ret )
1344  );
1345  $this->queryLogger->debug( $sql, [
1346  'method' => $fname,
1347  'master' => $isMaster,
1348  'runtime' => $queryRuntime,
1349  ] );
1350 
1351  return $ret;
1352  }
1353 
1360  private function beginIfImplied( $sql, $fname ) {
1361  if (
1362  !$this->trxLevel &&
1363  $this->getFlag( self::DBO_TRX ) &&
1364  $this->isTransactableQuery( $sql )
1365  ) {
1366  $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
1367  $this->trxAutomatic = true;
1368  }
1369  }
1370 
1383  private function updateTrxWriteQueryTime( $sql, $runtime, $affected ) {
1384  // Whether this is indicative of replica DB runtime (except for RBR or ws_repl)
1385  $indicativeOfReplicaRuntime = true;
1386  if ( $runtime > self::SLOW_WRITE_SEC ) {
1387  $verb = $this->getQueryVerb( $sql );
1388  // insert(), upsert(), replace() are fast unless bulky in size or blocked on locks
1389  if ( $verb === 'INSERT' ) {
1390  $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS;
1391  } elseif ( $verb === 'REPLACE' ) {
1392  $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS / 2;
1393  }
1394  }
1395 
1396  $this->trxWriteDuration += $runtime;
1397  $this->trxWriteQueryCount += 1;
1398  $this->trxWriteAffectedRows += $affected;
1399  if ( $indicativeOfReplicaRuntime ) {
1400  $this->trxWriteAdjDuration += $runtime;
1401  $this->trxWriteAdjQueryCount += 1;
1402  }
1403  }
1404 
1412  private function assertTransactionStatus( $sql, $fname ) {
1413  $verb = $this->getQueryVerb( $sql );
1414  if ( $verb === 'USE' ) {
1415  throw new DBUnexpectedError( $this, "Got USE query; use selectDomain() instead." );
1416  }
1417 
1418  if ( $verb === 'ROLLBACK' ) { // transaction/savepoint
1419  return;
1420  }
1421 
1422  if ( $this->trxStatus < self::STATUS_TRX_OK ) {
1423  throw new DBTransactionStateError(
1424  $this,
1425  "Cannot execute query from $fname while transaction status is ERROR.",
1426  [],
1427  $this->trxStatusCause
1428  );
1429  } elseif ( $this->trxStatus === self::STATUS_TRX_OK && $this->trxStatusIgnoredCause ) {
1430  list( $iLastError, $iLastErrno, $iFname ) = $this->trxStatusIgnoredCause;
1431  call_user_func( $this->deprecationLogger,
1432  "Caller from $fname ignored an error originally raised from $iFname: " .
1433  "[$iLastErrno] $iLastError"
1434  );
1435  $this->trxStatusIgnoredCause = null;
1436  }
1437  }
1438 
1439  public function assertNoOpenTransactions() {
1440  if ( $this->explicitTrxActive() ) {
1441  throw new DBTransactionError(
1442  $this,
1443  "Explicit transaction still active. A caller may have caught an error. "
1444  . "Open transactions: " . $this->flatAtomicSectionList()
1445  );
1446  }
1447  }
1448 
1458  private function canRecoverFromDisconnect( $sql, $priorWritesPending ) {
1459  # Transaction dropped; this can mean lost writes, or REPEATABLE-READ snapshots.
1460  # Dropped connections also mean that named locks are automatically released.
1461  # Only allow error suppression in autocommit mode or when the lost transaction
1462  # didn't matter anyway (aside from DBO_TRX snapshot loss).
1463  if ( $this->namedLocksHeld ) {
1464  return false; // possible critical section violation
1465  } elseif ( $this->sessionTempTables ) {
1466  return false; // tables might be queried latter
1467  } elseif ( $sql === 'COMMIT' ) {
1468  return !$priorWritesPending; // nothing written anyway? (T127428)
1469  } elseif ( $sql === 'ROLLBACK' ) {
1470  return true; // transaction lost...which is also what was requested :)
1471  } elseif ( $this->explicitTrxActive() ) {
1472  return false; // don't drop atomocity and explicit snapshots
1473  } elseif ( $priorWritesPending ) {
1474  return false; // prior writes lost from implicit transaction
1475  }
1476 
1477  return true;
1478  }
1479 
1483  private function handleSessionLossPreconnect() {
1484  // Clean up tracking of session-level things...
1485  // https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html
1486  // https://www.postgresql.org/docs/9.2/static/sql-createtable.html (ignoring ON COMMIT)
1487  $this->sessionTempTables = [];
1488  // https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
1489  // https://www.postgresql.org/docs/9.4/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1490  $this->namedLocksHeld = [];
1491  // Session loss implies transaction loss
1492  $this->trxLevel = 0;
1493  $this->trxAtomicCounter = 0;
1494  $this->trxIdleCallbacks = []; // T67263; transaction already lost
1495  $this->trxPreCommitCallbacks = []; // T67263; transaction already lost
1496  // @note: leave trxRecurringCallbacks in place
1497  if ( $this->trxDoneWrites ) {
1498  $this->trxProfiler->transactionWritingOut(
1499  $this->server,
1500  $this->getDomainID(),
1501  $this->trxShortId,
1502  $this->pendingWriteQueryDuration( self::ESTIMATE_TOTAL ),
1503  $this->trxWriteAffectedRows
1504  );
1505  }
1506  }
1507 
1511  private function handleSessionLossPostconnect() {
1512  try {
1513  // Handle callbacks in trxEndCallbacks, e.g. onTransactionResolution().
1514  // If callback suppression is set then the array will remain unhandled.
1515  $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
1516  } catch ( Exception $ex ) {
1517  // Already logged; move on...
1518  }
1519  try {
1520  // Handle callbacks in trxRecurringCallbacks, e.g. setTransactionListener()
1521  $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
1522  } catch ( Exception $ex ) {
1523  // Already logged; move on...
1524  }
1525  }
1526 
1537  protected function wasQueryTimeout( $error, $errno ) {
1538  return false;
1539  }
1540 
1552  public function reportQueryError( $error, $errno, $sql, $fname, $ignoreErrors = false ) {
1553  if ( $ignoreErrors ) {
1554  $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" );
1555  } else {
1556  $exception = $this->getQueryExceptionAndLog( $error, $errno, $sql, $fname );
1557 
1558  throw $exception;
1559  }
1560  }
1561 
1569  private function getQueryExceptionAndLog( $error, $errno, $sql, $fname ) {
1570  $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
1571  $this->queryLogger->error(
1572  "{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
1573  $this->getLogContext( [
1574  'method' => __METHOD__,
1575  'errno' => $errno,
1576  'error' => $error,
1577  'sql1line' => $sql1line,
1578  'fname' => $fname,
1579  'trace' => ( new RuntimeException() )->getTraceAsString()
1580  ] )
1581  );
1582  $this->queryLogger->debug( "SQL ERROR: " . $error . "\n" );
1583  $wasQueryTimeout = $this->wasQueryTimeout( $error, $errno );
1584  if ( $wasQueryTimeout ) {
1585  $e = new DBQueryTimeoutError( $this, $error, $errno, $sql, $fname );
1586  } else {
1587  $e = new DBQueryError( $this, $error, $errno, $sql, $fname );
1588  }
1589 
1590  return $e;
1591  }
1592 
1593  public function freeResult( $res ) {
1594  }
1595 
1596  public function selectField(
1597  $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1598  ) {
1599  if ( $var === '*' ) { // sanity
1600  throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
1601  }
1602 
1603  if ( !is_array( $options ) ) {
1604  $options = [ $options ];
1605  }
1606 
1607  $options['LIMIT'] = 1;
1608 
1609  $res = $this->select( $table, $var, $cond, $fname, $options, $join_conds );
1610  if ( $res === false || !$this->numRows( $res ) ) {
1611  return false;
1612  }
1613 
1614  $row = $this->fetchRow( $res );
1615 
1616  if ( $row !== false ) {
1617  return reset( $row );
1618  } else {
1619  return false;
1620  }
1621  }
1622 
1623  public function selectFieldValues(
1624  $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1625  ) {
1626  if ( $var === '*' ) { // sanity
1627  throw new DBUnexpectedError( $this, "Cannot use a * field" );
1628  } elseif ( !is_string( $var ) ) { // sanity
1629  throw new DBUnexpectedError( $this, "Cannot use an array of fields" );
1630  }
1631 
1632  if ( !is_array( $options ) ) {
1633  $options = [ $options ];
1634  }
1635 
1636  $res = $this->select( $table, [ 'value' => $var ], $cond, $fname, $options, $join_conds );
1637  if ( $res === false ) {
1638  return false;
1639  }
1640 
1641  $values = [];
1642  foreach ( $res as $row ) {
1643  $values[] = $row->value;
1644  }
1645 
1646  return $values;
1647  }
1648 
1658  protected function makeSelectOptions( $options ) {
1659  $preLimitTail = $postLimitTail = '';
1660  $startOpts = '';
1661 
1662  $noKeyOptions = [];
1663 
1664  foreach ( $options as $key => $option ) {
1665  if ( is_numeric( $key ) ) {
1666  $noKeyOptions[$option] = true;
1667  }
1668  }
1669 
1670  $preLimitTail .= $this->makeGroupByWithHaving( $options );
1671 
1672  $preLimitTail .= $this->makeOrderBy( $options );
1673 
1674  if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1675  $postLimitTail .= ' FOR UPDATE';
1676  }
1677 
1678  if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
1679  $postLimitTail .= ' LOCK IN SHARE MODE';
1680  }
1681 
1682  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1683  $startOpts .= 'DISTINCT';
1684  }
1685 
1686  # Various MySQL extensions
1687  if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
1688  $startOpts .= ' /*! STRAIGHT_JOIN */';
1689  }
1690 
1691  if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
1692  $startOpts .= ' HIGH_PRIORITY';
1693  }
1694 
1695  if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
1696  $startOpts .= ' SQL_BIG_RESULT';
1697  }
1698 
1699  if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
1700  $startOpts .= ' SQL_BUFFER_RESULT';
1701  }
1702 
1703  if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
1704  $startOpts .= ' SQL_SMALL_RESULT';
1705  }
1706 
1707  if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
1708  $startOpts .= ' SQL_CALC_FOUND_ROWS';
1709  }
1710 
1711  if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
1712  $startOpts .= ' SQL_CACHE';
1713  }
1714 
1715  if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
1716  $startOpts .= ' SQL_NO_CACHE';
1717  }
1718 
1719  if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
1720  $useIndex = $this->useIndexClause( $options['USE INDEX'] );
1721  } else {
1722  $useIndex = '';
1723  }
1724  if ( isset( $options['IGNORE INDEX'] ) && is_string( $options['IGNORE INDEX'] ) ) {
1725  $ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
1726  } else {
1727  $ignoreIndex = '';
1728  }
1729 
1730  return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1731  }
1732 
1741  protected function makeGroupByWithHaving( $options ) {
1742  $sql = '';
1743  if ( isset( $options['GROUP BY'] ) ) {
1744  $gb = is_array( $options['GROUP BY'] )
1745  ? implode( ',', $options['GROUP BY'] )
1746  : $options['GROUP BY'];
1747  $sql .= ' GROUP BY ' . $gb;
1748  }
1749  if ( isset( $options['HAVING'] ) ) {
1750  $having = is_array( $options['HAVING'] )
1751  ? $this->makeList( $options['HAVING'], self::LIST_AND )
1752  : $options['HAVING'];
1753  $sql .= ' HAVING ' . $having;
1754  }
1755 
1756  return $sql;
1757  }
1758 
1767  protected function makeOrderBy( $options ) {
1768  if ( isset( $options['ORDER BY'] ) ) {
1769  $ob = is_array( $options['ORDER BY'] )
1770  ? implode( ',', $options['ORDER BY'] )
1771  : $options['ORDER BY'];
1772 
1773  return ' ORDER BY ' . $ob;
1774  }
1775 
1776  return '';
1777  }
1778 
1779  public function select(
1780  $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1781  ) {
1782  $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
1783 
1784  return $this->query( $sql, $fname );
1785  }
1786 
1787  public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
1788  $options = [], $join_conds = []
1789  ) {
1790  if ( is_array( $vars ) ) {
1791  $fields = implode( ',', $this->fieldNamesWithAlias( $vars ) );
1792  } else {
1793  $fields = $vars;
1794  }
1795 
1796  $options = (array)$options;
1797  $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
1798  ? $options['USE INDEX']
1799  : [];
1800  $ignoreIndexes = (
1801  isset( $options['IGNORE INDEX'] ) &&
1802  is_array( $options['IGNORE INDEX'] )
1803  )
1804  ? $options['IGNORE INDEX']
1805  : [];
1806 
1807  if (
1810  ) {
1811  // Some DB types (postgres/oracle) disallow FOR UPDATE with aggregate
1812  // functions. Discourage use of such queries to encourage compatibility.
1813  call_user_func(
1814  $this->deprecationLogger,
1815  __METHOD__ . ": aggregation used with a locking SELECT ($fname)."
1816  );
1817  }
1818 
1819  if ( is_array( $table ) ) {
1820  $from = ' FROM ' .
1822  $table, $useIndexes, $ignoreIndexes, $join_conds );
1823  } elseif ( $table != '' ) {
1824  $from = ' FROM ' .
1826  [ $table ], $useIndexes, $ignoreIndexes, [] );
1827  } else {
1828  $from = '';
1829  }
1830 
1831  list( $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ) =
1832  $this->makeSelectOptions( $options );
1833 
1834  if ( is_array( $conds ) ) {
1835  $conds = $this->makeList( $conds, self::LIST_AND );
1836  }
1837 
1838  if ( $conds === null || $conds === false ) {
1839  $this->queryLogger->warning(
1840  __METHOD__
1841  . ' called from '
1842  . $fname
1843  . ' with incorrect parameters: $conds must be a string or an array'
1844  );
1845  $conds = '';
1846  }
1847 
1848  if ( $conds === '' || $conds === '*' ) {
1849  $sql = "SELECT $startOpts $fields $from $useIndex $ignoreIndex $preLimitTail";
1850  } elseif ( is_string( $conds ) ) {
1851  $sql = "SELECT $startOpts $fields $from $useIndex $ignoreIndex " .
1852  "WHERE $conds $preLimitTail";
1853  } else {
1854  throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
1855  }
1856 
1857  if ( isset( $options['LIMIT'] ) ) {
1858  $sql = $this->limitResult( $sql, $options['LIMIT'],
1859  $options['OFFSET'] ?? false );
1860  }
1861  $sql = "$sql $postLimitTail";
1862 
1863  if ( isset( $options['EXPLAIN'] ) ) {
1864  $sql = 'EXPLAIN ' . $sql;
1865  }
1866 
1867  return $sql;
1868  }
1869 
1870  public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
1871  $options = [], $join_conds = []
1872  ) {
1873  $options = (array)$options;
1874  $options['LIMIT'] = 1;
1875  $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
1876 
1877  if ( $res === false ) {
1878  return false;
1879  }
1880 
1881  if ( !$this->numRows( $res ) ) {
1882  return false;
1883  }
1884 
1885  $obj = $this->fetchObject( $res );
1886 
1887  return $obj;
1888  }
1889 
1890  public function estimateRowCount(
1891  $table, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1892  ) {
1893  $conds = $this->normalizeConditions( $conds, $fname );
1894  $column = $this->extractSingleFieldFromList( $var );
1895  if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
1896  $conds[] = "$column IS NOT NULL";
1897  }
1898 
1899  $res = $this->select(
1900  $table, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options, $join_conds
1901  );
1902  $row = $res ? $this->fetchRow( $res ) : [];
1903 
1904  return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
1905  }
1906 
1907  public function selectRowCount(
1908  $tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1909  ) {
1910  $conds = $this->normalizeConditions( $conds, $fname );
1911  $column = $this->extractSingleFieldFromList( $var );
1912  if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
1913  $conds[] = "$column IS NOT NULL";
1914  }
1915 
1916  $res = $this->select(
1917  [
1918  'tmp_count' => $this->buildSelectSubquery(
1919  $tables,
1920  '1',
1921  $conds,
1922  $fname,
1923  $options,
1924  $join_conds
1925  )
1926  ],
1927  [ 'rowcount' => 'COUNT(*)' ],
1928  [],
1929  $fname
1930  );
1931  $row = $res ? $this->fetchRow( $res ) : [];
1932 
1933  return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
1934  }
1935 
1941  $options = (array)$options;
1942  foreach ( [ 'FOR UPDATE', 'LOCK IN SHARE MODE' ] as $lock ) {
1943  if ( in_array( $lock, $options, true ) ) {
1944  return true;
1945  }
1946  }
1947 
1948  return false;
1949  }
1950 
1956  private function selectFieldsOrOptionsAggregate( $fields, $options ) {
1957  foreach ( (array)$options as $key => $value ) {
1958  if ( is_string( $key ) ) {
1959  if ( preg_match( '/^(?:GROUP BY|HAVING)$/i', $key ) ) {
1960  return true;
1961  }
1962  } elseif ( is_string( $value ) ) {
1963  if ( preg_match( '/^(?:DISTINCT|DISTINCTROW)$/i', $value ) ) {
1964  return true;
1965  }
1966  }
1967  }
1968 
1969  $regex = '/^(?:COUNT|MIN|MAX|SUM|GROUP_CONCAT|LISTAGG|ARRAY_AGG)\s*\\(/i';
1970  foreach ( (array)$fields as $field ) {
1971  if ( is_string( $field ) && preg_match( $regex, $field ) ) {
1972  return true;
1973  }
1974  }
1975 
1976  return false;
1977  }
1978 
1984  final protected function normalizeConditions( $conds, $fname ) {
1985  if ( $conds === null || $conds === false ) {
1986  $this->queryLogger->warning(
1987  __METHOD__
1988  . ' called from '
1989  . $fname
1990  . ' with incorrect parameters: $conds must be a string or an array'
1991  );
1992  $conds = '';
1993  }
1994 
1995  if ( !is_array( $conds ) ) {
1996  $conds = ( $conds === '' ) ? [] : [ $conds ];
1997  }
1998 
1999  return $conds;
2000  }
2001 
2007  final protected function extractSingleFieldFromList( $var ) {
2008  if ( is_array( $var ) ) {
2009  if ( !$var ) {
2010  $column = null;
2011  } elseif ( count( $var ) == 1 ) {
2012  $column = $var[0] ?? reset( $var );
2013  } else {
2014  throw new DBUnexpectedError( $this, __METHOD__ . ': got multiple columns.' );
2015  }
2016  } else {
2017  $column = $var;
2018  }
2019 
2020  return $column;
2021  }
2022 
2023  public function lockForUpdate(
2024  $table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
2025  ) {
2026  if ( !$this->trxLevel && !$this->getFlag( self::DBO_TRX ) ) {
2027  throw new DBUnexpectedError(
2028  $this,
2029  __METHOD__ . ': no transaction is active nor is DBO_TRX set'
2030  );
2031  }
2032 
2033  $options = (array)$options;
2034  $options[] = 'FOR UPDATE';
2035 
2036  return $this->selectRowCount( $table, '*', $conds, $fname, $options, $join_conds );
2037  }
2038 
2047  protected static function generalizeSQL( $sql ) {
2048  # This does the same as the regexp below would do, but in such a way
2049  # as to avoid crashing php on some large strings.
2050  # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
2051 
2052  $sql = str_replace( "\\\\", '', $sql );
2053  $sql = str_replace( "\\'", '', $sql );
2054  $sql = str_replace( "\\\"", '', $sql );
2055  $sql = preg_replace( "/'.*'/s", "'X'", $sql );
2056  $sql = preg_replace( '/".*"/s', "'X'", $sql );
2057 
2058  # All newlines, tabs, etc replaced by single space
2059  $sql = preg_replace( '/\s+/', ' ', $sql );
2060 
2061  # All numbers => N,
2062  # except the ones surrounded by characters, e.g. l10n
2063  $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
2064  $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
2065 
2066  return $sql;
2067  }
2068 
2069  public function fieldExists( $table, $field, $fname = __METHOD__ ) {
2070  $info = $this->fieldInfo( $table, $field );
2071 
2072  return (bool)$info;
2073  }
2074 
2075  public function indexExists( $table, $index, $fname = __METHOD__ ) {
2076  if ( !$this->tableExists( $table ) ) {
2077  return null;
2078  }
2079 
2080  $info = $this->indexInfo( $table, $index, $fname );
2081  if ( is_null( $info ) ) {
2082  return null;
2083  } else {
2084  return $info !== false;
2085  }
2086  }
2087 
2088  abstract public function tableExists( $table, $fname = __METHOD__ );
2089 
2090  public function indexUnique( $table, $index ) {
2091  $indexInfo = $this->indexInfo( $table, $index );
2092 
2093  if ( !$indexInfo ) {
2094  return null;
2095  }
2096 
2097  return !$indexInfo[0]->Non_unique;
2098  }
2099 
2106  protected function makeInsertOptions( $options ) {
2107  return implode( ' ', $options );
2108  }
2109 
2110  public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
2111  # No rows to insert, easy just return now
2112  if ( !count( $a ) ) {
2113  return true;
2114  }
2115 
2116  $table = $this->tableName( $table );
2117 
2118  if ( !is_array( $options ) ) {
2119  $options = [ $options ];
2120  }
2121 
2122  $options = $this->makeInsertOptions( $options );
2123 
2124  if ( isset( $a[0] ) && is_array( $a[0] ) ) {
2125  $multi = true;
2126  $keys = array_keys( $a[0] );
2127  } else {
2128  $multi = false;
2129  $keys = array_keys( $a );
2130  }
2131 
2132  $sql = 'INSERT ' . $options .
2133  " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
2134 
2135  if ( $multi ) {
2136  $first = true;
2137  foreach ( $a as $row ) {
2138  if ( $first ) {
2139  $first = false;
2140  } else {
2141  $sql .= ',';
2142  }
2143  $sql .= '(' . $this->makeList( $row ) . ')';
2144  }
2145  } else {
2146  $sql .= '(' . $this->makeList( $a ) . ')';
2147  }
2148 
2149  $this->query( $sql, $fname );
2150 
2151  return true;
2152  }
2153 
2160  protected function makeUpdateOptionsArray( $options ) {
2161  if ( !is_array( $options ) ) {
2162  $options = [ $options ];
2163  }
2164 
2165  $opts = [];
2166 
2167  if ( in_array( 'IGNORE', $options ) ) {
2168  $opts[] = 'IGNORE';
2169  }
2170 
2171  return $opts;
2172  }
2173 
2180  protected function makeUpdateOptions( $options ) {
2181  $opts = $this->makeUpdateOptionsArray( $options );
2182 
2183  return implode( ' ', $opts );
2184  }
2185 
2186  public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
2187  $table = $this->tableName( $table );
2188  $opts = $this->makeUpdateOptions( $options );
2189  $sql = "UPDATE $opts $table SET " . $this->makeList( $values, self::LIST_SET );
2190 
2191  if ( $conds !== [] && $conds !== '*' ) {
2192  $sql .= " WHERE " . $this->makeList( $conds, self::LIST_AND );
2193  }
2194 
2195  $this->query( $sql, $fname );
2196 
2197  return true;
2198  }
2199 
2200  public function makeList( $a, $mode = self::LIST_COMMA ) {
2201  if ( !is_array( $a ) ) {
2202  throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
2203  }
2204 
2205  $first = true;
2206  $list = '';
2207 
2208  foreach ( $a as $field => $value ) {
2209  if ( !$first ) {
2210  if ( $mode == self::LIST_AND ) {
2211  $list .= ' AND ';
2212  } elseif ( $mode == self::LIST_OR ) {
2213  $list .= ' OR ';
2214  } else {
2215  $list .= ',';
2216  }
2217  } else {
2218  $first = false;
2219  }
2220 
2221  if ( ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_numeric( $field ) ) {
2222  $list .= "($value)";
2223  } elseif ( $mode == self::LIST_SET && is_numeric( $field ) ) {
2224  $list .= "$value";
2225  } elseif (
2226  ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_array( $value )
2227  ) {
2228  // Remove null from array to be handled separately if found
2229  $includeNull = false;
2230  foreach ( array_keys( $value, null, true ) as $nullKey ) {
2231  $includeNull = true;
2232  unset( $value[$nullKey] );
2233  }
2234  if ( count( $value ) == 0 && !$includeNull ) {
2235  throw new InvalidArgumentException(
2236  __METHOD__ . ": empty input for field $field" );
2237  } elseif ( count( $value ) == 0 ) {
2238  // only check if $field is null
2239  $list .= "$field IS NULL";
2240  } else {
2241  // IN clause contains at least one valid element
2242  if ( $includeNull ) {
2243  // Group subconditions to ensure correct precedence
2244  $list .= '(';
2245  }
2246  if ( count( $value ) == 1 ) {
2247  // Special-case single values, as IN isn't terribly efficient
2248  // Don't necessarily assume the single key is 0; we don't
2249  // enforce linear numeric ordering on other arrays here.
2250  $value = array_values( $value )[0];
2251  $list .= $field . " = " . $this->addQuotes( $value );
2252  } else {
2253  $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
2254  }
2255  // if null present in array, append IS NULL
2256  if ( $includeNull ) {
2257  $list .= " OR $field IS NULL)";
2258  }
2259  }
2260  } elseif ( $value === null ) {
2261  if ( $mode == self::LIST_AND || $mode == self::LIST_OR ) {
2262  $list .= "$field IS ";
2263  } elseif ( $mode == self::LIST_SET ) {
2264  $list .= "$field = ";
2265  }
2266  $list .= 'NULL';
2267  } else {
2268  if (
2269  $mode == self::LIST_AND || $mode == self::LIST_OR || $mode == self::LIST_SET
2270  ) {
2271  $list .= "$field = ";
2272  }
2273  $list .= $mode == self::LIST_NAMES ? $value : $this->addQuotes( $value );
2274  }
2275  }
2276 
2277  return $list;
2278  }
2279 
2280  public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
2281  $conds = [];
2282 
2283  foreach ( $data as $base => $sub ) {
2284  if ( count( $sub ) ) {
2285  $conds[] = $this->makeList(
2286  [ $baseKey => $base, $subKey => array_keys( $sub ) ],
2287  self::LIST_AND );
2288  }
2289  }
2290 
2291  if ( $conds ) {
2292  return $this->makeList( $conds, self::LIST_OR );
2293  } else {
2294  // Nothing to search for...
2295  return false;
2296  }
2297  }
2298 
2299  public function aggregateValue( $valuedata, $valuename = 'value' ) {
2300  return $valuename;
2301  }
2302 
2303  public function bitNot( $field ) {
2304  return "(~$field)";
2305  }
2306 
2307  public function bitAnd( $fieldLeft, $fieldRight ) {
2308  return "($fieldLeft & $fieldRight)";
2309  }
2310 
2311  public function bitOr( $fieldLeft, $fieldRight ) {
2312  return "($fieldLeft | $fieldRight)";
2313  }
2314 
2315  public function buildConcat( $stringList ) {
2316  return 'CONCAT(' . implode( ',', $stringList ) . ')';
2317  }
2318 
2319  public function buildGroupConcatField(
2320  $delim, $table, $field, $conds = '', $join_conds = []
2321  ) {
2322  $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
2323 
2324  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
2325  }
2326 
2327  public function buildSubstring( $input, $startPosition, $length = null ) {
2328  $this->assertBuildSubstringParams( $startPosition, $length );
2329  $functionBody = "$input FROM $startPosition";
2330  if ( $length !== null ) {
2331  $functionBody .= " FOR $length";
2332  }
2333  return 'SUBSTRING(' . $functionBody . ')';
2334  }
2335 
2348  protected function assertBuildSubstringParams( $startPosition, $length ) {
2349  if ( !is_int( $startPosition ) || $startPosition <= 0 ) {
2350  throw new InvalidArgumentException(
2351  '$startPosition must be a positive integer'
2352  );
2353  }
2354  if ( !( is_int( $length ) && $length >= 0 || $length === null ) ) {
2355  throw new InvalidArgumentException(
2356  '$length must be null or an integer greater than or equal to 0'
2357  );
2358  }
2359  }
2360 
2361  public function buildStringCast( $field ) {
2362  // In theory this should work for any standards-compliant
2363  // SQL implementation, although it may not be the best way to do it.
2364  return "CAST( $field AS CHARACTER )";
2365  }
2366 
2367  public function buildIntegerCast( $field ) {
2368  return 'CAST( ' . $field . ' AS INTEGER )';
2369  }
2370 
2371  public function buildSelectSubquery(
2372  $table, $vars, $conds = '', $fname = __METHOD__,
2373  $options = [], $join_conds = []
2374  ) {
2375  return new Subquery(
2376  $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds )
2377  );
2378  }
2379 
2380  public function databasesAreIndependent() {
2381  return false;
2382  }
2383 
2384  final public function selectDB( $db ) {
2385  $this->selectDomain( new DatabaseDomain(
2386  $db,
2387  $this->currentDomain->getSchema(),
2388  $this->currentDomain->getTablePrefix()
2389  ) );
2390 
2391  return true;
2392  }
2393 
2394  final public function selectDomain( $domain ) {
2395  $this->doSelectDomain( DatabaseDomain::newFromId( $domain ) );
2396  }
2397 
2398  protected function doSelectDomain( DatabaseDomain $domain ) {
2399  $this->currentDomain = $domain;
2400  }
2401 
2402  public function getDBname() {
2403  return $this->currentDomain->getDatabase();
2404  }
2405 
2406  public function getServer() {
2407  return $this->server;
2408  }
2409 
2410  public function tableName( $name, $format = 'quoted' ) {
2411  if ( $name instanceof Subquery ) {
2412  throw new DBUnexpectedError(
2413  $this,
2414  __METHOD__ . ': got Subquery instance when expecting a string.'
2415  );
2416  }
2417 
2418  # Skip the entire process when we have a string quoted on both ends.
2419  # Note that we check the end so that we will still quote any use of
2420  # use of `database`.table. But won't break things if someone wants
2421  # to query a database table with a dot in the name.
2422  if ( $this->isQuotedIdentifier( $name ) ) {
2423  return $name;
2424  }
2425 
2426  # Lets test for any bits of text that should never show up in a table
2427  # name. Basically anything like JOIN or ON which are actually part of
2428  # SQL queries, but may end up inside of the table value to combine
2429  # sql. Such as how the API is doing.
2430  # Note that we use a whitespace test rather than a \b test to avoid
2431  # any remote case where a word like on may be inside of a table name
2432  # surrounded by symbols which may be considered word breaks.
2433  if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
2434  $this->queryLogger->warning(
2435  __METHOD__ . ": use of subqueries is not supported this way.",
2436  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
2437  );
2438 
2439  return $name;
2440  }
2441 
2442  # Split database and table into proper variables.
2443  list( $database, $schema, $prefix, $table ) = $this->qualifiedTableComponents( $name );
2444 
2445  # Quote $table and apply the prefix if not quoted.
2446  # $tableName might be empty if this is called from Database::replaceVars()
2447  $tableName = "{$prefix}{$table}";
2448  if ( $format === 'quoted'
2449  && !$this->isQuotedIdentifier( $tableName )
2450  && $tableName !== ''
2451  ) {
2452  $tableName = $this->addIdentifierQuotes( $tableName );
2453  }
2454 
2455  # Quote $schema and $database and merge them with the table name if needed
2456  $tableName = $this->prependDatabaseOrSchema( $schema, $tableName, $format );
2457  $tableName = $this->prependDatabaseOrSchema( $database, $tableName, $format );
2458 
2459  return $tableName;
2460  }
2461 
2468  protected function qualifiedTableComponents( $name ) {
2469  # We reverse the explode so that database.table and table both output the correct table.
2470  $dbDetails = explode( '.', $name, 3 );
2471  if ( count( $dbDetails ) == 3 ) {
2472  list( $database, $schema, $table ) = $dbDetails;
2473  # We don't want any prefix added in this case
2474  $prefix = '';
2475  } elseif ( count( $dbDetails ) == 2 ) {
2476  list( $database, $table ) = $dbDetails;
2477  # We don't want any prefix added in this case
2478  $prefix = '';
2479  # In dbs that support it, $database may actually be the schema
2480  # but that doesn't affect any of the functionality here
2481  $schema = '';
2482  } else {
2483  list( $table ) = $dbDetails;
2484  if ( isset( $this->tableAliases[$table] ) ) {
2485  $database = $this->tableAliases[$table]['dbname'];
2486  $schema = is_string( $this->tableAliases[$table]['schema'] )
2487  ? $this->tableAliases[$table]['schema']
2488  : $this->relationSchemaQualifier();
2489  $prefix = is_string( $this->tableAliases[$table]['prefix'] )
2490  ? $this->tableAliases[$table]['prefix']
2491  : $this->tablePrefix();
2492  } else {
2493  $database = '';
2494  $schema = $this->relationSchemaQualifier(); # Default schema
2495  $prefix = $this->tablePrefix(); # Default prefix
2496  }
2497  }
2498 
2499  return [ $database, $schema, $prefix, $table ];
2500  }
2501 
2508  private function prependDatabaseOrSchema( $namespace, $relation, $format ) {
2509  if ( strlen( $namespace ) ) {
2510  if ( $format === 'quoted' && !$this->isQuotedIdentifier( $namespace ) ) {
2511  $namespace = $this->addIdentifierQuotes( $namespace );
2512  }
2513  $relation = $namespace . '.' . $relation;
2514  }
2515 
2516  return $relation;
2517  }
2518 
2519  public function tableNames() {
2520  $inArray = func_get_args();
2521  $retVal = [];
2522 
2523  foreach ( $inArray as $name ) {
2524  $retVal[$name] = $this->tableName( $name );
2525  }
2526 
2527  return $retVal;
2528  }
2529 
2530  public function tableNamesN() {
2531  $inArray = func_get_args();
2532  $retVal = [];
2533 
2534  foreach ( $inArray as $name ) {
2535  $retVal[] = $this->tableName( $name );
2536  }
2537 
2538  return $retVal;
2539  }
2540 
2552  protected function tableNameWithAlias( $table, $alias = false ) {
2553  if ( is_string( $table ) ) {
2554  $quotedTable = $this->tableName( $table );
2555  } elseif ( $table instanceof Subquery ) {
2556  $quotedTable = (string)$table;
2557  } else {
2558  throw new InvalidArgumentException( "Table must be a string or Subquery." );
2559  }
2560 
2561  if ( $alias === false || $alias === $table ) {
2562  if ( $table instanceof Subquery ) {
2563  throw new InvalidArgumentException( "Subquery table missing alias." );
2564  }
2565 
2566  return $quotedTable;
2567  } else {
2568  return $quotedTable . ' ' . $this->addIdentifierQuotes( $alias );
2569  }
2570  }
2571 
2578  protected function tableNamesWithAlias( $tables ) {
2579  $retval = [];
2580  foreach ( $tables as $alias => $table ) {
2581  if ( is_numeric( $alias ) ) {
2582  $alias = $table;
2583  }
2584  $retval[] = $this->tableNameWithAlias( $table, $alias );
2585  }
2586 
2587  return $retval;
2588  }
2589 
2598  protected function fieldNameWithAlias( $name, $alias = false ) {
2599  if ( !$alias || (string)$alias === (string)$name ) {
2600  return $name;
2601  } else {
2602  return $name . ' AS ' . $this->addIdentifierQuotes( $alias ); // PostgreSQL needs AS
2603  }
2604  }
2605 
2612  protected function fieldNamesWithAlias( $fields ) {
2613  $retval = [];
2614  foreach ( $fields as $alias => $field ) {
2615  if ( is_numeric( $alias ) ) {
2616  $alias = $field;
2617  }
2618  $retval[] = $this->fieldNameWithAlias( $field, $alias );
2619  }
2620 
2621  return $retval;
2622  }
2623 
2635  $tables, $use_index = [], $ignore_index = [], $join_conds = []
2636  ) {
2637  $ret = [];
2638  $retJOIN = [];
2639  $use_index = (array)$use_index;
2640  $ignore_index = (array)$ignore_index;
2641  $join_conds = (array)$join_conds;
2642 
2643  foreach ( $tables as $alias => $table ) {
2644  if ( !is_string( $alias ) ) {
2645  // No alias? Set it equal to the table name
2646  $alias = $table;
2647  }
2648 
2649  if ( is_array( $table ) ) {
2650  // A parenthesized group
2651  if ( count( $table ) > 1 ) {
2652  $joinedTable = '(' .
2654  $table, $use_index, $ignore_index, $join_conds ) . ')';
2655  } else {
2656  // Degenerate case
2657  $innerTable = reset( $table );
2658  $innerAlias = key( $table );
2659  $joinedTable = $this->tableNameWithAlias(
2660  $innerTable,
2661  is_string( $innerAlias ) ? $innerAlias : $innerTable
2662  );
2663  }
2664  } else {
2665  $joinedTable = $this->tableNameWithAlias( $table, $alias );
2666  }
2667 
2668  // Is there a JOIN clause for this table?
2669  if ( isset( $join_conds[$alias] ) ) {
2670  list( $joinType, $conds ) = $join_conds[$alias];
2671  $tableClause = $joinType;
2672  $tableClause .= ' ' . $joinedTable;
2673  if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
2674  $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
2675  if ( $use != '' ) {
2676  $tableClause .= ' ' . $use;
2677  }
2678  }
2679  if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX?
2680  $ignore = $this->ignoreIndexClause(
2681  implode( ',', (array)$ignore_index[$alias] ) );
2682  if ( $ignore != '' ) {
2683  $tableClause .= ' ' . $ignore;
2684  }
2685  }
2686  $on = $this->makeList( (array)$conds, self::LIST_AND );
2687  if ( $on != '' ) {
2688  $tableClause .= ' ON (' . $on . ')';
2689  }
2690 
2691  $retJOIN[] = $tableClause;
2692  } elseif ( isset( $use_index[$alias] ) ) {
2693  // Is there an INDEX clause for this table?
2694  $tableClause = $joinedTable;
2695  $tableClause .= ' ' . $this->useIndexClause(
2696  implode( ',', (array)$use_index[$alias] )
2697  );
2698 
2699  $ret[] = $tableClause;
2700  } elseif ( isset( $ignore_index[$alias] ) ) {
2701  // Is there an INDEX clause for this table?
2702  $tableClause = $joinedTable;
2703  $tableClause .= ' ' . $this->ignoreIndexClause(
2704  implode( ',', (array)$ignore_index[$alias] )
2705  );
2706 
2707  $ret[] = $tableClause;
2708  } else {
2709  $tableClause = $joinedTable;
2710 
2711  $ret[] = $tableClause;
2712  }
2713  }
2714 
2715  // We can't separate explicit JOIN clauses with ',', use ' ' for those
2716  $implicitJoins = $ret ? implode( ',', $ret ) : "";
2717  $explicitJoins = $retJOIN ? implode( ' ', $retJOIN ) : "";
2718 
2719  // Compile our final table clause
2720  return implode( ' ', [ $implicitJoins, $explicitJoins ] );
2721  }
2722 
2729  protected function indexName( $index ) {
2730  return $this->indexAliases[$index] ?? $index;
2731  }
2732 
2733  public function addQuotes( $s ) {
2734  if ( $s instanceof Blob ) {
2735  $s = $s->fetch();
2736  }
2737  if ( $s === null ) {
2738  return 'NULL';
2739  } elseif ( is_bool( $s ) ) {
2740  return (int)$s;
2741  } else {
2742  # This will also quote numeric values. This should be harmless,
2743  # and protects against weird problems that occur when they really
2744  # _are_ strings such as article titles and string->number->string
2745  # conversion is not 1:1.
2746  return "'" . $this->strencode( $s ) . "'";
2747  }
2748  }
2749 
2750  public function addIdentifierQuotes( $s ) {
2751  return '"' . str_replace( '"', '""', $s ) . '"';
2752  }
2753 
2763  public function isQuotedIdentifier( $name ) {
2764  return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
2765  }
2766 
2772  protected function escapeLikeInternal( $s, $escapeChar = '`' ) {
2773  return str_replace( [ $escapeChar, '%', '_' ],
2774  [ "{$escapeChar}{$escapeChar}", "{$escapeChar}%", "{$escapeChar}_" ],
2775  $s );
2776  }
2777 
2778  public function buildLike() {
2779  $params = func_get_args();
2780 
2781  if ( count( $params ) > 0 && is_array( $params[0] ) ) {
2782  $params = $params[0];
2783  }
2784 
2785  $s = '';
2786 
2787  // We use ` instead of \ as the default LIKE escape character, since addQuotes()
2788  // may escape backslashes, creating problems of double escaping. The `
2789  // character has good cross-DBMS compatibility, avoiding special operators
2790  // in MS SQL like ^ and %
2791  $escapeChar = '`';
2792 
2793  foreach ( $params as $value ) {
2794  if ( $value instanceof LikeMatch ) {
2795  $s .= $value->toString();
2796  } else {
2797  $s .= $this->escapeLikeInternal( $value, $escapeChar );
2798  }
2799  }
2800 
2801  return ' LIKE ' .
2802  $this->addQuotes( $s ) . ' ESCAPE ' . $this->addQuotes( $escapeChar ) . ' ';
2803  }
2804 
2805  public function anyChar() {
2806  return new LikeMatch( '_' );
2807  }
2808 
2809  public function anyString() {
2810  return new LikeMatch( '%' );
2811  }
2812 
2813  public function nextSequenceValue( $seqName ) {
2814  return null;
2815  }
2816 
2827  public function useIndexClause( $index ) {
2828  return '';
2829  }
2830 
2841  public function ignoreIndexClause( $index ) {
2842  return '';
2843  }
2844 
2845  public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
2846  if ( count( $rows ) == 0 ) {
2847  return;
2848  }
2849 
2850  $uniqueIndexes = (array)$uniqueIndexes;
2851  // Single row case
2852  if ( !is_array( reset( $rows ) ) ) {
2853  $rows = [ $rows ];
2854  }
2855 
2856  try {
2857  $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
2858  $affectedRowCount = 0;
2859  foreach ( $rows as $row ) {
2860  // Delete rows which collide with this one
2861  $indexWhereClauses = [];
2862  foreach ( $uniqueIndexes as $index ) {
2863  $indexColumns = (array)$index;
2864  $indexRowValues = array_intersect_key( $row, array_flip( $indexColumns ) );
2865  if ( count( $indexRowValues ) != count( $indexColumns ) ) {
2866  throw new DBUnexpectedError(
2867  $this,
2868  'New record does not provide all values for unique key (' .
2869  implode( ', ', $indexColumns ) . ')'
2870  );
2871  } elseif ( in_array( null, $indexRowValues, true ) ) {
2872  throw new DBUnexpectedError(
2873  $this,
2874  'New record has a null value for unique key (' .
2875  implode( ', ', $indexColumns ) . ')'
2876  );
2877  }
2878  $indexWhereClauses[] = $this->makeList( $indexRowValues, LIST_AND );
2879  }
2880 
2881  if ( $indexWhereClauses ) {
2882  $this->delete( $table, $this->makeList( $indexWhereClauses, LIST_OR ), $fname );
2883  $affectedRowCount += $this->affectedRows();
2884  }
2885 
2886  // Now insert the row
2887  $this->insert( $table, $row, $fname );
2888  $affectedRowCount += $this->affectedRows();
2889  }
2890  $this->endAtomic( $fname );
2891  $this->affectedRowCount = $affectedRowCount;
2892  } catch ( Exception $e ) {
2893  $this->cancelAtomic( $fname );
2894  throw $e;
2895  }
2896  }
2897 
2906  protected function nativeReplace( $table, $rows, $fname ) {
2907  $table = $this->tableName( $table );
2908 
2909  # Single row case
2910  if ( !is_array( reset( $rows ) ) ) {
2911  $rows = [ $rows ];
2912  }
2913 
2914  $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
2915  $first = true;
2916 
2917  foreach ( $rows as $row ) {
2918  if ( $first ) {
2919  $first = false;
2920  } else {
2921  $sql .= ',';
2922  }
2923 
2924  $sql .= '(' . $this->makeList( $row ) . ')';
2925  }
2926 
2927  $this->query( $sql, $fname );
2928  }
2929 
2930  public function upsert( $table, array $rows, $uniqueIndexes, array $set,
2931  $fname = __METHOD__
2932  ) {
2933  if ( $rows === [] ) {
2934  return true; // nothing to do
2935  }
2936 
2937  $uniqueIndexes = (array)$uniqueIndexes;
2938  if ( !is_array( reset( $rows ) ) ) {
2939  $rows = [ $rows ];
2940  }
2941 
2942  if ( count( $uniqueIndexes ) ) {
2943  $clauses = []; // list WHERE clauses that each identify a single row
2944  foreach ( $rows as $row ) {
2945  foreach ( $uniqueIndexes as $index ) {
2946  $index = is_array( $index ) ? $index : [ $index ]; // columns
2947  $rowKey = []; // unique key to this row
2948  foreach ( $index as $column ) {
2949  $rowKey[$column] = $row[$column];
2950  }
2951  $clauses[] = $this->makeList( $rowKey, self::LIST_AND );
2952  }
2953  }
2954  $where = [ $this->makeList( $clauses, self::LIST_OR ) ];
2955  } else {
2956  $where = false;
2957  }
2958 
2959  $affectedRowCount = 0;
2960  try {
2961  $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
2962  # Update any existing conflicting row(s)
2963  if ( $where !== false ) {
2964  $this->update( $table, $set, $where, $fname );
2965  $affectedRowCount += $this->affectedRows();
2966  }
2967  # Now insert any non-conflicting row(s)
2968  $this->insert( $table, $rows, $fname, [ 'IGNORE' ] );
2969  $affectedRowCount += $this->affectedRows();
2970  $this->endAtomic( $fname );
2971  $this->affectedRowCount = $affectedRowCount;
2972  } catch ( Exception $e ) {
2973  $this->cancelAtomic( $fname );
2974  throw $e;
2975  }
2976 
2977  return true;
2978  }
2979 
2980  public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
2981  $fname = __METHOD__
2982  ) {
2983  if ( !$conds ) {
2984  throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
2985  }
2986 
2987  $delTable = $this->tableName( $delTable );
2988  $joinTable = $this->tableName( $joinTable );
2989  $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
2990  if ( $conds != '*' ) {
2991  $sql .= 'WHERE ' . $this->makeList( $conds, self::LIST_AND );
2992  }
2993  $sql .= ')';
2994 
2995  $this->query( $sql, $fname );
2996  }
2997 
2998  public function textFieldSize( $table, $field ) {
2999  $table = $this->tableName( $table );
3000  $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
3001  $res = $this->query( $sql, __METHOD__ );
3002  $row = $this->fetchObject( $res );
3003 
3004  $m = [];
3005 
3006  if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
3007  $size = $m[1];
3008  } else {
3009  $size = -1;
3010  }
3011 
3012  return $size;
3013  }
3014 
3015  public function delete( $table, $conds, $fname = __METHOD__ ) {
3016  if ( !$conds ) {
3017  throw new DBUnexpectedError( $this, __METHOD__ . ' called with no conditions' );
3018  }
3019 
3020  $table = $this->tableName( $table );
3021  $sql = "DELETE FROM $table";
3022 
3023  if ( $conds != '*' ) {
3024  if ( is_array( $conds ) ) {
3025  $conds = $this->makeList( $conds, self::LIST_AND );
3026  }
3027  $sql .= ' WHERE ' . $conds;
3028  }
3029 
3030  $this->query( $sql, $fname );
3031 
3032  return true;
3033  }
3034 
3035  final public function insertSelect(
3036  $destTable, $srcTable, $varMap, $conds,
3037  $fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
3038  ) {
3039  static $hints = [ 'NO_AUTO_COLUMNS' ];
3040 
3041  $insertOptions = (array)$insertOptions;
3042  $selectOptions = (array)$selectOptions;
3043 
3044  if ( $this->cliMode && $this->isInsertSelectSafe( $insertOptions, $selectOptions ) ) {
3045  // For massive migrations with downtime, we don't want to select everything
3046  // into memory and OOM, so do all this native on the server side if possible.
3047  $this->nativeInsertSelect(
3048  $destTable,
3049  $srcTable,
3050  $varMap,
3051  $conds,
3052  $fname,
3053  array_diff( $insertOptions, $hints ),
3054  $selectOptions,
3055  $selectJoinConds
3056  );
3057  } else {
3058  $this->nonNativeInsertSelect(
3059  $destTable,
3060  $srcTable,
3061  $varMap,
3062  $conds,
3063  $fname,
3064  array_diff( $insertOptions, $hints ),
3065  $selectOptions,
3066  $selectJoinConds
3067  );
3068  }
3069 
3070  return true;
3071  }
3072 
3079  protected function isInsertSelectSafe( array $insertOptions, array $selectOptions ) {
3080  return true;
3081  }
3082 
3097  protected function nonNativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
3098  $fname = __METHOD__,
3099  $insertOptions = [], $selectOptions = [], $selectJoinConds = []
3100  ) {
3101  // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
3102  // on only the master (without needing row-based-replication). It also makes it easy to
3103  // know how big the INSERT is going to be.
3104  $fields = [];
3105  foreach ( $varMap as $dstColumn => $sourceColumnOrSql ) {
3106  $fields[] = $this->fieldNameWithAlias( $sourceColumnOrSql, $dstColumn );
3107  }
3108  $selectOptions[] = 'FOR UPDATE';
3109  $res = $this->select(
3110  $srcTable, implode( ',', $fields ), $conds, $fname, $selectOptions, $selectJoinConds
3111  );
3112  if ( !$res ) {
3113  return;
3114  }
3115 
3116  try {
3117  $affectedRowCount = 0;
3118  $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
3119  $rows = [];
3120  $ok = true;
3121  foreach ( $res as $row ) {
3122  $rows[] = (array)$row;
3123 
3124  // Avoid inserts that are too huge
3126  $ok = $this->insert( $destTable, $rows, $fname, $insertOptions );
3127  if ( !$ok ) {
3128  break;
3129  }
3130  $affectedRowCount += $this->affectedRows();
3131  $rows = [];
3132  }
3133  }
3134  if ( $rows && $ok ) {
3135  $ok = $this->insert( $destTable, $rows, $fname, $insertOptions );
3136  if ( $ok ) {
3137  $affectedRowCount += $this->affectedRows();
3138  }
3139  }
3140  if ( $ok ) {
3141  $this->endAtomic( $fname );
3142  $this->affectedRowCount = $affectedRowCount;
3143  } else {
3144  $this->cancelAtomic( $fname );
3145  }
3146  } catch ( Exception $e ) {
3147  $this->cancelAtomic( $fname );
3148  throw $e;
3149  }
3150  }
3151 
3166  protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
3167  $fname = __METHOD__,
3168  $insertOptions = [], $selectOptions = [], $selectJoinConds = []
3169  ) {
3170  $destTable = $this->tableName( $destTable );
3171 
3172  if ( !is_array( $insertOptions ) ) {
3173  $insertOptions = [ $insertOptions ];
3174  }
3175 
3176  $insertOptions = $this->makeInsertOptions( $insertOptions );
3177 
3178  $selectSql = $this->selectSQLText(
3179  $srcTable,
3180  array_values( $varMap ),
3181  $conds,
3182  $fname,
3183  $selectOptions,
3184  $selectJoinConds
3185  );
3186 
3187  $sql = "INSERT $insertOptions" .
3188  " INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' .
3189  $selectSql;
3190 
3191  $this->query( $sql, $fname );
3192  }
3193 
3213  public function limitResult( $sql, $limit, $offset = false ) {
3214  if ( !is_numeric( $limit ) ) {
3215  throw new DBUnexpectedError( $this,
3216  "Invalid non-numeric limit passed to limitResult()\n" );
3217  }
3218 
3219  return "$sql LIMIT "
3220  . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
3221  . "{$limit} ";
3222  }
3223 
3224  public function unionSupportsOrderAndLimit() {
3225  return true; // True for almost every DB supported
3226  }
3227 
3228  public function unionQueries( $sqls, $all ) {
3229  $glue = $all ? ') UNION ALL (' : ') UNION (';
3230 
3231  return '(' . implode( $glue, $sqls ) . ')';
3232  }
3233 
3235  $table, $vars, array $permute_conds, $extra_conds = '', $fname = __METHOD__,
3236  $options = [], $join_conds = []
3237  ) {
3238  // First, build the Cartesian product of $permute_conds
3239  $conds = [ [] ];
3240  foreach ( $permute_conds as $field => $values ) {
3241  if ( !$values ) {
3242  // Skip empty $values
3243  continue;
3244  }
3245  $values = array_unique( $values ); // For sanity
3246  $newConds = [];
3247  foreach ( $conds as $cond ) {
3248  foreach ( $values as $value ) {
3249  $cond[$field] = $value;
3250  $newConds[] = $cond; // Arrays are by-value, not by-reference, so this works
3251  }
3252  }
3253  $conds = $newConds;
3254  }
3255 
3256  $extra_conds = $extra_conds === '' ? [] : (array)$extra_conds;
3257 
3258  // If there's just one condition and no subordering, hand off to
3259  // selectSQLText directly.
3260  if ( count( $conds ) === 1 &&
3261  ( !isset( $options['INNER ORDER BY'] ) || !$this->unionSupportsOrderAndLimit() )
3262  ) {
3263  return $this->selectSQLText(
3264  $table, $vars, $conds[0] + $extra_conds, $fname, $options, $join_conds
3265  );
3266  }
3267 
3268  // Otherwise, we need to pull out the order and limit to apply after
3269  // the union. Then build the SQL queries for each set of conditions in
3270  // $conds. Then union them together (using UNION ALL, because the
3271  // product *should* already be distinct).
3272  $orderBy = $this->makeOrderBy( $options );
3273  $limit = $options['LIMIT'] ?? null;
3274  $offset = $options['OFFSET'] ?? false;
3275  $all = empty( $options['NOTALL'] ) && !in_array( 'NOTALL', $options );
3276  if ( !$this->unionSupportsOrderAndLimit() ) {
3277  unset( $options['ORDER BY'], $options['LIMIT'], $options['OFFSET'] );
3278  } else {
3279  if ( array_key_exists( 'INNER ORDER BY', $options ) ) {
3280  $options['ORDER BY'] = $options['INNER ORDER BY'];
3281  }
3282  if ( $limit !== null && is_numeric( $offset ) && $offset != 0 ) {
3283  // We need to increase the limit by the offset rather than
3284  // using the offset directly, otherwise it'll skip incorrectly
3285  // in the subqueries.
3286  $options['LIMIT'] = $limit + $offset;
3287  unset( $options['OFFSET'] );
3288  }
3289  }
3290 
3291  $sqls = [];
3292  foreach ( $conds as $cond ) {
3293  $sqls[] = $this->selectSQLText(
3294  $table, $vars, $cond + $extra_conds, $fname, $options, $join_conds
3295  );
3296  }
3297  $sql = $this->unionQueries( $sqls, $all ) . $orderBy;
3298  if ( $limit !== null ) {
3299  $sql = $this->limitResult( $sql, $limit, $offset );
3300  }
3301 
3302  return $sql;
3303  }
3304 
3305  public function conditional( $cond, $trueVal, $falseVal ) {
3306  if ( is_array( $cond ) ) {
3307  $cond = $this->makeList( $cond, self::LIST_AND );
3308  }
3309 
3310  return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
3311  }
3312 
3313  public function strreplace( $orig, $old, $new ) {
3314  return "REPLACE({$orig}, {$old}, {$new})";
3315  }
3316 
3317  public function getServerUptime() {
3318  return 0;
3319  }
3320 
3321  public function wasDeadlock() {
3322  return false;
3323  }
3324 
3325  public function wasLockTimeout() {
3326  return false;
3327  }
3328 
3329  public function wasConnectionLoss() {
3330  return $this->wasConnectionError( $this->lastErrno() );
3331  }
3332 
3333  public function wasReadOnlyError() {
3334  return false;
3335  }
3336 
3337  public function wasErrorReissuable() {
3338  return (
3339  $this->wasDeadlock() ||
3340  $this->wasLockTimeout() ||
3341  $this->wasConnectionLoss()
3342  );
3343  }
3344 
3351  public function wasConnectionError( $errno ) {
3352  return false;
3353  }
3354 
3361  protected function wasKnownStatementRollbackError() {
3362  return false; // don't know; it could have caused a transaction rollback
3363  }
3364 
3365  public function deadlockLoop() {
3366  $args = func_get_args();
3367  $function = array_shift( $args );
3368  $tries = self::DEADLOCK_TRIES;
3369 
3370  $this->begin( __METHOD__ );
3371 
3372  $retVal = null;
3374  $e = null;
3375  do {
3376  try {
3377  $retVal = $function( ...$args );
3378  break;
3379  } catch ( DBQueryError $e ) {
3380  if ( $this->wasDeadlock() ) {
3381  // Retry after a randomized delay
3382  usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
3383  } else {
3384  // Throw the error back up
3385  throw $e;
3386  }
3387  }
3388  } while ( --$tries > 0 );
3389 
3390  if ( $tries <= 0 ) {
3391  // Too many deadlocks; give up
3392  $this->rollback( __METHOD__ );
3393  throw $e;
3394  } else {
3395  $this->commit( __METHOD__ );
3396 
3397  return $retVal;
3398  }
3399  }
3400 
3401  public function masterPosWait( DBMasterPos $pos, $timeout ) {
3402  # Real waits are implemented in the subclass.
3403  return 0;
3404  }
3405 
3406  public function getReplicaPos() {
3407  # Stub
3408  return false;
3409  }
3410 
3411  public function getMasterPos() {
3412  # Stub
3413  return false;
3414  }
3415 
3416  public function serverIsReadOnly() {
3417  return false;
3418  }
3419 
3420  final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
3421  if ( !$this->trxLevel ) {
3422  throw new DBUnexpectedError( $this, "No transaction is active." );
3423  }
3424  $this->trxEndCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
3425  }
3426 
3427  final public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
3428  if ( !$this->trxLevel && $this->getTransactionRoundId() ) {
3429  // Start an implicit transaction similar to how query() does
3430  $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
3431  $this->trxAutomatic = true;
3432  }
3433 
3434  $this->trxIdleCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
3435  if ( !$this->trxLevel ) {
3436  $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
3437  }
3438  }
3439 
3440  final public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
3441  $this->onTransactionCommitOrIdle( $callback, $fname );
3442  }
3443 
3444  final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
3445  if ( !$this->trxLevel && $this->getTransactionRoundId() ) {
3446  // Start an implicit transaction similar to how query() does
3447  $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
3448  $this->trxAutomatic = true;
3449  }
3450 
3451  if ( $this->trxLevel ) {
3452  $this->trxPreCommitCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
3453  } else {
3454  // No transaction is active nor will start implicitly, so make one for this callback
3455  $this->startAtomic( __METHOD__, self::ATOMIC_CANCELABLE );
3456  try {
3457  $callback( $this );
3458  $this->endAtomic( __METHOD__ );
3459  } catch ( Exception $e ) {
3460  $this->cancelAtomic( __METHOD__ );
3461  throw $e;
3462  }
3463  }
3464  }
3465 
3469  private function currentAtomicSectionId() {
3470  if ( $this->trxLevel && $this->trxAtomicLevels ) {
3471  $levelInfo = end( $this->trxAtomicLevels );
3472 
3473  return $levelInfo[1];
3474  }
3475 
3476  return null;
3477  }
3478 
3485  ) {
3486  foreach ( $this->trxPreCommitCallbacks as $key => $info ) {
3487  if ( $info[2] === $old ) {
3488  $this->trxPreCommitCallbacks[$key][2] = $new;
3489  }
3490  }
3491  foreach ( $this->trxIdleCallbacks as $key => $info ) {
3492  if ( $info[2] === $old ) {
3493  $this->trxIdleCallbacks[$key][2] = $new;
3494  }
3495  }
3496  foreach ( $this->trxEndCallbacks as $key => $info ) {
3497  if ( $info[2] === $old ) {
3498  $this->trxEndCallbacks[$key][2] = $new;
3499  }
3500  }
3501  }
3502 
3507  private function modifyCallbacksForCancel( array $sectionIds ) {
3508  // Cancel the "on commit" callbacks owned by this savepoint
3509  $this->trxIdleCallbacks = array_filter(
3510  $this->trxIdleCallbacks,
3511  function ( $entry ) use ( $sectionIds ) {
3512  return !in_array( $entry[2], $sectionIds, true );
3513  }
3514  );
3515  $this->trxPreCommitCallbacks = array_filter(
3516  $this->trxPreCommitCallbacks,
3517  function ( $entry ) use ( $sectionIds ) {
3518  return !in_array( $entry[2], $sectionIds, true );
3519  }
3520  );
3521  // Make "on resolution" callbacks owned by this savepoint to perceive a rollback
3522  foreach ( $this->trxEndCallbacks as $key => $entry ) {
3523  if ( in_array( $entry[2], $sectionIds, true ) ) {
3524  $callback = $entry[0];
3525  $this->trxEndCallbacks[$key][0] = function () use ( $callback ) {
3526  return $callback( self::TRIGGER_ROLLBACK, $this );
3527  };
3528  }
3529  }
3530  }
3531 
3532  final public function setTransactionListener( $name, callable $callback = null ) {
3533  if ( $callback ) {
3534  $this->trxRecurringCallbacks[$name] = $callback;
3535  } else {
3536  unset( $this->trxRecurringCallbacks[$name] );
3537  }
3538  }
3539 
3548  final public function setTrxEndCallbackSuppression( $suppress ) {
3549  $this->trxEndCallbacksSuppressed = $suppress;
3550  }
3551 
3562  public function runOnTransactionIdleCallbacks( $trigger ) {
3563  if ( $this->trxLevel ) { // sanity
3564  throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open.' );
3565  }
3566 
3567  if ( $this->trxEndCallbacksSuppressed ) {
3568  return 0;
3569  }
3570 
3571  $count = 0;
3572  $autoTrx = $this->getFlag( self::DBO_TRX ); // automatic begin() enabled?
3574  $e = null; // first exception
3575  do { // callbacks may add callbacks :)
3576  $callbacks = array_merge(
3577  $this->trxIdleCallbacks,
3578  $this->trxEndCallbacks // include "transaction resolution" callbacks
3579  );
3580  $this->trxIdleCallbacks = []; // consumed (and recursion guard)
3581  $this->trxEndCallbacks = []; // consumed (recursion guard)
3582  foreach ( $callbacks as $callback ) {
3583  ++$count;
3584  list( $phpCallback ) = $callback;
3585  $this->clearFlag( self::DBO_TRX ); // make each query its own transaction
3586  try {
3587  // @phan-suppress-next-line PhanParamTooManyCallable
3588  call_user_func( $phpCallback, $trigger, $this );
3589  } catch ( Exception $ex ) {
3590  call_user_func( $this->errorLogger, $ex );
3591  $e = $e ?: $ex;
3592  // Some callbacks may use startAtomic/endAtomic, so make sure
3593  // their transactions are ended so other callbacks don't fail
3594  if ( $this->trxLevel() ) {
3595  $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
3596  }
3597  } finally {
3598  if ( $autoTrx ) {
3599  $this->setFlag( self::DBO_TRX ); // restore automatic begin()
3600  } else {
3601  $this->clearFlag( self::DBO_TRX ); // restore auto-commit
3602  }
3603  }
3604  }
3605  } while ( count( $this->trxIdleCallbacks ) );
3606 
3607  if ( $e instanceof Exception ) {
3608  throw $e; // re-throw any first exception
3609  }
3610 
3611  return $count;
3612  }
3613 
3624  $count = 0;
3625 
3626  $e = null; // first exception
3627  do { // callbacks may add callbacks :)
3628  $callbacks = $this->trxPreCommitCallbacks;
3629  $this->trxPreCommitCallbacks = []; // consumed (and recursion guard)
3630  foreach ( $callbacks as $callback ) {
3631  try {
3632  ++$count;
3633  list( $phpCallback ) = $callback;
3634  $phpCallback( $this );
3635  } catch ( Exception $ex ) {
3636  ( $this->errorLogger )( $ex );
3637  $e = $e ?: $ex;
3638  }
3639  }
3640  } while ( count( $this->trxPreCommitCallbacks ) );
3641 
3642  if ( $e instanceof Exception ) {
3643  throw $e; // re-throw any first exception
3644  }
3645 
3646  return $count;
3647  }
3648 
3658  public function runTransactionListenerCallbacks( $trigger ) {
3659  if ( $this->trxEndCallbacksSuppressed ) {
3660  return;
3661  }
3662 
3664  $e = null; // first exception
3665 
3666  foreach ( $this->trxRecurringCallbacks as $phpCallback ) {
3667  try {
3668  $phpCallback( $trigger, $this );
3669  } catch ( Exception $ex ) {
3670  ( $this->errorLogger )( $ex );
3671  $e = $e ?: $ex;
3672  }
3673  }
3674 
3675  if ( $e instanceof Exception ) {
3676  throw $e; // re-throw any first exception
3677  }
3678  }
3679 
3690  protected function doSavepoint( $identifier, $fname ) {
3691  $this->query( 'SAVEPOINT ' . $this->addIdentifierQuotes( $identifier ), $fname );
3692  }
3693 
3704  protected function doReleaseSavepoint( $identifier, $fname ) {
3705  $this->query( 'RELEASE SAVEPOINT ' . $this->addIdentifierQuotes( $identifier ), $fname );
3706  }
3707 
3718  protected function doRollbackToSavepoint( $identifier, $fname ) {
3719  $this->query( 'ROLLBACK TO SAVEPOINT ' . $this->addIdentifierQuotes( $identifier ), $fname );
3720  }
3721 
3726  private function nextSavepointId( $fname ) {
3727  $savepointId = self::$SAVEPOINT_PREFIX . ++$this->trxAtomicCounter;
3728  if ( strlen( $savepointId ) > 30 ) {
3729  // 30 == Oracle's identifier length limit (pre 12c)
3730  // With a 22 character prefix, that puts the highest number at 99999999.
3731  throw new DBUnexpectedError(
3732  $this,
3733  'There have been an excessively large number of atomic sections in a transaction'
3734  . " started by $this->trxFname (at $fname)"
3735  );
3736  }
3737 
3738  return $savepointId;
3739  }
3740 
3741  final public function startAtomic(
3742  $fname = __METHOD__, $cancelable = self::ATOMIC_NOT_CANCELABLE
3743  ) {
3744  $savepointId = $cancelable === self::ATOMIC_CANCELABLE ? self::$NOT_APPLICABLE : null;
3745 
3746  if ( !$this->trxLevel ) {
3747  $this->begin( $fname, self::TRANSACTION_INTERNAL ); // sets trxAutomatic
3748  // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
3749  // in all changes being in one transaction to keep requests transactional.
3750  if ( $this->getFlag( self::DBO_TRX ) ) {
3751  // Since writes could happen in between the topmost atomic sections as part
3752  // of the transaction, those sections will need savepoints.
3753  $savepointId = $this->nextSavepointId( $fname );
3754  $this->doSavepoint( $savepointId, $fname );
3755  } else {
3756  $this->trxAutomaticAtomic = true;
3757  }
3758  } elseif ( $cancelable === self::ATOMIC_CANCELABLE ) {
3759  $savepointId = $this->nextSavepointId( $fname );
3760  $this->doSavepoint( $savepointId, $fname );
3761  }
3762 
3763  $sectionId = new AtomicSectionIdentifier;
3764  $this->trxAtomicLevels[] = [ $fname, $sectionId, $savepointId ];
3765  $this->queryLogger->debug( 'startAtomic: entering level ' .
3766  ( count( $this->trxAtomicLevels ) - 1 ) . " ($fname)" );
3767 
3768  return $sectionId;
3769  }
3770 
3771  final public function endAtomic( $fname = __METHOD__ ) {
3772  if ( !$this->trxLevel || !$this->trxAtomicLevels ) {
3773  throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
3774  }
3775 
3776  // Check if the current section matches $fname
3777  $pos = count( $this->trxAtomicLevels ) - 1;
3778  list( $savedFname, $sectionId, $savepointId ) = $this->trxAtomicLevels[$pos];
3779  $this->queryLogger->debug( "endAtomic: leaving level $pos ($fname)" );
3780 
3781  if ( $savedFname !== $fname ) {
3782  throw new DBUnexpectedError(
3783  $this,
3784  "Invalid atomic section ended (got $fname but expected $savedFname)."
3785  );
3786  }
3787 
3788  // Remove the last section (no need to re-index the array)
3789  array_pop( $this->trxAtomicLevels );
3790 
3791  if ( !$this->trxAtomicLevels && $this->trxAutomaticAtomic ) {
3792  $this->commit( $fname, self::FLUSHING_INTERNAL );
3793  } elseif ( $savepointId !== null && $savepointId !== self::$NOT_APPLICABLE ) {
3794  $this->doReleaseSavepoint( $savepointId, $fname );
3795  }
3796 
3797  // Hoist callback ownership for callbacks in the section that just ended;
3798  // all callbacks should have an owner that is present in trxAtomicLevels.
3799  $currentSectionId = $this->currentAtomicSectionId();
3800  if ( $currentSectionId ) {
3801  $this->reassignCallbacksForSection( $sectionId, $currentSectionId );
3802  }
3803  }
3804 
3805  final public function cancelAtomic(
3806  $fname = __METHOD__, AtomicSectionIdentifier $sectionId = null
3807  ) {
3808  if ( !$this->trxLevel || !$this->trxAtomicLevels ) {
3809  throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
3810  }
3811 
3812  $excisedFnames = [];
3813  if ( $sectionId !== null ) {
3814  // Find the (last) section with the given $sectionId
3815  $pos = -1;
3816  foreach ( $this->trxAtomicLevels as $i => list( $asFname, $asId, $spId ) ) {
3817  if ( $asId === $sectionId ) {
3818  $pos = $i;
3819  }
3820  }
3821  if ( $pos < 0 ) {
3822  throw new DBUnexpectedError( $this, "Atomic section not found (for $fname)" );
3823  }
3824  // Remove all descendant sections and re-index the array
3825  $excisedIds = [];
3826  $len = count( $this->trxAtomicLevels );
3827  for ( $i = $pos + 1; $i < $len; ++$i ) {
3828  $excisedFnames[] = $this->trxAtomicLevels[$i][0];
3829  $excisedIds[] = $this->trxAtomicLevels[$i][1];
3830  }
3831  $this->trxAtomicLevels = array_slice( $this->trxAtomicLevels, 0, $pos + 1 );
3832  $this->modifyCallbacksForCancel( $excisedIds );
3833  }
3834 
3835  // Check if the current section matches $fname
3836  $pos = count( $this->trxAtomicLevels ) - 1;
3837  list( $savedFname, $savedSectionId, $savepointId ) = $this->trxAtomicLevels[$pos];
3838 
3839  if ( $excisedFnames ) {
3840  $this->queryLogger->debug( "cancelAtomic: canceling level $pos ($savedFname) " .
3841  "and descendants " . implode( ', ', $excisedFnames ) );
3842  } else {
3843  $this->queryLogger->debug( "cancelAtomic: canceling level $pos ($savedFname)" );
3844  }
3845 
3846  if ( $savedFname !== $fname ) {
3847  throw new DBUnexpectedError(
3848  $this,
3849  "Invalid atomic section ended (got $fname but expected $savedFname)."
3850  );
3851  }
3852 
3853  // Remove the last section (no need to re-index the array)
3854  array_pop( $this->trxAtomicLevels );
3855  $this->modifyCallbacksForCancel( [ $savedSectionId ] );
3856 
3857  if ( $savepointId !== null ) {
3858  // Rollback the transaction to the state just before this atomic section
3859  if ( $savepointId === self::$NOT_APPLICABLE ) {
3860  $this->rollback( $fname, self::FLUSHING_INTERNAL );
3861  } else {
3862  $this->doRollbackToSavepoint( $savepointId, $fname );
3863  $this->trxStatus = self::STATUS_TRX_OK; // no exception; recovered
3864  $this->trxStatusIgnoredCause = null;
3865  }
3866  } elseif ( $this->trxStatus > self::STATUS_TRX_ERROR ) {
3867  // Put the transaction into an error state if it's not already in one
3868  $this->trxStatus = self::STATUS_TRX_ERROR;
3869  $this->trxStatusCause = new DBUnexpectedError(
3870  $this,
3871  "Uncancelable atomic section canceled (got $fname)."
3872  );
3873  }
3874 
3875  $this->affectedRowCount = 0; // for the sake of consistency
3876  }
3877 
3878  final public function doAtomicSection(
3879  $fname, callable $callback, $cancelable = self::ATOMIC_NOT_CANCELABLE
3880  ) {
3881  $sectionId = $this->startAtomic( $fname, $cancelable );
3882  try {
3883  $res = $callback( $this, $fname );
3884  } catch ( Exception $e ) {
3885  $this->cancelAtomic( $fname, $sectionId );
3886 
3887  throw $e;
3888  }
3889  $this->endAtomic( $fname );
3890 
3891  return $res;
3892  }
3893 
3894  final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
3895  static $modes = [ self::TRANSACTION_EXPLICIT, self::TRANSACTION_INTERNAL ];
3896  if ( !in_array( $mode, $modes, true ) ) {
3897  throw new DBUnexpectedError( $this, "$fname: invalid mode parameter '$mode'." );
3898  }
3899 
3900  // Protect against mismatched atomic section, transaction nesting, and snapshot loss
3901  if ( $this->trxLevel ) {
3902  if ( $this->trxAtomicLevels ) {
3903  $levels = $this->flatAtomicSectionList();
3904  $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
3905  throw new DBUnexpectedError( $this, $msg );
3906  } elseif ( !$this->trxAutomatic ) {
3907  $msg = "$fname: Explicit transaction already active (from {$this->trxFname}).";
3908  throw new DBUnexpectedError( $this, $msg );
3909  } else {
3910  $msg = "$fname: Implicit transaction already active (from {$this->trxFname}).";
3911  throw new DBUnexpectedError( $this, $msg );
3912  }
3913  } elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
3914  $msg = "$fname: Implicit transaction expected (DBO_TRX set).";
3915  throw new DBUnexpectedError( $this, $msg );
3916  }
3917 
3918  $this->assertHasConnectionHandle();
3919 
3920  $this->doBegin( $fname );
3921  $this->trxStatus = self::STATUS_TRX_OK;
3922  $this->trxStatusIgnoredCause = null;
3923  $this->trxAtomicCounter = 0;
3924  $this->trxTimestamp = microtime( true );
3925  $this->trxFname = $fname;
3926  $this->trxDoneWrites = false;
3927  $this->trxAutomaticAtomic = false;
3928  $this->trxAtomicLevels = [];
3929  $this->trxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
3930  $this->trxWriteDuration = 0.0;
3931  $this->trxWriteQueryCount = 0;
3932  $this->trxWriteAffectedRows = 0;
3933  $this->trxWriteAdjDuration = 0.0;
3934  $this->trxWriteAdjQueryCount = 0;
3935  $this->trxWriteCallers = [];
3936  // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
3937  // Get an estimate of the replication lag before any such queries.
3938  $this->trxReplicaLag = null; // clear cached value first
3939  $this->trxReplicaLag = $this->getApproximateLagStatus()['lag'];
3940  // T147697: make explicitTrxActive() return true until begin() finishes. This way, no
3941  // caller will think its OK to muck around with the transaction just because startAtomic()
3942  // has not yet completed (e.g. setting trxAtomicLevels).
3943  $this->trxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
3944  }
3945 
3952  protected function doBegin( $fname ) {
3953  $this->query( 'BEGIN', $fname );
3954  $this->trxLevel = 1;
3955  }
3956 
3957  final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
3958  static $modes = [ self::FLUSHING_ONE, self::FLUSHING_ALL_PEERS, self::FLUSHING_INTERNAL ];
3959  if ( !in_array( $flush, $modes, true ) ) {
3960  throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'." );
3961  }
3962 
3963  if ( $this->trxLevel && $this->trxAtomicLevels ) {
3964  // There are still atomic sections open; this cannot be ignored
3965  $levels = $this->flatAtomicSectionList();
3966  throw new DBUnexpectedError(
3967  $this,
3968  "$fname: Got COMMIT while atomic sections $levels are still open."
3969  );
3970  }
3971 
3972  if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
3973  if ( !$this->trxLevel ) {
3974  return; // nothing to do
3975  } elseif ( !$this->trxAutomatic ) {
3976  throw new DBUnexpectedError(
3977  $this,
3978  "$fname: Flushing an explicit transaction, getting out of sync."
3979  );
3980  }
3981  } elseif ( !$this->trxLevel ) {
3982  $this->queryLogger->error(
3983  "$fname: No transaction to commit, something got out of sync." );
3984  return; // nothing to do
3985  } elseif ( $this->trxAutomatic ) {
3986  throw new DBUnexpectedError(
3987  $this,
3988  "$fname: Expected mass commit of all peer transactions (DBO_TRX set)."
3989  );
3990  }
3991 
3992  $this->assertHasConnectionHandle();
3993 
3995 
3996  $writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY );
3997  $this->doCommit( $fname );
3998  $this->trxStatus = self::STATUS_TRX_NONE;
3999 
4000  if ( $this->trxDoneWrites ) {
4001  $this->lastWriteTime = microtime( true );
4002  $this->trxProfiler->transactionWritingOut(
4003  $this->server,
4004  $this->getDomainID(),
4005  $this->trxShortId,
4006  $writeTime,
4007  $this->trxWriteAffectedRows
4008  );
4009  }
4010 
4011  // With FLUSHING_ALL_PEERS, callbacks will be explicitly run later
4012  if ( $flush !== self::FLUSHING_ALL_PEERS ) {
4013  $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
4014  $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT );
4015  }
4016  }
4017 
4024  protected function doCommit( $fname ) {
4025  if ( $this->trxLevel ) {
4026  $this->query( 'COMMIT', $fname );
4027  $this->trxLevel = 0;
4028  }
4029  }
4030 
4031  final public function rollback( $fname = __METHOD__, $flush = '' ) {
4032  $trxActive = $this->trxLevel;
4033 
4034  if ( $flush !== self::FLUSHING_INTERNAL
4035  && $flush !== self::FLUSHING_ALL_PEERS
4036  && $this->getFlag( self::DBO_TRX )
4037  ) {
4038  throw new DBUnexpectedError(
4039  $this,
4040  "$fname: Expected mass rollback of all peer transactions (DBO_TRX set)."
4041  );
4042  }
4043 
4044  if ( $trxActive ) {
4045  $this->assertHasConnectionHandle();
4046 
4047  $this->doRollback( $fname );
4048  $this->trxStatus = self::STATUS_TRX_NONE;
4049  $this->trxAtomicLevels = [];
4050  // Estimate the RTT via a query now that trxStatus is OK
4051  $writeTime = $this->pingAndCalculateLastTrxApplyTime();
4052 
4053  if ( $this->trxDoneWrites ) {
4054  $this->trxProfiler->transactionWritingOut(
4055  $this->server,
4056  $this->getDomainID(),
4057  $this->trxShortId,
4058  $writeTime,
4059  $this->trxWriteAffectedRows
4060  );
4061  }
4062  }
4063 
4064  // Clear any commit-dependant callbacks. They might even be present
4065  // only due to transaction rounds, with no SQL transaction being active
4066  $this->trxIdleCallbacks = [];
4067  $this->trxPreCommitCallbacks = [];
4068 
4069  // With FLUSHING_ALL_PEERS, callbacks will be explicitly run later
4070  if ( $trxActive && $flush !== self::FLUSHING_ALL_PEERS ) {
4071  try {
4072  $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
4073  } catch ( Exception $e ) {
4074  // already logged; finish and let LoadBalancer move on during mass-rollback
4075  }
4076  try {
4077  $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
4078  } catch ( Exception $e ) {
4079  // already logged; let LoadBalancer move on during mass-rollback
4080  }
4081 
4082  $this->affectedRowCount = 0; // for the sake of consistency
4083  }
4084  }
4085 
4092  protected function doRollback( $fname ) {
4093  if ( $this->trxLevel ) {
4094  # Disconnects cause rollback anyway, so ignore those errors
4095  $ignoreErrors = true;
4096  $this->query( 'ROLLBACK', $fname, $ignoreErrors );
4097  $this->trxLevel = 0;
4098  }
4099  }
4100 
4101  public function flushSnapshot( $fname = __METHOD__ ) {
4102  if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
4103  // This only flushes transactions to clear snapshots, not to write data
4104  $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
4105  throw new DBUnexpectedError(
4106  $this,
4107  "$fname: Cannot flush snapshot because writes are pending ($fnames)."
4108  );
4109  }
4110 
4111  $this->commit( $fname, self::FLUSHING_INTERNAL );
4112  }
4113 
4114  public function explicitTrxActive() {
4115  return $this->trxLevel && ( $this->trxAtomicLevels || !$this->trxAutomatic );
4116  }
4117 
4118  public function duplicateTableStructure(
4119  $oldName, $newName, $temporary = false, $fname = __METHOD__
4120  ) {
4121  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
4122  }
4123 
4124  public function listTables( $prefix = null, $fname = __METHOD__ ) {
4125  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
4126  }
4127 
4128  public function listViews( $prefix = null, $fname = __METHOD__ ) {
4129  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
4130  }
4131 
4132  public function timestamp( $ts = 0 ) {
4133  $t = new ConvertibleTimestamp( $ts );
4134  // Let errors bubble up to avoid putting garbage in the DB
4135  return $t->getTimestamp( TS_MW );
4136  }
4137 
4138  public function timestampOrNull( $ts = null ) {
4139  if ( is_null( $ts ) ) {
4140  return null;
4141  } else {
4142  return $this->timestamp( $ts );
4143  }
4144  }
4145 
4146  public function affectedRows() {
4147  return ( $this->affectedRowCount === null )
4148  ? $this->fetchAffectedRowCount() // default to driver value
4150  }
4151 
4155  abstract protected function fetchAffectedRowCount();
4156 
4170  protected function resultObject( $result ) {
4171  if ( !$result ) {
4172  return false;
4173  } elseif ( $result instanceof ResultWrapper ) {
4174  return $result;
4175  } elseif ( $result === true ) {
4176  // Successful write query
4177  return $result;
4178  } else {
4179  return new ResultWrapper( $this, $result );
4180  }
4181  }
4182 
4183  public function ping( &$rtt = null ) {
4184  // Avoid hitting the server if it was hit recently
4185  if ( $this->isOpen() && ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
4186  if ( !func_num_args() || $this->rttEstimate > 0 ) {
4187  $rtt = $this->rttEstimate;
4188  return true; // don't care about $rtt
4189  }
4190  }
4191 
4192  // This will reconnect if possible or return false if not
4193  $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
4194  $ok = ( $this->query( self::PING_QUERY, __METHOD__, true ) !== false );
4195  $this->restoreFlags( self::RESTORE_PRIOR );
4196 
4197  if ( $ok ) {
4198  $rtt = $this->rttEstimate;
4199  }
4200 
4201  return $ok;
4202  }
4203 
4210  protected function replaceLostConnection( $fname ) {
4211  $this->closeConnection();
4212  $this->opened = false;
4213  $this->conn = false;
4214 
4215  $this->handleSessionLossPreconnect();
4216 
4217  try {
4218  $this->open(
4219  $this->server,
4220  $this->user,
4221  $this->password,
4222  $this->getDBname(),
4223  $this->dbSchema(),
4224  $this->tablePrefix()
4225  );
4226  $this->lastPing = microtime( true );
4227  $ok = true;
4228 
4229  $this->connLogger->warning(
4230  $fname . ': lost connection to {dbserver}; reconnected',
4231  [
4232  'dbserver' => $this->getServer(),
4233  'trace' => ( new RuntimeException() )->getTraceAsString()
4234  ]
4235  );
4236  } catch ( DBConnectionError $e ) {
4237  $ok = false;
4238 
4239  $this->connLogger->error(
4240  $fname . ': lost connection to {dbserver} permanently',
4241  [ 'dbserver' => $this->getServer() ]
4242  );
4243  }
4244 
4246 
4247  return $ok;
4248  }
4249 
4250  public function getSessionLagStatus() {
4251  return $this->getRecordedTransactionLagStatus() ?: $this->getApproximateLagStatus();
4252  }
4253 
4267  final protected function getRecordedTransactionLagStatus() {
4268  return ( $this->trxLevel && $this->trxReplicaLag !== null )
4269  ? [ 'lag' => $this->trxReplicaLag, 'since' => $this->trxTimestamp() ]
4270  : null;
4271  }
4272 
4279  protected function getApproximateLagStatus() {
4280  return [
4281  'lag' => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
4282  'since' => microtime( true )
4283  ];
4284  }
4285 
4305  public static function getCacheSetOptions( IDatabase $db1, IDatabase $db2 = null ) {
4306  $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
4307  foreach ( func_get_args() as $db ) {
4309  $status = $db->getSessionLagStatus();
4310  if ( $status['lag'] === false ) {
4311  $res['lag'] = false;
4312  } elseif ( $res['lag'] !== false ) {
4313  $res['lag'] = max( $res['lag'], $status['lag'] );
4314  }
4315  $res['since'] = min( $res['since'], $status['since'] );
4316  $res['pending'] = $res['pending'] ?: $db->writesPending();
4317  }
4318 
4319  return $res;
4320  }
4321 
4322  public function getLag() {
4323  return 0;
4324  }
4325 
4326  public function maxListLen() {
4327  return 0;
4328  }
4329 
4330  public function encodeBlob( $b ) {
4331  return $b;
4332  }
4333 
4334  public function decodeBlob( $b ) {
4335  if ( $b instanceof Blob ) {
4336  $b = $b->fetch();
4337  }
4338  return $b;
4339  }
4340 
4341  public function setSessionOptions( array $options ) {
4342  }
4343 
4344  public function sourceFile(
4345  $filename,
4346  callable $lineCallback = null,
4347  callable $resultCallback = null,
4348  $fname = false,
4349  callable $inputCallback = null
4350  ) {
4351  Wikimedia\suppressWarnings();
4352  $fp = fopen( $filename, 'r' );
4353  Wikimedia\restoreWarnings();
4354 
4355  if ( $fp === false ) {
4356  throw new RuntimeException( "Could not open \"{$filename}\".\n" );
4357  }
4358 
4359  if ( !$fname ) {
4360  $fname = __METHOD__ . "( $filename )";
4361  }
4362 
4363  try {
4364  $error = $this->sourceStream(
4365  $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
4366  } catch ( Exception $e ) {
4367  fclose( $fp );
4368  throw $e;
4369  }
4370 
4371  fclose( $fp );
4372 
4373  return $error;
4374  }
4375 
4376  public function setSchemaVars( $vars ) {
4377  $this->schemaVars = $vars;
4378  }
4379 
4380  public function sourceStream(
4381  $fp,
4382  callable $lineCallback = null,
4383  callable $resultCallback = null,
4384  $fname = __METHOD__,
4385  callable $inputCallback = null
4386  ) {
4387  $delimiterReset = new ScopedCallback(
4388  function ( $delimiter ) {
4389  $this->delimiter = $delimiter;
4390  },
4391  [ $this->delimiter ]
4392  );
4393  $cmd = '';
4394 
4395  while ( !feof( $fp ) ) {
4396  if ( $lineCallback ) {
4397  call_user_func( $lineCallback );
4398  }
4399 
4400  $line = trim( fgets( $fp ) );
4401 
4402  if ( $line == '' ) {
4403  continue;
4404  }
4405 
4406  if ( $line[0] == '-' && $line[1] == '-' ) {
4407  continue;
4408  }
4409 
4410  if ( $cmd != '' ) {
4411  $cmd .= ' ';
4412  }
4413 
4414  $done = $this->streamStatementEnd( $cmd, $line );
4415 
4416  $cmd .= "$line\n";
4417 
4418  if ( $done || feof( $fp ) ) {
4419  $cmd = $this->replaceVars( $cmd );
4420 
4421  if ( $inputCallback ) {
4422  $callbackResult = $inputCallback( $cmd );
4423 
4424  if ( is_string( $callbackResult ) || !$callbackResult ) {
4425  $cmd = $callbackResult;
4426  }
4427  }
4428 
4429  if ( $cmd ) {
4430  $res = $this->query( $cmd, $fname );
4431 
4432  if ( $resultCallback ) {
4433  $resultCallback( $res, $this );
4434  }
4435 
4436  if ( $res === false ) {
4437  $err = $this->lastError();
4438 
4439  return "Query \"{$cmd}\" failed with error code \"$err\".\n";
4440  }
4441  }
4442  $cmd = '';
4443  }
4444  }
4445 
4446  ScopedCallback::consume( $delimiterReset );
4447  return true;
4448  }
4449 
4457  public function streamStatementEnd( &$sql, &$newLine ) {
4458  if ( $this->delimiter ) {
4459  $prev = $newLine;
4460  $newLine = preg_replace(
4461  '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
4462  if ( $newLine != $prev ) {
4463  return true;
4464  }
4465  }
4466 
4467  return false;
4468  }
4469 
4490  protected function replaceVars( $ins ) {
4491  $vars = $this->getSchemaVars();
4492  return preg_replace_callback(
4493  '!
4494  /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
4495  \'\{\$ (\w+) }\' | # 3. addQuotes
4496  `\{\$ (\w+) }` | # 4. addIdentifierQuotes
4497  /\*\$ (\w+) \*/ # 5. leave unencoded
4498  !x',
4499  function ( $m ) use ( $vars ) {
4500  // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
4501  // check for both nonexistent keys *and* the empty string.
4502  if ( isset( $m[1] ) && $m[1] !== '' ) {
4503  if ( $m[1] === 'i' ) {
4504  return $this->indexName( $m[2] );
4505  } else {
4506  return $this->tableName( $m[2] );
4507  }
4508  } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
4509  return $this->addQuotes( $vars[$m[3]] );
4510  } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
4511  return $this->addIdentifierQuotes( $vars[$m[4]] );
4512  } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
4513  return $vars[$m[5]];
4514  } else {
4515  return $m[0];
4516  }
4517  },
4518  $ins
4519  );
4520  }
4521 
4528  protected function getSchemaVars() {
4529  if ( $this->schemaVars ) {
4530  return $this->schemaVars;
4531  } else {
4532  return $this->getDefaultSchemaVars();
4533  }
4534  }
4535 
4544  protected function getDefaultSchemaVars() {
4545  return [];
4546  }
4547 
4548  public function lockIsFree( $lockName, $method ) {
4549  // RDBMs methods for checking named locks may or may not count this thread itself.
4550  // In MySQL, IS_FREE_LOCK() returns 0 if the thread already has the lock. This is
4551  // the behavior choosen by the interface for this method.
4552  return !isset( $this->namedLocksHeld[$lockName] );
4553  }
4554 
4555  public function lock( $lockName, $method, $timeout = 5 ) {
4556  $this->namedLocksHeld[$lockName] = 1;
4557 
4558  return true;
4559  }
4560 
4561  public function unlock( $lockName, $method ) {
4562  unset( $this->namedLocksHeld[$lockName] );
4563 
4564  return true;
4565  }
4566 
4567  public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
4568  if ( $this->writesOrCallbacksPending() ) {
4569  // This only flushes transactions to clear snapshots, not to write data
4570  $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
4571  throw new DBUnexpectedError(
4572  $this,
4573  "$fname: Cannot flush pre-lock snapshot because writes are pending ($fnames)."
4574  );
4575  }
4576 
4577  if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
4578  return null;
4579  }
4580 
4581  $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
4582  if ( $this->trxLevel() ) {
4583  // There is a good chance an exception was thrown, causing any early return
4584  // from the caller. Let any error handler get a chance to issue rollback().
4585  // If there isn't one, let the error bubble up and trigger server-side rollback.
4586  $this->onTransactionResolution(
4587  function () use ( $lockKey, $fname ) {
4588  $this->unlock( $lockKey, $fname );
4589  },
4590  $fname
4591  );
4592  } else {
4593  $this->unlock( $lockKey, $fname );
4594  }
4595  } );
4596 
4597  $this->commit( $fname, self::FLUSHING_INTERNAL );
4598 
4599  return $unlocker;
4600  }
4601 
4602  public function namedLocksEnqueue() {
4603  return false;
4604  }
4605 
4607  return true;
4608  }
4609 
4610  final public function lockTables( array $read, array $write, $method ) {
4611  if ( $this->writesOrCallbacksPending() ) {
4612  throw new DBUnexpectedError( $this, "Transaction writes or callbacks still pending." );
4613  }
4614 
4615  if ( $this->tableLocksHaveTransactionScope() ) {
4616  $this->startAtomic( $method );
4617  }
4618 
4619  return $this->doLockTables( $read, $write, $method );
4620  }
4621 
4630  protected function doLockTables( array $read, array $write, $method ) {
4631  return true;
4632  }
4633 
4634  final public function unlockTables( $method ) {
4635  if ( $this->tableLocksHaveTransactionScope() ) {
4636  $this->endAtomic( $method );
4637 
4638  return true; // locks released on COMMIT/ROLLBACK
4639  }
4640 
4641  return $this->doUnlockTables( $method );
4642  }
4643 
4650  protected function doUnlockTables( $method ) {
4651  return true;
4652  }
4653 
4661  public function dropTable( $tableName, $fName = __METHOD__ ) {
4662  if ( !$this->tableExists( $tableName, $fName ) ) {
4663  return false;
4664  }
4665  $sql = "DROP TABLE " . $this->tableName( $tableName ) . " CASCADE";
4666 
4667  return $this->query( $sql, $fName );
4668  }
4669 
4670  public function getInfinity() {
4671  return 'infinity';
4672  }
4673 
4674  public function encodeExpiry( $expiry ) {
4675  return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
4676  ? $this->getInfinity()
4677  : $this->timestamp( $expiry );
4678  }
4679 
4680  public function decodeExpiry( $expiry, $format = TS_MW ) {
4681  if ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) {
4682  return 'infinity';
4683  }
4684 
4685  return ConvertibleTimestamp::convert( $format, $expiry );
4686  }
4687 
4688  public function setBigSelects( $value = true ) {
4689  // no-op
4690  }
4691 
4692  public function isReadOnly() {
4693  return ( $this->getReadOnlyReason() !== false );
4694  }
4695 
4699  protected function getReadOnlyReason() {
4700  $reason = $this->getLBInfo( 'readOnlyReason' );
4701 
4702  return is_string( $reason ) ? $reason : false;
4703  }
4704 
4705  public function setTableAliases( array $aliases ) {
4706  $this->tableAliases = $aliases;
4707  }
4708 
4709  public function setIndexAliases( array $aliases ) {
4710  $this->indexAliases = $aliases;
4711  }
4712 
4718  protected function hasFlags( $field, $flags ) {
4719  return ( ( $field & $flags ) === $flags );
4720  }
4721 
4733  protected function getBindingHandle() {
4734  if ( !$this->conn ) {
4735  throw new DBUnexpectedError(
4736  $this,
4737  'DB connection was already closed or the connection dropped.'
4738  );
4739  }
4740 
4741  return $this->conn;
4742  }
4743 
4748  public function __toString() {
4749  return (string)$this->conn;
4750  }
4751 
4756  public function __clone() {
4757  $this->connLogger->warning(
4758  "Cloning " . static::class . " is not recommended; forking connection:\n" .
4759  ( new RuntimeException() )->getTraceAsString()
4760  );
4761 
4762  if ( $this->isOpen() ) {
4763  // Open a new connection resource without messing with the old one
4764  $this->opened = false;
4765  $this->conn = false;
4766  $this->trxEndCallbacks = []; // don't copy
4767  $this->handleSessionLossPreconnect(); // no trx or locks anymore
4768  $this->open(
4769  $this->server,
4770  $this->user,
4771  $this->password,
4772  $this->getDBname(),
4773  $this->dbSchema(),
4774  $this->tablePrefix()
4775  );
4776  $this->lastPing = microtime( true );
4777  }
4778  }
4779 
4785  public function __sleep() {
4786  throw new RuntimeException( 'Database serialization may cause problems, since ' .
4787  'the connection is not restored on wakeup.' );
4788  }
4789 
4793  public function __destruct() {
4794  if ( $this->trxLevel && $this->trxDoneWrites ) {
4795  trigger_error( "Uncommitted DB writes (transaction from {$this->trxFname})." );
4796  }
4797 
4798  $danglingWriters = $this->pendingWriteAndCallbackCallers();
4799  if ( $danglingWriters ) {
4800  $fnames = implode( ', ', $danglingWriters );
4801  trigger_error( "DB transaction writes or callbacks still pending ($fnames)." );
4802  }
4803 
4804  if ( $this->conn ) {
4805  // Avoid connection leaks for sanity. Normally, resources close at script completion.
4806  // The connection might already be closed in zend/hhvm by now, so suppress warnings.
4807  Wikimedia\suppressWarnings();
4808  $this->closeConnection();
4809  Wikimedia\restoreWarnings();
4810  $this->conn = false;
4811  $this->opened = false;
4812  }
4813  }
4814 }
4815 
4819 class_alias( Database::class, 'DatabaseBase' );
4820 
4824 class_alias( Database::class, 'Database' );
Wikimedia\Rdbms\Database\implicitOrderby
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
Definition: Database.php:679
Wikimedia\Rdbms\Database\tablePrefix
tablePrefix( $prefix=null)
Get/set the table prefix.
Definition: Database.php:604
$status
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1266
Wikimedia\Rdbms\Database\$trxFname
string $trxFname
Remembers the function name given for starting the most recent transaction via begin().
Definition: Database.php:187
Wikimedia\Rdbms\Database\getLastPHPError
getLastPHPError()
Definition: Database.php:899
Wikimedia\Rdbms\Database\insert
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
Definition: Database.php:2110
Wikimedia\Rdbms\Database\setSessionOptions
setSessionOptions(array $options)
Override database's default behavior.
Definition: Database.php:4341
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:48
Wikimedia\Rdbms\Database\limitResult
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.
Definition: Database.php:3213
Wikimedia\Rdbms\Database\$trxDoneWrites
bool $trxDoneWrites
Record if possible write queries were done in the last transaction started.
Definition: Database.php:194
Wikimedia\Rdbms\Database\$trxEndCallbacksSuppressed
bool $trxEndCallbacksSuppressed
Whether to suppress triggering of transaction end callbacks.
Definition: Database.php:121
Wikimedia\Rdbms\Database\makeUpdateOptionsArray
makeUpdateOptionsArray( $options)
Make UPDATE options array for Database::makeUpdateOptions.
Definition: Database.php:2160
Wikimedia\Rdbms\Database\replaceLostConnection
replaceLostConnection( $fname)
Close any existing (dead) database connection and open a new connection.
Definition: Database.php:4210
Wikimedia\Rdbms\Database\$trxWriteAdjQueryCount
int $trxWriteAdjQueryCount
Number of write queries counted in trxWriteAdjDuration.
Definition: Database.php:245
Wikimedia\Rdbms\Database\getBindingHandle
getBindingHandle()
Get the underlying binding connection handle.
Definition: Database.php:4733
Wikimedia\Rdbms\Database\$trxLevel
int $trxLevel
Either 1 if a transaction is active or 0 otherwise.
Definition: Database.php:161
Wikimedia\Rdbms\Database\canRecoverFromDisconnect
canRecoverFromDisconnect( $sql, $priorWritesPending)
Determine whether it is safe to retry queries after a database connection is lost.
Definition: Database.php:1458
Wikimedia\Rdbms\Database\buildGroupConcatField
buildGroupConcatField( $delim, $table, $field, $conds='', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
Definition: Database.php:2319
Wikimedia\Rdbms\Database\doInitConnection
doInitConnection()
Actually connect to the database over the wire (or to local files)
Definition: Database.php:360
Wikimedia\Rdbms\Database\$trxReplicaLag
float $trxReplicaLag
Lag estimate at the time of BEGIN.
Definition: Database.php:179
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
Wikimedia\Rdbms\Database\isQuotedIdentifier
isQuotedIdentifier( $name)
Returns if the given identifier looks quoted or not according to the database convention for quoting ...
Definition: Database.php:2763
Wikimedia\Rdbms\IDatabase\getServerVersion
getServerVersion()
A string describing the current software version, like from mysql_get_server_info().
Wikimedia\Rdbms\Database\fieldExists
fieldExists( $table, $field, $fname=__METHOD__)
Determines whether a field exists in a table.
Definition: Database.php:2069
Wikimedia\Rdbms\Database\bitOr
bitOr( $fieldLeft, $fieldRight)
Definition: Database.php:2311
Wikimedia\Rdbms\Database\selectRowCount
selectRowCount( $tables, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Get the number of rows in dataset.
Definition: Database.php:1907
Wikimedia\Rdbms\Database\ignoreIndexClause
ignoreIndexClause( $index)
IGNORE INDEX clause.
Definition: Database.php:2841
Wikimedia\Rdbms\Database\factory
static factory( $dbType, $p=[], $connect=self::NEW_CONNECTED)
Construct a Database subclass instance given a database type and parameters.
Definition: Database.php:434
Wikimedia\Rdbms\Database\trxStatus
trxStatus()
Definition: Database.php:600
Wikimedia\Rdbms\Database\estimateRowCount
estimateRowCount( $table, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Estimate the number of rows in dataset.
Definition: Database.php:1890
Wikimedia\Rdbms\Database\listTables
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
Definition: Database.php:4124
HashBagOStuff
Simple store for keeping values in an associative array for the current process.
Definition: HashBagOStuff.php:31
Wikimedia\Rdbms\Database\updateTrxWriteQueryTime
updateTrxWriteQueryTime( $sql, $runtime, $affected)
Update the estimated run-time of a query, not counting large row lock times.
Definition: Database.php:1383
Wikimedia\Rdbms\Database\makeWhereFrom2d
makeWhereFrom2d( $data, $baseKey, $subKey)
Build a partial where clause from a 2-d array such as used for LinkBatch.
Definition: Database.php:2280
Wikimedia\Rdbms\Database\$queryLogger
LoggerInterface $queryLogger
Definition: Database.php:101
Wikimedia\Rdbms\DatabaseDomain\newFromId
static newFromId( $domain)
Definition: DatabaseDomain.php:67
Wikimedia\Rdbms\Database\encodeBlob
encodeBlob( $b)
Some DBMSs have a special format for inserting into blob fields, they don't allow simple quoted strin...
Definition: Database.php:4330
Wikimedia\Rdbms\Database\PING_TTL
const PING_TTL
How long before it is worth doing a dummy query to test the connection.
Definition: Database.php:57
Wikimedia\Rdbms\Database\getDomainID
getDomainID()
Return the currently selected domain ID.
Definition: Database.php:848
Wikimedia\Rdbms\Database\getClass
static getClass( $dbType, $driver=null)
Definition: Database.php:506
Wikimedia\Rdbms\Database\getProperty
getProperty( $name)
Definition: Database.php:844
Wikimedia\Rdbms\Database\pingAndCalculateLastTrxApplyTime
pingAndCalculateLastTrxApplyTime()
Definition: Database.php:744
Wikimedia\Rdbms\IDatabase\numRows
numRows( $res)
Get the number of rows in a query result.
Wikimedia\Rdbms\Database\assertHasConnectionHandle
assertHasConnectionHandle()
Make sure there is an open connection handle (alive or not) as a sanity check.
Definition: Database.php:1022
Wikimedia\Rdbms\Database\onTransactionPreCommitOrIdle
onTransactionPreCommitOrIdle(callable $callback, $fname=__METHOD__)
Run a callback before the current transaction commits or now if there is none.
Definition: Database.php:3444
Wikimedia\Rdbms\Database\tableLocksHaveTransactionScope
tableLocksHaveTransactionScope()
Checks if table locks acquired by lockTables() are transaction-bound in their scope.
Definition: Database.php:4606
Wikimedia\Rdbms\Database\wasErrorReissuable
wasErrorReissuable()
Determines if the last query error was due to something outside of the query itself.
Definition: Database.php:3337
Wikimedia\Rdbms\Database\deleteJoin
deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
Definition: Database.php:2980
captcha-old.count
count
Definition: captcha-old.py:249
Wikimedia\Rdbms\Database\setIndexAliases
setIndexAliases(array $aliases)
Convert certain index names to alternative names before querying the DB.
Definition: Database.php:4709
Wikimedia\Rdbms\Database\$password
string $password
Password used to establish the current connection.
Definition: Database.php:85
Wikimedia\Rdbms\Database\nativeReplace
nativeReplace( $table, $rows, $fname)
REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE statement.
Definition: Database.php:2906
Wikimedia\Rdbms\Database\decodeBlob
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects.
Definition: Database.php:4334
Wikimedia\Rdbms\Database\unionConditionPermutations
unionConditionPermutations( $table, $vars, array $permute_conds, $extra_conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Construct a UNION query for permutations of conditions.
Definition: Database.php:3234
Wikimedia\Rdbms\Database\maxListLen
maxListLen()
Return the maximum number of items allowed in a list, or 0 for unlimited.
Definition: Database.php:4326
Wikimedia\Rdbms\Database\selectDomain
selectDomain( $domain)
Set the current domain (database, schema, and table prefix)
Definition: Database.php:2394
Wikimedia\Rdbms\Database\buildIntegerCast
buildIntegerCast( $field)
Definition: Database.php:2367
$result
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 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name '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. '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 '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:Array with elements of the form "language:title" in the order that they will be output. & $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 since 1.28! 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:1983
Wikimedia\Rdbms\Database\buildLike
buildLike()
LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match conta...
Definition: Database.php:2778
Wikimedia\Rdbms\Database\getServerUptime
getServerUptime()
Determines how long the server has been up.
Definition: Database.php:3317
Wikimedia\Rdbms\Database\tableNames
tableNames()
Fetch a number of table names into an array This is handy when you need to construct SQL for joins.
Definition: Database.php:2519
Wikimedia\Rdbms\Database\prependDatabaseOrSchema
prependDatabaseOrSchema( $namespace, $relation, $format)
Definition: Database.php:2508
Wikimedia\Rdbms\Database\$delimiter
string $delimiter
Definition: Database.php:136
Wikimedia\Rdbms\Database\__destruct
__destruct()
Run a few simple sanity checks and close dangling connections.
Definition: Database.php:4793
Wikimedia\Rdbms\Database\endAtomic
endAtomic( $fname=__METHOD__)
Ends an atomic section of SQL statements.
Definition: Database.php:3771
Wikimedia\Rdbms\Database\indexName
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
Definition: Database.php:2729
Wikimedia\Rdbms\IDatabase\lastError
lastError()
Get a description of the last error.
DBO_DEBUG
const DBO_DEBUG
Definition: defines.php:9
Wikimedia\Rdbms\Database\setBigSelects
setBigSelects( $value=true)
Allow or deny "big selects" for this session only.
Definition: Database.php:4688
Wikimedia\Rdbms\DBReadOnlyRoleError
Exception class for attempted DB write access to a DBConnRef with the DB_REPLICA role.
Definition: DBReadOnlyRoleError.php:29
$tables
this hook is for auditing only RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition: hooks.txt:979
Wikimedia\Rdbms\DBTransactionStateError
Definition: DBTransactionStateError.php:27
Wikimedia\Rdbms
Definition: ChronologyProtector.php:24
Wikimedia\Rdbms\Database\timestampOrNull
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:4138
Wikimedia\Rdbms\Database\decodeExpiry
decodeExpiry( $expiry, $format=TS_MW)
Decode an expiry time into a DBMS independent format.
Definition: Database.php:4680
Wikimedia\Rdbms\Database\$trxAutomaticAtomic
bool $trxAutomaticAtomic
Record if the current transaction was started implicitly by Database::startAtomic.
Definition: Database.php:219
Wikimedia\Rdbms\Database\initConnection
initConnection()
Initialize the connection to the database over the wire (or to local files)
Definition: Database.php:345
Wikimedia\Rdbms\Database\normalizeConditions
normalizeConditions( $conds, $fname)
Definition: Database.php:1984
Wikimedia\Rdbms\Database\anyString
anyString()
Returns a token for buildLike() that denotes a '' to be used in a LIKE query.
Definition: Database.php:2809
Wikimedia\Rdbms\Database\unionSupportsOrderAndLimit
unionSupportsOrderAndLimit()
Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries within th...
Definition: Database.php:3224
Wikimedia\Rdbms\DBMasterPos
An object representing a master or replica DB position in a replicated setup.
Definition: DBMasterPos.php:12
$params
$params
Definition: styleTest.css.php:44
Wikimedia\Rdbms\Database\isInsertSelectSafe
isInsertSelectSafe(array $insertOptions, array $selectOptions)
Definition: Database.php:3079
Wikimedia\Rdbms\Database\fieldNameWithAlias
fieldNameWithAlias( $name, $alias=false)
Get an aliased field name e.g.
Definition: Database.php:2598
Wikimedia\Rdbms\Database\assertIsWritableMaster
assertIsWritableMaster()
Make sure that this server is not marked as a replica nor read-only as a sanity check.
Definition: Database.php:1033
Wikimedia\Rdbms\Database\getDefaultSchemaVars
getDefaultSchemaVars()
Get schema variables to use if none have been set via setSchemaVars().
Definition: Database.php:4544
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:58
$s
$s
Definition: mergeMessageFileList.php:186
Wikimedia\Rdbms\Database\extractSingleFieldFromList
extractSingleFieldFromList( $var)
Definition: Database.php:2007
Wikimedia\Rdbms\Database\duplicateTableStructure
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Creates a new table with structure copied from existing table.
Definition: Database.php:4118
Wikimedia\Rdbms\Database\onTransactionCommitOrIdle
onTransactionCommitOrIdle(callable $callback, $fname=__METHOD__)
Run a callback as soon as there is no transaction pending.
Definition: Database.php:3427
Wikimedia\Rdbms\Database\upsert
upsert( $table, array $rows, $uniqueIndexes, array $set, $fname=__METHOD__)
INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
Definition: Database.php:2930
$res
$res
Definition: database.txt:21
Wikimedia\Rdbms\Database\isWriteQuery
isWriteQuery( $sql)
Determine whether a query writes to the DB.
Definition: Database.php:1099
Wikimedia\Rdbms\Database\namedLocksEnqueue
namedLocksEnqueue()
Check to see if a named lock used by lock() use blocking queues.
Definition: Database.php:4602
DBO_IGNORE
const DBO_IGNORE
Definition: defines.php:11
Wikimedia\Rdbms\Database\buildSubstring
buildSubstring( $input, $startPosition, $length=null)
Definition: Database.php:2327
Wikimedia\Rdbms\ResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: ResultWrapper.php:24
Wikimedia\Rdbms\Database\setLazyMasterHandle
setLazyMasterHandle(IDatabase $conn)
Set a lazy-connecting DB handle to the master DB (for replication status purposes)
Definition: Database.php:662
Wikimedia\Rdbms\Database\tableNamesWithIndexClauseOrJOIN
tableNamesWithIndexClauseOrJOIN( $tables, $use_index=[], $ignore_index=[], $join_conds=[])
Get the aliased table name clause for a FROM clause which might have a JOIN and/or USE INDEX or IGNOR...
Definition: Database.php:2634
Wikimedia\Rdbms\Database\getReplicaPos
getReplicaPos()
Get the replication position of this replica DB.
Definition: Database.php:3406
Wikimedia\Rdbms\Database\getSchemaVars
getSchemaVars()
Get schema variables.
Definition: Database.php:4528
Wikimedia\Rdbms\Database\setLBInfo
setLBInfo( $name, $value=null)
Set the LB info array, or a member of it.
Definition: Database.php:654
Wikimedia\Rdbms\Database\setLogger
setLogger(LoggerInterface $logger)
Set the PSR-3 logger interface to use for query logging.
Definition: Database.php:569
Wikimedia\Rdbms\Database\__clone
__clone()
Make sure that copies do not share the same client binding handle.
Definition: Database.php:4756
Wikimedia\Rdbms\Database\cancelAtomic
cancelAtomic( $fname=__METHOD__, AtomicSectionIdentifier $sectionId=null)
Cancel an atomic section of SQL statements.
Definition: Database.php:3805
$base
$base
Definition: generateLocalAutoload.php:11
Wikimedia\Rdbms\Database\getFlag
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition: Database.php:835
Wikimedia\Rdbms\Database\conditional
conditional( $cond, $trueVal, $falseVal)
Returns an SQL expression for a simple conditional.
Definition: Database.php:3305
Wikimedia\Rdbms\Database\$trxAtomicLevels
array $trxAtomicLevels
Array of levels of atomicity within transactions.
Definition: Database.php:213
Wikimedia\Rdbms\Database\handleSessionLossPostconnect
handleSessionLossPostconnect()
Clean things up after session (and thus transaction) loss after reconnect.
Definition: Database.php:1511
Wikimedia\Rdbms\Database\$trxStatusIgnoredCause
array null $trxStatusIgnoredCause
If wasKnownStatementRollbackError() prevented trxStatus from being set, the relevant details are stor...
Definition: Database.php:154
Wikimedia\Rdbms\Database\$lastQuery
string $lastQuery
SQL query.
Definition: Database.php:75
DBO_TRX
const DBO_TRX
Definition: defines.php:12
Wikimedia\Rdbms\Database\textFieldSize
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited".
Definition: Database.php:2998
Wikimedia\Rdbms\Database\buildConcat
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.
Definition: Database.php:2315
Wikimedia\Rdbms\Database\deadlockLoop
deadlockLoop()
Perform a deadlock-prone transaction.
Definition: Database.php:3365
Wikimedia\Rdbms\Database\bitNot
bitNot( $field)
Definition: Database.php:2303
Wikimedia\Rdbms\Database\nextSavepointId
nextSavepointId( $fname)
Definition: Database.php:3726
php
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
LIST_AND
const LIST_AND
Definition: Defines.php:43
Wikimedia\Rdbms\Database\$agent
string $agent
Agent name for query profiling.
Definition: Database.php:93
Wikimedia\Rdbms\Database\DEADLOCK_TRIES
const DEADLOCK_TRIES
Number of times to re-try an operation in case of deadlock.
Definition: Database.php:50
Wikimedia\Rdbms\Database\implicitGroupby
implicitGroupby()
Returns true if this database does an implicit sort when doing GROUP BY.
Definition: Database.php:675
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
Wikimedia\Rdbms\Database\__sleep
__sleep()
Called by serialize.
Definition: Database.php:4785
DBO_NOBUFFER
const DBO_NOBUFFER
Definition: defines.php:10
Wikimedia\Rdbms\Database\doBegin
doBegin( $fname)
Issues the BEGIN command to the database server.
Definition: Database.php:3952
Wikimedia\Rdbms\Database\$trxIdleCallbacks
array[] $trxIdleCallbacks
List of (callable, method name, atomic section id)
Definition: Database.php:113
Wikimedia\Rdbms\Database\clearFlag
clearFlag( $flag, $remember=self::REMEMBER_NOTHING)
Clear a flag for this connection.
Definition: Database.php:811
Wikimedia\Rdbms\Database\getQueryExceptionAndLog
getQueryExceptionAndLog( $error, $errno, $sql, $fname)
Definition: Database.php:1569
Wikimedia\Rdbms\Database\begin
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
Definition: Database.php:3894
Wikimedia\Rdbms\IDatabase\fetchObject
fetchObject( $res)
Fetch the next row from the given result object, in object form.
Wikimedia\Rdbms\Database\resultObject
resultObject( $result)
Take the result from a query, and wrap it in a ResultWrapper if necessary.
Definition: Database.php:4170
Wikimedia\Rdbms\Database\$lastWriteTime
float bool $lastWriteTime
UNIX timestamp of last write query.
Definition: Database.php:77
Wikimedia\Rdbms\Database\doQuery
doQuery( $sql)
Run a query and return a DBMS-dependent wrapper or boolean.
Wikimedia\Rdbms\Database\runOnTransactionIdleCallbacks
runOnTransactionIdleCallbacks( $trigger)
Actually consume and run any "on transaction idle/resolution" callbacks.
Definition: Database.php:3562
Wikimedia\Rdbms\Database\makeSelectOptions
makeSelectOptions( $options)
Returns an optional USE INDEX clause to go after the table, and a string to go at the end of the quer...
Definition: Database.php:1658
Wikimedia\Rdbms\Database\$trxStatusCause
Exception null $trxStatusCause
The last error that caused the status to become STATUS_TRX_ERROR.
Definition: Database.php:149
Wikimedia\Rdbms\Database\replaceVars
replaceVars( $ins)
Database independent variable replacement.
Definition: Database.php:4490
$data
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
Definition: generatePhpCharToUpperMappings.php:13
Wikimedia\Rdbms\Database\handleSessionLossPreconnect
handleSessionLossPreconnect()
Clean things up after session (and thus transaction) loss before reconnect.
Definition: Database.php:1483
Wikimedia\Rdbms\Database\$NOT_APPLICABLE
static string $NOT_APPLICABLE
Idiom used when a cancelable atomic section started the transaction.
Definition: Database.php:274
Wikimedia\Rdbms\Database\$trxStatus
int $trxStatus
Transaction status.
Definition: Database.php:145
Wikimedia\Rdbms\Database\runOnTransactionPreCommitCallbacks
runOnTransactionPreCommitCallbacks()
Actually consume and run any "on transaction pre-commit" callbacks.
Definition: Database.php:3623
Wikimedia\Rdbms\Database\wasLockTimeout
wasLockTimeout()
Determines if the last failure was due to a lock timeout.
Definition: Database.php:3325
Wikimedia\Rdbms\Database\$affectedRowCount
integer null $affectedRowCount
Rows affected by the last query to query() or its CRUD wrappers.
Definition: Database.php:140
Wikimedia\Rdbms\Database\$currentDomain
DatabaseDomain $currentDomain
Definition: Database.php:138
LIST_OR
const LIST_OR
Definition: Defines.php:46
Wikimedia\Rdbms\Database\getLag
getLag()
Get the amount of replication lag for this database server.
Definition: Database.php:4322
Wikimedia\Rdbms\Database\modifyCallbacksForCancel
modifyCallbacksForCancel(array $sectionIds)
Definition: Database.php:3507
Wikimedia\Rdbms\Database\$sessionTempTables
array $sessionTempTables
Map of (table name => 1) for TEMPORARY tables.
Definition: Database.php:254
Wikimedia\Rdbms\Database\buildSelectSubquery
buildSelectSubquery( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Equivalent to IDatabase::selectSQLText() except wraps the result in Subqyery.
Definition: Database.php:2371
Wikimedia\Rdbms\Database\rollback
rollback( $fname=__METHOD__, $flush='')
Rollback a transaction previously started using begin().
Definition: Database.php:4031
Wikimedia\Rdbms\Database\assertBuildSubstringParams
assertBuildSubstringParams( $startPosition, $length)
Check type and bounds for parameters to self::buildSubstring()
Definition: Database.php:2348
Wikimedia\Rdbms\Database\isReadOnly
isReadOnly()
Definition: Database.php:4692
user
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 and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same user
Definition: distributors.txt:9
Wikimedia\Rdbms\Database\buildStringCast
buildStringCast( $field)
Definition: Database.php:2361
Wikimedia\Rdbms\Database\$lazyMasterHandle
IDatabase null $lazyMasterHandle
Lazy handle to the master DB this server replicates from.
Definition: Database.php:257
Wikimedia\Rdbms\Database\unlockTables
unlockTables( $method)
Unlock all tables locked via lockTables()
Definition: Database.php:4634
Wikimedia\Rdbms\Database\getSessionLagStatus
getSessionLagStatus()
Get the replica DB lag when the current transaction started or a general lag estimate if not transact...
Definition: Database.php:4250
Wikimedia\Rdbms\Database\nonNativeInsertSelect
nonNativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[], $selectJoinConds=[])
Implementation of insertSelect() based on select() and insert()
Definition: Database.php:3097
Wikimedia\Rdbms\Database\$trxWriteDuration
float $trxWriteDuration
Seconds spent in write queries for the current transaction.
Definition: Database.php:229
Wikimedia\Rdbms\Database\doRollbackToSavepoint
doRollbackToSavepoint( $identifier, $fname)
Rollback to a savepoint.
Definition: Database.php:3718
Wikimedia\Rdbms\Database\startAtomic
startAtomic( $fname=__METHOD__, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Begin an atomic section of SQL statements.
Definition: Database.php:3741
Wikimedia\Rdbms\DBQueryTimeoutError
Error thrown when a query times out.
Definition: DBQueryTimeoutError.php:29
Wikimedia\Rdbms\Database\selectOptionsIncludeLocking
selectOptionsIncludeLocking( $options)
Definition: Database.php:1940
Wikimedia\Rdbms\Database\getCacheSetOptions
static getCacheSetOptions(IDatabase $db1, IDatabase $db2=null)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:4305
Wikimedia\Rdbms\Database\open
open( $server, $user, $password, $dbName, $schema, $tablePrefix)
Open a new connection to the database (closing any existing one)
Wikimedia\Rdbms\Database\lock
lock( $lockName, $method, $timeout=5)
Acquire a named lock.
Definition: Database.php:4555
Wikimedia\Rdbms\Database\getTransactionRoundId
getTransactionRoundId()
Definition: Database.php:715
Wikimedia\Rdbms\Database\trxLevel
trxLevel()
Gets the current transaction level.
Definition: Database.php:588
$input
if(is_array( $mode)) switch( $mode) $input
Definition: postprocess-phan.php:141
Wikimedia\Rdbms\Database\setFlag
setFlag( $flag, $remember=self::REMEMBER_NOTHING)
Set a flag for this connection.
Definition: Database.php:800
$matches
$matches
Definition: NoLocalSettings.php:24
Wikimedia\Rdbms\Database\setTransactionListener
setTransactionListener( $name, callable $callback=null)
Run a callback after each time any transaction commits or rolls back.
Definition: Database.php:3532
Wikimedia\Rdbms\Database\getWikiID
getWikiID()
Alias for getDomainID()
Definition: Database.php:852
Wikimedia\Rdbms\Database\escapeLikeInternal
escapeLikeInternal( $s, $escapeChar='`')
Definition: Database.php:2772
Wikimedia\Rdbms\Database\$tableAliases
array[] $tableAliases
Map of (table => (dbname, schema, prefix) map)
Definition: Database.php:87
Wikimedia\Rdbms\Database\$cliMode
bool $cliMode
Whether this PHP instance is for a CLI script.
Definition: Database.php:91
Wikimedia\Rdbms\Database\$trxShortId
string $trxShortId
Either a short hexidecimal string if a transaction is active or "".
Definition: Database.php:168
Wikimedia\Rdbms\Database\isTransactableQuery
isTransactableQuery( $sql)
Determine whether a SQL statement is sensitive to isolation level.
Definition: Database.php:1140
Wikimedia\Rdbms\Database\lockForUpdate
lockForUpdate( $table, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Lock all rows meeting the given conditions/options FOR UPDATE.
Definition: Database.php:2023
LIST_SET
const LIST_SET
Definition: Defines.php:44
Wikimedia\Rdbms\Database\indexExists
indexExists( $table, $index, $fname=__METHOD__)
Determines whether an index exists Usually throws a DBQueryError on failure If errors are explicitly ...
Definition: Database.php:2075
Wikimedia\Rdbms\Database\writesOrCallbacksPending
writesOrCallbacksPending()
Whether there is a transaction open with either possible write queries or unresolved pre-commit/commi...
Definition: Database.php:699
Wikimedia\Rdbms\Database\sourceStream
sourceStream( $fp, callable $lineCallback=null, callable $resultCallback=null, $fname=__METHOD__, callable $inputCallback=null)
Read and execute commands from an open file handle.
Definition: Database.php:4380
Wikimedia\Rdbms\Database\selectSQLText
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
The equivalent of IDatabase::select() except that the constructed SQL is returned,...
Definition: Database.php:1787
Wikimedia\Rdbms\Database\$trxWriteCallers
string[] $trxWriteCallers
Track the write query callers of the current transaction.
Definition: Database.php:225
Wikimedia\Rdbms\Database\$lastPing
float $lastPing
UNIX timestamp.
Definition: Database.php:260
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
Wikimedia\Rdbms\Database\bitAnd
bitAnd( $fieldLeft, $fieldRight)
Definition: Database.php:2307
Wikimedia\Rdbms\Database\$phpError
string bool $phpError
Definition: Database.php:79
Wikimedia\Rdbms\Database\$trxProfiler
TransactionProfiler $trxProfiler
Definition: Database.php:268
Wikimedia\Rdbms\Database\$user
string $user
User that this instance is currently connected under the name of.
Definition: Database.php:83
Wikimedia\Rdbms\Database\SMALL_WRITE_ROWS
const SMALL_WRITE_ROWS
Definition: Database.php:62
Wikimedia\Rdbms\Database\assertNoOpenTransactions
assertNoOpenTransactions()
Assert that all explicit transactions or atomic sections have been closed.
Definition: Database.php:1439
Wikimedia\Rdbms\Database\selectDB
selectDB( $db)
Change the current database.
Definition: Database.php:2384
Wikimedia\Rdbms\Database\reportConnectionError
reportConnectionError( $error='Unknown error')
Definition: Database.php:1058
Wikimedia\Rdbms\Database\wasQueryTimeout
wasQueryTimeout( $error, $errno)
Checks whether the cause of the error is detected to be a timeout.
Definition: Database.php:1537
Wikimedia\Rdbms\Database\closeConnection
closeConnection()
Closes underlying database connection.
Wikimedia\Rdbms\Database\lastDoneWrites
lastDoneWrites()
Returns the last time the connection may have been used for write queries.
Definition: Database.php:691
$vars
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2220
Wikimedia\Rdbms\Database\dropTable
dropTable( $tableName, $fName=__METHOD__)
Delete a table.
Definition: Database.php:4661
Wikimedia\Rdbms\Database\wasReadOnlyError
wasReadOnlyError()
Determines if the last failure was due to the database being read-only.
Definition: Database.php:3333
Wikimedia\Rdbms\Database\beginIfImplied
beginIfImplied( $sql, $fname)
Start an implicit transaction if DBO_TRX is enabled and no transaction is active.
Definition: Database.php:1360
Wikimedia\Rdbms\Database\getServerInfo
getServerInfo()
A string describing the current software version, and possibly other details in a user-friendly way.
Definition: Database.php:573
Wikimedia\Rdbms\Database\commit
commit( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Commits a transaction previously started using begin().
Definition: Database.php:3957
Wikimedia\Rdbms\Database\restoreFlags
restoreFlags( $state=self::RESTORE_PRIOR)
Restore the flags to their prior state before the last setFlag/clearFlag call.
Definition: Database.php:822
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
Wikimedia\Rdbms\Database\reportQueryError
reportQueryError( $error, $errno, $sql, $fname, $ignoreErrors=false)
Report a query error.
Definition: Database.php:1552
Wikimedia\Rdbms\Database\connectionErrorLogger
connectionErrorLogger( $errno, $errstr)
Error handler for logging errors during database connection This method should not be used outside of...
Definition: Database.php:917
Wikimedia\Rdbms\Database\getScopedLockAndFlush
getScopedLockAndFlush( $lockKey, $fname, $timeout)
Acquire a named lock, flush any transaction, and return an RAII style unlocker object.
Definition: Database.php:4567
Wikimedia\Rdbms\Database\fieldNamesWithAlias
fieldNamesWithAlias( $fields)
Gets an array of aliased field names.
Definition: Database.php:2612
string
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:175
Wikimedia\Rdbms\Database\getLBInfo
getLBInfo( $name=null)
Get properties passed down from the server info array of the load balancer.
Definition: Database.php:642
Wikimedia\Rdbms\DBReadOnlyError
Definition: DBReadOnlyError.php:27
Wikimedia\Rdbms\Database\$trxWriteAffectedRows
int $trxWriteAffectedRows
Number of rows affected by write queries for the current transaction.
Definition: Database.php:237
Wikimedia\Rdbms\Database\$trxWriteAdjDuration
float $trxWriteAdjDuration
Like trxWriteQueryCount but excludes lock-bound, easy to replicate, queries.
Definition: Database.php:241
list
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
Wikimedia\Rdbms\Database\insertSelect
insertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[], $selectJoinConds=[])
INSERT SELECT wrapper.
Definition: Database.php:3035
Wikimedia\Rdbms\Database\makeInsertOptions
makeInsertOptions( $options)
Helper for Database::insert().
Definition: Database.php:2106
Wikimedia\Rdbms\Database\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Single row SELECT wrapper.
Definition: Database.php:1870
Wikimedia\Rdbms\DBQueryError
Definition: DBQueryError.php:27
Wikimedia\Rdbms\Database\useIndexClause
useIndexClause( $index)
USE INDEX clause.
Definition: Database.php:2827
Wikimedia\Rdbms\Database\makeUpdateOptions
makeUpdateOptions( $options)
Make UPDATE options for the Database::update function.
Definition: Database.php:2180
LIST_COMMA
const LIST_COMMA
Definition: Defines.php:42
Wikimedia\Rdbms\Database\getReadOnlyReason
getReadOnlyReason()
Definition: Database.php:4699
$fname
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition: Setup.php:123
Wikimedia\Rdbms\Database\$priorFlags
int[] $priorFlags
Prior flags member variable values.
Definition: Database.php:263
Wikimedia\Rdbms\Database\installErrorHandler
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition: Database.php:876
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
Wikimedia\Rdbms\Database\aggregateValue
aggregateValue( $valuedata, $valuename='value')
Return aggregated value alias.
Definition: Database.php:2299
Wikimedia\Rdbms\Database\restoreErrorHandler
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition: Database.php:887
Wikimedia\Rdbms\Database\preCommitCallbacksPending
preCommitCallbacksPending()
Definition: Database.php:708
key
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message key
Definition: hooks.txt:2154
$line
$line
Definition: cdb.php:59
Wikimedia\Rdbms\Database\replace
replace( $table, $uniqueIndexes, $rows, $fname=__METHOD__)
REPLACE query wrapper.
Definition: Database.php:2845
Wikimedia\Rdbms\Database\wasKnownStatementRollbackError
wasKnownStatementRollbackError()
Definition: Database.php:3361
Wikimedia\Rdbms\Database\pendingWriteAndCallbackCallers
pendingWriteAndCallbackCallers()
List the methods that have write queries or callbacks for the current transaction.
Definition: Database.php:772
Wikimedia\Rdbms\Database\$trxPreCommitCallbacks
array[] $trxPreCommitCallbacks
List of (callable, method name, atomic section id)
Definition: Database.php:115
Wikimedia\Rdbms\Database\$htmlErrors
string bool null $htmlErrors
Stashed value of html_errors INI setting.
Definition: Database.php:134
Wikimedia\Rdbms\Database\tableExists
tableExists( $table, $fname=__METHOD__)
Query whether a given table exists.
Wikimedia\Rdbms\Database\$SAVEPOINT_PREFIX
static string $SAVEPOINT_PREFIX
Prefix to the atomic section counter used to make savepoint IDs.
Definition: Database.php:276
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2162
Wikimedia\Rdbms\Database\pendingWriteCallers
pendingWriteCallers()
Get the list of method names that did write queries for this transaction.
Definition: Database.php:756
Wikimedia\Rdbms\Database\$trxEndCallbacks
array[] $trxEndCallbacks
List of (callable, method name, atomic section id)
Definition: Database.php:117
Wikimedia\Rdbms\Database\tableNamesN
tableNamesN()
Fetch a number of table names into an zero-indexed numerical array This is handy when you need to con...
Definition: Database.php:2530
$value
$value
Definition: styleTest.css.php:49
Wikimedia\Rdbms\Database\$opened
bool $opened
Definition: Database.php:110
Wikimedia\Rdbms\Database\__toString
__toString()
Definition: Database.php:4748
Wikimedia\Rdbms\Database\getLogContext
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition: Database.php:927
Wikimedia\Rdbms\Database\$connectionParams
array $connectionParams
Parameters used by initConnection() to establish a connection.
Definition: Database.php:95
Wikimedia\Rdbms\Database\$trxAutomatic
bool $trxAutomatic
Record if the current transaction was started implicitly due to DBO_TRX being set.
Definition: Database.php:201
Wikimedia\Rdbms\Database\$namedLocksHeld
array $namedLocksHeld
Map of (name => 1) for locks obtained via lock()
Definition: Database.php:252
Wikimedia\Rdbms\Database\pendingWriteRowsAffected
pendingWriteRowsAffected()
Get the number of affected rows from pending write queries.
Definition: Database.php:760
Wikimedia\Rdbms\Database\doneWrites
doneWrites()
Returns true if the connection may have been used for write queries.
Definition: Database.php:687
Wikimedia\Rdbms\Database\$trxWriteQueryCount
int $trxWriteQueryCount
Number of write queries for the current transaction.
Definition: Database.php:233
Wikimedia\Rdbms\AtomicSectionIdentifier
Class used for token representing identifiers for atomic sections from IDatabase instances.
Definition: AtomicSectionIdentifier.php:26
Wikimedia\Rdbms\Database\doReleaseSavepoint
doReleaseSavepoint( $identifier, $fname)
Release a savepoint.
Definition: Database.php:3704
Wikimedia\Rdbms\Database\currentAtomicSectionId
currentAtomicSectionId()
Definition: Database.php:3469
Wikimedia\Rdbms\Database\setTrxEndCallbackSuppression
setTrxEndCallbackSuppression( $suppress)
Whether to disable running of post-COMMIT/ROLLBACK callbacks.
Definition: Database.php:3548
Wikimedia\Rdbms\IDatabase\fetchRow
fetchRow( $res)
Fetch the next row from the given result object, in associative array form.
Wikimedia\Rdbms\Database\getMasterPos
getMasterPos()
Get the position of this master.
Definition: Database.php:3411
Wikimedia\Rdbms\Database\DEADLOCK_DELAY_MIN
const DEADLOCK_DELAY_MIN
Minimum time to wait before retry, in microseconds.
Definition: Database.php:52
Wikimedia\Rdbms\Database\hasFlags
hasFlags( $field, $flags)
Definition: Database.php:4718
Wikimedia\Rdbms\Database\pendingWriteQueryDuration
pendingWriteQueryDuration( $type=self::ESTIMATE_TOTAL)
Get the time spend running write queries for this transaction.
Definition: Database.php:726
Wikimedia\Rdbms\Database\$indexAliases
string[] $indexAliases
Map of (index alias => index)
Definition: Database.php:89
$ret
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1985
Wikimedia\Rdbms\LikeMatch
Used by Database::buildLike() to represent characters that have special meaning in SQL LIKE clauses a...
Definition: LikeMatch.php:10
Wikimedia\Rdbms\Database\tableName
tableName( $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.
Definition: Database.php:2410
Wikimedia\Rdbms\Database\wasConnectionError
wasConnectionError( $errno)
Do not use this method outside of Database/DBError classes.
Definition: Database.php:3351
Wikimedia\Rdbms\Database\doAtomicSection
doAtomicSection( $fname, callable $callback, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Perform an atomic section of reversable SQL statements from a callback.
Definition: Database.php:3878
Wikimedia\Rdbms\Database\getRecordedTransactionLagStatus
getRecordedTransactionLagStatus()
Get the replica DB lag when the current transaction started.
Definition: Database.php:4267
Wikimedia\Rdbms\Database\strencode
strencode( $s)
Wrapper for addslashes()
Wikimedia\Rdbms\Database\getServer
getServer()
Get the server hostname or IP address.
Definition: Database.php:2406
Wikimedia\Rdbms\DBUnexpectedError
Definition: DBUnexpectedError.php:27
Wikimedia\Rdbms\Database\doLockTables
doLockTables(array $read, array $write, $method)
Helper function for lockTables() that handles the actual table locking.
Definition: Database.php:4630
Wikimedia\Rdbms\Database\doUnlockTables
doUnlockTables( $method)
Helper function for unlockTables() that handles the actual table unlocking.
Definition: Database.php:4650
Wikimedia\Rdbms\Database\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:4132
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:1596
Wikimedia\Rdbms\Database\bufferResults
bufferResults( $buffer=null)
Turns buffering of SQL result sets on (true) or off (false).
Definition: Database.php:577
Wikimedia\Rdbms\Database\makeList
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Definition: Database.php:2200
Wikimedia\Rdbms\Database\anyChar
anyChar()
Returns a token for buildLike() that denotes a '_' to be used in a LIKE query.
Definition: Database.php:2805
Wikimedia\Rdbms\Database\onTransactionIdle
onTransactionIdle(callable $callback, $fname=__METHOD__)
Alias for onTransactionCommitOrIdle() for backwards-compatibility.
Definition: Database.php:3440
Wikimedia\Rdbms\Database\unlock
unlock( $lockName, $method)
Release a lock.
Definition: Database.php:4561
Wikimedia\Rdbms\Database\$profiler
callable null $profiler
Definition: Database.php:266
Wikimedia\Rdbms\Database\DEADLOCK_DELAY_MAX
const DEADLOCK_DELAY_MAX
Maximum time to wait before retry.
Definition: Database.php:54
Wikimedia\Rdbms\Database\assertTransactionStatus
assertTransactionStatus( $sql, $fname)
Error out if the DB is not in a valid state for a query via query()
Definition: Database.php:1412
Wikimedia\Rdbms\Database\addIdentifierQuotes
addIdentifierQuotes( $s)
Quotes an identifier, in order to make user controlled input safe.
Definition: Database.php:2750
Wikimedia\Rdbms\Database\getLazyMasterHandle
getLazyMasterHandle()
Definition: Database.php:671
Wikimedia\Rdbms\Database\indexUnique
indexUnique( $table, $index)
Determines if a given index is unique.
Definition: Database.php:2090
$args
if( $line===false) $args
Definition: cdb.php:64
Wikimedia\Rdbms\Database\$preparedArgs
array null $preparedArgs
Definition: Database.php:132
Wikimedia\Rdbms\Database\dbSchema
dbSchema( $schema=null)
Get/set the db schema.
Definition: Database.php:617
Wikimedia\Rdbms\Database\$trxRecurringCallbacks
callable[] $trxRecurringCallbacks
Map of (name => callable)
Definition: Database.php:119
Wikimedia\Rdbms\Database\close
close()
Close the database connection.
Definition: Database.php:938
Wikimedia\Rdbms\Database\getQueryVerb
getQueryVerb( $sql)
Definition: Database.php:1123
Wikimedia\Rdbms\Database\unionQueries
unionQueries( $sqls, $all)
Construct a UNION query This is used for providing overload point for other DB abstractions not compa...
Definition: Database.php:3228
Wikimedia\Rdbms\DBTransactionError
Definition: DBTransactionError.php:27
Wikimedia\Rdbms\Database\reassignCallbacksForSection
reassignCallbacksForSection(AtomicSectionIdentifier $old, AtomicSectionIdentifier $new)
Definition: Database.php:3483
Wikimedia\Rdbms\IDatabase\lastErrno
lastErrno()
Get the last error number.
Wikimedia\Rdbms\Database\relationSchemaQualifier
relationSchemaQualifier()
Definition: Database.php:638
Wikimedia\Rdbms\Database\PING_QUERY
const PING_QUERY
Definition: Database.php:58
Wikimedia\Rdbms\Database\lockTables
lockTables(array $read, array $write, $method)
Lock specific tables.
Definition: Database.php:4610
Wikimedia\Rdbms\Database\$trxAtomicCounter
int $trxAtomicCounter
Counter for atomic savepoint identifiers.
Definition: Database.php:207
$rows
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2636
Wikimedia\Rdbms\Database\encodeExpiry
encodeExpiry( $expiry)
Encode an expiry time into the DBMS dependent format.
Definition: Database.php:4674
Wikimedia\Rdbms\Database\$srvCache
BagOStuff $srvCache
APC cache.
Definition: Database.php:97
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1985
Wikimedia\Rdbms\Database\registerTempTableWrite
registerTempTableWrite( $sql, $pseudoPermanent)
Definition: Database.php:1153
Wikimedia\Rdbms\Database\doSavepoint
doSavepoint( $identifier, $fname)
Create a savepoint.
Definition: Database.php:3690
Wikimedia\Rdbms\Database\setSchemaVars
setSchemaVars( $vars)
Set variables to be used in sourceFile/sourceStream, in preference to the ones in $GLOBALS.
Definition: Database.php:4376
Wikimedia\Rdbms\Database\__construct
__construct(array $params)
Definition: Database.php:294
Wikimedia\Rdbms\Database\trxTimestamp
trxTimestamp()
Get the UNIX timestamp of the time that the transaction was established.
Definition: Database.php:592
Wikimedia\Rdbms\Database\$trxTimestamp
float null $trxTimestamp
The UNIX time that the transaction started.
Definition: Database.php:177
Wikimedia\Rdbms\Database\flushSnapshot
flushSnapshot( $fname=__METHOD__)
Commit any transaction but error out if writes or callbacks are pending.
Definition: Database.php:4101
Wikimedia\Rdbms\Database\makeGroupByWithHaving
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1741
Wikimedia\Rdbms\Database\TINY_WRITE_SEC
const TINY_WRITE_SEC
Definition: Database.php:60
Wikimedia\Rdbms\Database\tableNameWithAlias
tableNameWithAlias( $table, $alias=false)
Get an aliased table name.
Definition: Database.php:2552
Wikimedia\Rdbms\Database\getAttributes
static getAttributes()
Definition: Database.php:558
Wikimedia\Rdbms\Database\writesPending
writesPending()
Definition: Database.php:695
as
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
Wikimedia\Rdbms\Database\selectFieldsOrOptionsAggregate
selectFieldsOrOptionsAggregate( $fields, $options)
Definition: Database.php:1956
Wikimedia\Rdbms\Database\lockIsFree
lockIsFree( $lockName, $method)
Check to see if a named lock is not locked by any thread (non-blocking)
Definition: Database.php:4548
Wikimedia\Rdbms\Database\ping
ping(&$rtt=null)
Ping the server and try to reconnect if it there is no connection.
Definition: Database.php:4183
Wikimedia\Rdbms\Database\$nonNativeInsertSelectBatchSize
int $nonNativeInsertSelectBatchSize
Definition: Database.php:271
Wikimedia\Rdbms\Database\runTransactionListenerCallbacks
runTransactionListenerCallbacks( $trigger)
Actually run any "transaction listener" callbacks.
Definition: Database.php:3658
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
Wikimedia
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...
Wikimedia\Rdbms\Database\setTableAliases
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
Definition: Database.php:4705
Wikimedia\Rdbms\Database\masterPosWait
masterPosWait(DBMasterPos $pos, $timeout)
Wait for the replica DB to catch up to a given master position.
Definition: Database.php:3401
Wikimedia\Rdbms\Database\onTransactionResolution
onTransactionResolution(callable $callback, $fname=__METHOD__)
Run a callback as soon as the current transaction commits or rolls back.
Definition: Database.php:3420
Wikimedia\Rdbms\Database\explicitTrxActive
explicitTrxActive()
Definition: Database.php:4114
Wikimedia\Rdbms\Database\$server
string $server
Server that this instance is currently connected to.
Definition: Database.php:81
$keys
$keys
Definition: testCompression.php:67
Wikimedia\Rdbms\Database\freeResult
freeResult( $res)
Free a result object returned by query() or select().
Definition: Database.php:1593
Wikimedia\Rdbms\Database\$schemaVars
array bool $schemaVars
Definition: Database.php:128
Wikimedia\Rdbms\Database\isOpen
isOpen()
Is a connection to the database open?
Definition: Database.php:796
Wikimedia\Rdbms\Database\doCommit
doCommit( $fname)
Issues the COMMIT command to the database server.
Definition: Database.php:4024
Wikimedia\Rdbms\IMaintainableDatabase\fieldInfo
fieldInfo( $table, $field)
mysql_fetch_field() wrapper Returns false if the field doesn't exist
Wikimedia\Rdbms\Database\attemptQuery
attemptQuery( $sql, $commentedSql, $isEffectiveWrite, $fname)
Wrapper for query() that also handles profiling, logging, and affected row count updates.
Definition: Database.php:1290
Wikimedia\Rdbms\Database\makeOrderBy
makeOrderBy( $options)
Returns an optional ORDER BY.
Definition: Database.php:1767
Wikimedia\Rdbms\Database\$flags
int $flags
Definition: Database.php:124
Wikimedia\Rdbms\Database\serverIsReadOnly
serverIsReadOnly()
Definition: Database.php:3416
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:1779
Wikimedia\Rdbms\Database\attributesFromType
static attributesFromType( $dbType, $driver=null)
Definition: Database.php:489
Wikimedia\Rdbms\Database\lastQuery
lastQuery()
Return the last query that went through IDatabase::query()
Definition: Database.php:683
Wikimedia\Rdbms\Database\wasConnectionLoss
wasConnectionLoss()
Determines if the last query error was due to a dropped connection.
Definition: Database.php:3329
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
Wikimedia\Rdbms\Database\wasDeadlock
wasDeadlock()
Determines if the last failure was due to a deadlock.
Definition: Database.php:3321
$t
$t
Definition: testCompression.php:69
Wikimedia\Rdbms\Database\doSelectDomain
doSelectDomain(DatabaseDomain $domain)
Definition: Database.php:2398
Wikimedia\Rdbms\Database\$connLogger
LoggerInterface $connLogger
Definition: Database.php:99
Wikimedia\Rdbms\Database\update
update( $table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
Definition: Database.php:2186
Wikimedia\Rdbms\Database\addQuotes
addQuotes( $s)
Adds quotes and backslashes.
Definition: Database.php:2733
LIST_NAMES
const LIST_NAMES
Definition: Defines.php:45
Wikimedia\Rdbms\Database\getApproximateLagStatus
getApproximateLagStatus()
Get a replica DB lag estimate for this server.
Definition: Database.php:4279
Wikimedia\Rdbms\Database\qualifiedTableComponents
qualifiedTableComponents( $name)
Get the table components needed for a query given the currently selected database.
Definition: Database.php:2468
Wikimedia\Rdbms\Database\affectedRows
affectedRows()
Get the number of rows affected by the last write query.
Definition: Database.php:4146
Wikimedia\Rdbms\Database\$sessionVars
array $sessionVars
Definition: Database.php:130
Wikimedia\Rdbms\Database\fetchAffectedRowCount
fetchAffectedRowCount()
Wikimedia\Rdbms\DatabaseDomain
Class to handle database/prefix specification for IDatabase domains.
Definition: DatabaseDomain.php:28
Wikimedia\Rdbms\Database\databasesAreIndependent
databasesAreIndependent()
Returns true if DBs are assumed to be on potentially different servers.
Definition: Database.php:2380
Wikimedia\Rdbms\TransactionProfiler
Helper class that detects high-contention DB queries via profiling calls.
Definition: TransactionProfiler.php:38
Wikimedia\Rdbms\Database\indexInfo
indexInfo( $table, $index, $fname=__METHOD__)
Get information about an index into an object.
Wikimedia\Rdbms\Database\strreplace
strreplace( $orig, $old, $new)
Returns a command for str_replace function in SQL query.
Definition: Database.php:3313
Wikimedia\Rdbms\Database\sourceFile
sourceFile( $filename, callable $lineCallback=null, callable $resultCallback=null, $fname=false, callable $inputCallback=null)
Read and execute SQL commands from a file.
Definition: Database.php:4344
DBO_DEFAULT
const DBO_DEFAULT
Definition: defines.php:13
Wikimedia\Rdbms\Database\listViews
listViews( $prefix=null, $fname=__METHOD__)
Lists all the VIEWs in the database.
Definition: Database.php:4128
Wikimedia\Rdbms\Database\streamStatementEnd
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
Definition: Database.php:4457
server
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 and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of and they certainly aren t ideal for someone who s installing MediaWiki as MediaWiki does not conform to normal Unix filesystem layout Hopefully we ll offer direct support for standard layouts in the but for now *any change to the location of files is unsupported *Moving things and leaving symlinks will *probably *not break but it is *strongly *advised not to try any more intrusive changes to get MediaWiki to conform more closely to your filesystem hierarchy Any such attempt will almost certainly result in unnecessary bugs The standard recommended location to install relative to the web is it should be possible to enable the appropriate rewrite rules by if you can reconfigure the web server
Definition: distributors.txt:53
Wikimedia\Rdbms\Database\nativeInsertSelect
nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[], $selectJoinConds=[])
Native server-side implementation of insertSelect() for situations where we don't want to select ever...
Definition: Database.php:3166
Wikimedia\Rdbms\IMaintainableDatabase
Advanced database interface for IDatabase handles that include maintenance methods.
Definition: IMaintainableDatabase.php:38
Wikimedia\Rdbms\Database\query
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query and return the result.
Definition: Database.php:1191
Wikimedia\Rdbms\Database\SLOW_WRITE_SEC
const SLOW_WRITE_SEC
Definition: Database.php:61
Wikimedia\Rdbms\Database\tableNamesWithAlias
tableNamesWithAlias( $tables)
Gets an array of aliased table names.
Definition: Database.php:2578
Wikimedia\Rdbms\Database\nextSequenceValue
nextSequenceValue( $seqName)
Deprecated method, calls should be removed.
Definition: Database.php:2813
Wikimedia\Rdbms\Database\generalizeSQL
static generalizeSQL( $sql)
Removes most variables from an SQL query and replaces them with X or N for numbers.
Definition: Database.php:2047
Wikimedia\Rdbms\Subquery
Definition: Subquery.php:27
Wikimedia\Rdbms\Database\$rttEstimate
float $rttEstimate
RTT time estimate.
Definition: Database.php:249
Wikimedia\Rdbms\Database\selectFieldValues
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.
Definition: Database.php:1623
Wikimedia\Rdbms\Database\flatAtomicSectionList
flatAtomicSectionList()
Definition: Database.php:790
$buffer
$buffer
Definition: mwdoc-filter.php:49
Wikimedia\Rdbms\Database\$deprecationLogger
callable $deprecationLogger
Deprecation logging callback.
Definition: Database.php:105
Wikimedia\Rdbms\Database\$errorLogger
callable $errorLogger
Error logging callback.
Definition: Database.php:103
Wikimedia\Rdbms\Database\$conn
object resource null $conn
Database connection.
Definition: Database.php:108
Wikimedia\Rdbms\Database\doRollback
doRollback( $fname)
Issues the ROLLBACK command to the database server.
Definition: Database.php:4092
Wikimedia\Rdbms\Database\$lbInfo
array $lbInfo
Definition: Database.php:126
Wikimedia\Rdbms\Database\getDBname
getDBname()
Get the current DB name.
Definition: Database.php:2402
Wikimedia\Rdbms\Blob
Definition: Blob.php:5
Wikimedia\Rdbms\Database\getInfinity
getInfinity()
Find out when 'infinity' is.
Definition: Database.php:4670
$type
$type
Definition: testCompression.php:48