MediaWiki  1.31.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';
66 
68  const NEW_UNCONNECTED = 0;
70  const NEW_CONNECTED = 1;
71 
73  protected $lastQuery = '';
75  protected $lastWriteTime = false;
77  protected $phpError = false;
79  protected $server;
81  protected $user;
83  protected $password;
85  protected $dbName;
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 $tablePrefix = '';
126  protected $schema = '';
128  protected $flags;
130  protected $lbInfo = [];
132  protected $schemaVars = false;
134  protected $sessionVars = [];
136  protected $preparedArgs;
138  protected $htmlErrors;
140  protected $delimiter = ';';
142  protected $currentDomain;
144  protected $affectedRowCount;
145 
149  protected $trxStatus = self::STATUS_TRX_NONE;
153  protected $trxStatusCause;
165  protected $trxLevel = 0;
172  protected $trxShortId = '';
181  private $trxTimestamp = null;
183  private $trxReplicaLag = null;
191  private $trxFname = null;
198  private $trxDoneWrites = false;
205  private $trxAutomatic = false;
211  private $trxAtomicCounter = 0;
217  private $trxAtomicLevels = [];
223  private $trxAutomaticAtomic = false;
229  private $trxWriteCallers = [];
233  private $trxWriteDuration = 0.0;
237  private $trxWriteQueryCount = 0;
245  private $trxWriteAdjDuration = 0.0;
253  private $rttEstimate = 0.0;
254 
256  private $namedLocksHeld = [];
258  protected $sessionTempTables = [];
259 
262 
264  protected $lastPing = 0.0;
265 
267  private $priorFlags = [];
268 
270  protected $profiler;
272  protected $trxProfiler;
273 
276 
278  private static $NOT_APPLICABLE = 'n/a';
280  private static $SAVEPOINT_PREFIX = 'wikimedia_rdbms_atomic';
281 
283  const STATUS_TRX_ERROR = 1;
285  const STATUS_TRX_OK = 2;
287  const STATUS_TRX_NONE = 3;
288 
293  protected function __construct( array $params ) {
294  foreach ( [ 'host', 'user', 'password', 'dbname' ] as $name ) {
295  $this->connectionParams[$name] = $params[$name];
296  }
297 
298  $this->schema = $params['schema'];
299  $this->tablePrefix = $params['tablePrefix'];
300 
301  $this->cliMode = $params['cliMode'];
302  // Agent name is added to SQL queries in a comment, so make sure it can't break out
303  $this->agent = str_replace( '/', '-', $params['agent'] );
304 
305  $this->flags = $params['flags'];
306  if ( $this->flags & self::DBO_DEFAULT ) {
307  if ( $this->cliMode ) {
308  $this->flags &= ~self::DBO_TRX;
309  } else {
310  $this->flags |= self::DBO_TRX;
311  }
312  }
313  // Disregard deprecated DBO_IGNORE flag (T189999)
314  $this->flags &= ~self::DBO_IGNORE;
315 
316  $this->sessionVars = $params['variables'];
317 
318  $this->srvCache = isset( $params['srvCache'] )
319  ? $params['srvCache']
320  : new HashBagOStuff();
321 
322  $this->profiler = $params['profiler'];
323  $this->trxProfiler = $params['trxProfiler'];
324  $this->connLogger = $params['connLogger'];
325  $this->queryLogger = $params['queryLogger'];
326  $this->errorLogger = $params['errorLogger'];
327  $this->deprecationLogger = $params['deprecationLogger'];
328 
329  if ( isset( $params['nonNativeInsertSelectBatchSize'] ) ) {
330  $this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'];
331  }
332 
333  // Set initial dummy domain until open() sets the final DB/prefix
334  $this->currentDomain = DatabaseDomain::newUnspecified();
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  // Set the domain object after open() sets the relevant fields
352  if ( $this->dbName != '' ) {
353  // Domains with server scope but a table prefix are not used by IDatabase classes
354  $this->currentDomain = new DatabaseDomain( $this->dbName, null, $this->tablePrefix );
355  }
356  }
357 
365  protected function doInitConnection() {
366  if ( strlen( $this->connectionParams['user'] ) ) {
367  $this->open(
368  $this->connectionParams['host'],
369  $this->connectionParams['user'],
370  $this->connectionParams['password'],
371  $this->connectionParams['dbname']
372  );
373  } else {
374  throw new InvalidArgumentException( "No database user provided." );
375  }
376  }
377 
422  final public static function factory( $dbType, $p = [], $connect = self::NEW_CONNECTED ) {
423  $class = self::getClass( $dbType, isset( $p['driver'] ) ? $p['driver'] : null );
424 
425  if ( class_exists( $class ) && is_subclass_of( $class, IDatabase::class ) ) {
426  // Resolve some defaults for b/c
427  $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
428  $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
429  $p['password'] = isset( $p['password'] ) ? $p['password'] : false;
430  $p['dbname'] = isset( $p['dbname'] ) ? $p['dbname'] : false;
431  $p['flags'] = isset( $p['flags'] ) ? $p['flags'] : 0;
432  $p['variables'] = isset( $p['variables'] ) ? $p['variables'] : [];
433  $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : '';
434  $p['schema'] = isset( $p['schema'] ) ? $p['schema'] : '';
435  $p['cliMode'] = isset( $p['cliMode'] )
436  ? $p['cliMode']
437  : ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
438  $p['agent'] = isset( $p['agent'] ) ? $p['agent'] : '';
439  if ( !isset( $p['connLogger'] ) ) {
440  $p['connLogger'] = new NullLogger();
441  }
442  if ( !isset( $p['queryLogger'] ) ) {
443  $p['queryLogger'] = new NullLogger();
444  }
445  $p['profiler'] = isset( $p['profiler'] ) ? $p['profiler'] : null;
446  if ( !isset( $p['trxProfiler'] ) ) {
447  $p['trxProfiler'] = new TransactionProfiler();
448  }
449  if ( !isset( $p['errorLogger'] ) ) {
450  $p['errorLogger'] = function ( Exception $e ) {
451  trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
452  };
453  }
454  if ( !isset( $p['deprecationLogger'] ) ) {
455  $p['deprecationLogger'] = function ( $msg ) {
456  trigger_error( $msg, E_USER_DEPRECATED );
457  };
458  }
459 
461  $conn = new $class( $p );
462  if ( $connect == self::NEW_CONNECTED ) {
463  $conn->initConnection();
464  }
465  } else {
466  $conn = null;
467  }
468 
469  return $conn;
470  }
471 
479  final public static function attributesFromType( $dbType, $driver = null ) {
480  static $defaults = [ self::ATTR_DB_LEVEL_LOCKING => false ];
481 
482  $class = self::getClass( $dbType, $driver );
483 
484  return call_user_func( [ $class, 'getAttributes' ] ) + $defaults;
485  }
486 
493  private static function getClass( $dbType, $driver = null ) {
494  // For database types with built-in support, the below maps type to IDatabase
495  // implementations. For types with multipe driver implementations (PHP extensions),
496  // an array can be used, keyed by extension name. In case of an array, the
497  // optional 'driver' parameter can be used to force a specific driver. Otherwise,
498  // we auto-detect the first available driver. For types without built-in support,
499  // an class named "Database<Type>" us used, eg. DatabaseFoo for type 'foo'.
500  static $builtinTypes = [
501  'mssql' => DatabaseMssql::class,
502  'mysql' => [ 'mysqli' => DatabaseMysqli::class ],
503  'sqlite' => DatabaseSqlite::class,
504  'postgres' => DatabasePostgres::class,
505  ];
506 
507  $dbType = strtolower( $dbType );
508  $class = false;
509 
510  if ( isset( $builtinTypes[$dbType] ) ) {
511  $possibleDrivers = $builtinTypes[$dbType];
512  if ( is_string( $possibleDrivers ) ) {
513  $class = $possibleDrivers;
514  } else {
515  if ( (string)$driver !== '' ) {
516  if ( !isset( $possibleDrivers[$driver] ) ) {
517  throw new InvalidArgumentException( __METHOD__ .
518  " type '$dbType' does not support driver '{$driver}'" );
519  } else {
520  $class = $possibleDrivers[$driver];
521  }
522  } else {
523  foreach ( $possibleDrivers as $posDriver => $possibleClass ) {
524  if ( extension_loaded( $posDriver ) ) {
525  $class = $possibleClass;
526  break;
527  }
528  }
529  }
530  }
531  } else {
532  $class = 'Database' . ucfirst( $dbType );
533  }
534 
535  if ( $class === false ) {
536  throw new InvalidArgumentException( __METHOD__ .
537  " no viable database extension found for type '$dbType'" );
538  }
539 
540  return $class;
541  }
542 
547  protected static function getAttributes() {
548  return [];
549  }
550 
558  public function setLogger( LoggerInterface $logger ) {
559  $this->queryLogger = $logger;
560  }
561 
562  public function getServerInfo() {
563  return $this->getServerVersion();
564  }
565 
566  public function bufferResults( $buffer = null ) {
567  $res = !$this->getFlag( self::DBO_NOBUFFER );
568  if ( $buffer !== null ) {
569  $buffer
570  ? $this->clearFlag( self::DBO_NOBUFFER )
571  : $this->setFlag( self::DBO_NOBUFFER );
572  }
573 
574  return $res;
575  }
576 
577  public function trxLevel() {
578  return $this->trxLevel;
579  }
580 
581  public function trxTimestamp() {
582  return $this->trxLevel ? $this->trxTimestamp : null;
583  }
584 
589  public function trxStatus() {
590  return $this->trxStatus;
591  }
592 
593  public function tablePrefix( $prefix = null ) {
594  $old = $this->tablePrefix;
595  if ( $prefix !== null ) {
596  $this->tablePrefix = $prefix;
597  $this->currentDomain = ( $this->dbName != '' )
598  ? new DatabaseDomain( $this->dbName, null, $this->tablePrefix )
600  }
601 
602  return $old;
603  }
604 
605  public function dbSchema( $schema = null ) {
606  $old = $this->schema;
607  if ( $schema !== null ) {
608  $this->schema = $schema;
609  }
610 
611  return $old;
612  }
613 
614  public function getLBInfo( $name = null ) {
615  if ( is_null( $name ) ) {
616  return $this->lbInfo;
617  } else {
618  if ( array_key_exists( $name, $this->lbInfo ) ) {
619  return $this->lbInfo[$name];
620  } else {
621  return null;
622  }
623  }
624  }
625 
626  public function setLBInfo( $name, $value = null ) {
627  if ( is_null( $value ) ) {
628  $this->lbInfo = $name;
629  } else {
630  $this->lbInfo[$name] = $value;
631  }
632  }
633 
634  public function setLazyMasterHandle( IDatabase $conn ) {
635  $this->lazyMasterHandle = $conn;
636  }
637 
643  protected function getLazyMasterHandle() {
645  }
646 
647  public function implicitGroupby() {
648  return true;
649  }
650 
651  public function implicitOrderby() {
652  return true;
653  }
654 
655  public function lastQuery() {
656  return $this->lastQuery;
657  }
658 
659  public function doneWrites() {
660  return (bool)$this->lastWriteTime;
661  }
662 
663  public function lastDoneWrites() {
664  return $this->lastWriteTime ?: false;
665  }
666 
667  public function writesPending() {
668  return $this->trxLevel && $this->trxDoneWrites;
669  }
670 
671  public function writesOrCallbacksPending() {
672  return $this->trxLevel && (
673  $this->trxDoneWrites ||
674  $this->trxIdleCallbacks ||
675  $this->trxPreCommitCallbacks ||
677  );
678  }
679 
683  final protected function getTransactionRoundId() {
684  // If transaction round participation is enabled, see if one is active
685  if ( $this->getFlag( self::DBO_TRX ) ) {
686  $id = $this->getLBInfo( 'trxRoundId' );
687 
688  return is_string( $id ) ? $id : null;
689  }
690 
691  return null;
692  }
693 
694  public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
695  if ( !$this->trxLevel ) {
696  return false;
697  } elseif ( !$this->trxDoneWrites ) {
698  return 0.0;
699  }
700 
701  switch ( $type ) {
702  case self::ESTIMATE_DB_APPLY:
703  $this->ping( $rtt );
704  $rttAdjTotal = $this->trxWriteAdjQueryCount * $rtt;
705  $applyTime = max( $this->trxWriteAdjDuration - $rttAdjTotal, 0 );
706  // For omitted queries, make them count as something at least
707  $omitted = $this->trxWriteQueryCount - $this->trxWriteAdjQueryCount;
708  $applyTime += self::TINY_WRITE_SEC * $omitted;
709 
710  return $applyTime;
711  default: // everything
713  }
714  }
715 
716  public function pendingWriteCallers() {
717  return $this->trxLevel ? $this->trxWriteCallers : [];
718  }
719 
720  public function pendingWriteRowsAffected() {
722  }
723 
730  protected function pendingWriteAndCallbackCallers() {
731  if ( !$this->trxLevel ) {
732  return [];
733  }
734 
735  $fnames = $this->trxWriteCallers;
736  foreach ( [
737  $this->trxIdleCallbacks,
738  $this->trxPreCommitCallbacks,
739  $this->trxEndCallbacks
740  ] as $callbacks ) {
741  foreach ( $callbacks as $callback ) {
742  $fnames[] = $callback[1];
743  }
744  }
745 
746  return $fnames;
747  }
748 
752  private function flatAtomicSectionList() {
753  return array_reduce( $this->trxAtomicLevels, function ( $accum, $v ) {
754  return $accum === null ? $v[0] : "$accum, " . $v[0];
755  } );
756  }
757 
758  public function isOpen() {
759  return $this->opened;
760  }
761 
762  public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
763  if ( ( $flag & self::DBO_IGNORE ) ) {
764  throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
765  }
766 
767  if ( $remember === self::REMEMBER_PRIOR ) {
768  array_push( $this->priorFlags, $this->flags );
769  }
770  $this->flags |= $flag;
771  }
772 
773  public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
774  if ( ( $flag & self::DBO_IGNORE ) ) {
775  throw new UnexpectedValueException( "Modifying DBO_IGNORE is not allowed." );
776  }
777 
778  if ( $remember === self::REMEMBER_PRIOR ) {
779  array_push( $this->priorFlags, $this->flags );
780  }
781  $this->flags &= ~$flag;
782  }
783 
784  public function restoreFlags( $state = self::RESTORE_PRIOR ) {
785  if ( !$this->priorFlags ) {
786  return;
787  }
788 
789  if ( $state === self::RESTORE_INITIAL ) {
790  $this->flags = reset( $this->priorFlags );
791  $this->priorFlags = [];
792  } else {
793  $this->flags = array_pop( $this->priorFlags );
794  }
795  }
796 
797  public function getFlag( $flag ) {
798  return !!( $this->flags & $flag );
799  }
800 
806  public function getProperty( $name ) {
807  return $this->$name;
808  }
809 
810  public function getDomainID() {
811  return $this->currentDomain->getId();
812  }
813 
814  final public function getWikiID() {
815  return $this->getDomainID();
816  }
817 
825  abstract function indexInfo( $table, $index, $fname = __METHOD__ );
826 
833  abstract function strencode( $s );
834 
838  protected function installErrorHandler() {
839  $this->phpError = false;
840  $this->htmlErrors = ini_set( 'html_errors', '0' );
841  set_error_handler( [ $this, 'connectionErrorLogger' ] );
842  }
843 
849  protected function restoreErrorHandler() {
850  restore_error_handler();
851  if ( $this->htmlErrors !== false ) {
852  ini_set( 'html_errors', $this->htmlErrors );
853  }
854 
855  return $this->getLastPHPError();
856  }
857 
861  protected function getLastPHPError() {
862  if ( $this->phpError ) {
863  $error = preg_replace( '!\[<a.*</a>\]!', '', $this->phpError );
864  $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
865 
866  return $error;
867  }
868 
869  return false;
870  }
871 
879  public function connectionErrorLogger( $errno, $errstr ) {
880  $this->phpError = $errstr;
881  }
882 
889  protected function getLogContext( array $extras = [] ) {
890  return array_merge(
891  [
892  'db_server' => $this->server,
893  'db_name' => $this->dbName,
894  'db_user' => $this->user,
895  ],
896  $extras
897  );
898  }
899 
900  final public function close() {
901  $exception = null; // error to throw after disconnecting
902 
903  if ( $this->conn ) {
904  // Resolve any dangling transaction first
905  if ( $this->trxLevel ) {
906  if ( $this->trxAtomicLevels ) {
907  // Cannot let incomplete atomic sections be committed
908  $levels = $this->flatAtomicSectionList();
909  $exception = new DBUnexpectedError(
910  $this,
911  __METHOD__ . ": atomic sections $levels are still open."
912  );
913  } elseif ( $this->trxAutomatic ) {
914  // Only the connection manager can commit non-empty DBO_TRX transactions
915  if ( $this->writesOrCallbacksPending() ) {
916  $exception = new DBUnexpectedError(
917  $this,
918  __METHOD__ .
919  ": mass commit/rollback of peer transaction required (DBO_TRX set)."
920  );
921  }
922  } elseif ( $this->trxLevel ) {
923  // Commit explicit transactions as if this was commit()
924  $this->queryLogger->warning(
925  __METHOD__ . ": writes or callbacks still pending.",
926  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
927  );
928  }
929 
930  if ( $this->trxEndCallbacksSuppressed ) {
931  $exception = $exception ?: new DBUnexpectedError(
932  $this,
933  __METHOD__ . ': callbacks are suppressed; cannot properly commit.'
934  );
935  }
936 
937  // Commit or rollback the changes and run any callbacks as needed
938  if ( $this->trxStatus === self::STATUS_TRX_OK && !$exception ) {
939  $this->commit(
940  __METHOD__,
941  $this->trxAutomatic ? self::FLUSHING_INTERNAL : self::FLUSHING_ONE
942  );
943  } else {
944  $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
945  }
946  }
947 
948  // Close the actual connection in the binding handle
949  $closed = $this->closeConnection();
950  $this->conn = false;
951  } else {
952  $closed = true; // already closed; nothing to do
953  }
954 
955  $this->opened = false;
956 
957  // Throw any unexpected errors after having disconnected
958  if ( $exception instanceof Exception ) {
959  throw $exception;
960  }
961 
962  // Sanity check that no callbacks are dangling
963  if (
964  $this->trxIdleCallbacks || $this->trxPreCommitCallbacks || $this->trxEndCallbacks
965  ) {
966  throw new RuntimeException(
967  "Transaction callbacks are still pending:\n" .
968  implode( ', ', $this->pendingWriteAndCallbackCallers() )
969  );
970  }
971 
972  return $closed;
973  }
974 
980  protected function assertOpen() {
981  if ( !$this->isOpen() ) {
982  throw new DBUnexpectedError( $this, "DB connection was already closed." );
983  }
984  }
985 
991  abstract protected function closeConnection();
992 
997  public function reportConnectionError( $error = 'Unknown error' ) {
998  $myError = $this->lastError();
999  if ( $myError ) {
1000  $error = $myError;
1001  }
1002 
1003  # New method
1004  throw new DBConnectionError( $this, $error );
1005  }
1006 
1016  abstract protected function doQuery( $sql );
1017 
1025  protected function isWriteQuery( $sql ) {
1026  return !preg_match(
1027  '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
1028  }
1029 
1034  protected function getQueryVerb( $sql ) {
1035  return preg_match( '/^\s*([a-z]+)/i', $sql, $m ) ? strtoupper( $m[1] ) : null;
1036  }
1037 
1047  protected function isTransactableQuery( $sql ) {
1048  return !in_array(
1049  $this->getQueryVerb( $sql ),
1050  [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET', 'CREATE', 'ALTER' ],
1051  true
1052  );
1053  }
1054 
1059  protected function registerTempTableOperation( $sql ) {
1060  if ( preg_match(
1061  '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
1062  $sql,
1063  $matches
1064  ) ) {
1065  $this->sessionTempTables[$matches[1]] = 1;
1066 
1067  return true;
1068  } elseif ( preg_match(
1069  '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
1070  $sql,
1071  $matches
1072  ) ) {
1073  $isTemp = isset( $this->sessionTempTables[$matches[1]] );
1074  unset( $this->sessionTempTables[$matches[1]] );
1075 
1076  return $isTemp;
1077  } elseif ( preg_match(
1078  '/^TRUNCATE\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
1079  $sql,
1080  $matches
1081  ) ) {
1082  return isset( $this->sessionTempTables[$matches[1]] );
1083  } elseif ( preg_match(
1084  '/^(?:INSERT\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+[`"\']?(\w+)[`"\']?/i',
1085  $sql,
1086  $matches
1087  ) ) {
1088  return isset( $this->sessionTempTables[$matches[1]] );
1089  }
1090 
1091  return false;
1092  }
1093 
1094  public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
1095  $this->assertTransactionStatus( $sql, $fname );
1096 
1097  # Avoid fatals if close() was called
1098  $this->assertOpen();
1099 
1100  $priorWritesPending = $this->writesOrCallbacksPending();
1101  $this->lastQuery = $sql;
1102 
1103  $isWrite = $this->isWriteQuery( $sql );
1104  if ( $isWrite ) {
1105  $isNonTempWrite = !$this->registerTempTableOperation( $sql );
1106  } else {
1107  $isNonTempWrite = false;
1108  }
1109 
1110  if ( $isWrite ) {
1111  if ( $this->getLBInfo( 'replica' ) === true ) {
1112  throw new DBError(
1113  $this,
1114  'Write operations are not allowed on replica database connections.'
1115  );
1116  }
1117  # In theory, non-persistent writes are allowed in read-only mode, but due to things
1118  # like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway...
1119  $reason = $this->getReadOnlyReason();
1120  if ( $reason !== false ) {
1121  throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
1122  }
1123  # Set a flag indicating that writes have been done
1124  $this->lastWriteTime = microtime( true );
1125  }
1126 
1127  # Add trace comment to the begin of the sql string, right after the operator.
1128  # Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598)
1129  $commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
1130 
1131  # Start implicit transactions that wrap the request if DBO_TRX is enabled
1132  if ( !$this->trxLevel && $this->getFlag( self::DBO_TRX )
1133  && $this->isTransactableQuery( $sql )
1134  ) {
1135  $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
1136  $this->trxAutomatic = true;
1137  }
1138 
1139  # Keep track of whether the transaction has write queries pending
1140  if ( $this->trxLevel && !$this->trxDoneWrites && $isWrite ) {
1141  $this->trxDoneWrites = true;
1142  $this->trxProfiler->transactionWritingIn(
1143  $this->server, $this->dbName, $this->trxShortId );
1144  }
1145 
1146  if ( $this->getFlag( self::DBO_DEBUG ) ) {
1147  $this->queryLogger->debug( "{$this->dbName} {$commentedSql}" );
1148  }
1149 
1150  # Send the query to the server and fetch any corresponding errors
1151  $ret = $this->doProfiledQuery( $sql, $commentedSql, $isNonTempWrite, $fname );
1152  $lastError = $this->lastError();
1153  $lastErrno = $this->lastErrno();
1154 
1155  # Try reconnecting if the connection was lost
1156  if ( $ret === false && $this->wasConnectionLoss() ) {
1157  # Check if any meaningful session state was lost
1158  $recoverable = $this->canRecoverFromDisconnect( $sql, $priorWritesPending );
1159  # Update session state tracking and try to restore the connection
1160  $reconnected = $this->replaceLostConnection( __METHOD__ );
1161  # Silently resend the query to the server if it is safe and possible
1162  if ( $reconnected && $recoverable ) {
1163  $ret = $this->doProfiledQuery( $sql, $commentedSql, $isNonTempWrite, $fname );
1164  $lastError = $this->lastError();
1165  $lastErrno = $this->lastErrno();
1166 
1167  if ( $ret === false && $this->wasConnectionLoss() ) {
1168  # Query probably causes disconnects; reconnect and do not re-run it
1169  $this->replaceLostConnection( __METHOD__ );
1170  }
1171  }
1172  }
1173 
1174  if ( $ret === false ) {
1175  if ( $this->trxLevel ) {
1176  if ( !$this->wasKnownStatementRollbackError() ) {
1177  # Either the query was aborted or all queries after BEGIN where aborted.
1178  if ( $this->explicitTrxActive() || $priorWritesPending ) {
1179  # In the first case, the only options going forward are (a) ROLLBACK, or
1180  # (b) ROLLBACK TO SAVEPOINT (if one was set). If the later case, the only
1181  # option is ROLLBACK, since the snapshots would have been released.
1182  $this->trxStatus = self::STATUS_TRX_ERROR;
1183  $this->trxStatusCause =
1184  $this->makeQueryException( $lastError, $lastErrno, $sql, $fname );
1185  $tempIgnore = false; // cannot recover
1186  } else {
1187  # Nothing prior was there to lose from the transaction,
1188  # so just roll it back.
1189  $this->rollback( __METHOD__ . " ($fname)", self::FLUSHING_INTERNAL );
1190  }
1191  $this->trxStatusIgnoredCause = null;
1192  } else {
1193  # We're ignoring an error that caused just the current query to be aborted.
1194  # But log the cause so we can log a deprecation notice if a
1195  # caller actually does ignore it.
1196  $this->trxStatusIgnoredCause = [ $lastError, $lastErrno, $fname ];
1197  }
1198  }
1199 
1200  $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $tempIgnore );
1201  }
1202 
1203  return $this->resultObject( $ret );
1204  }
1205 
1216  private function doProfiledQuery( $sql, $commentedSql, $isWrite, $fname ) {
1217  $isMaster = !is_null( $this->getLBInfo( 'master' ) );
1218  # generalizeSQL() will probably cut down the query to reasonable
1219  # logging size most of the time. The substr is really just a sanity check.
1220  if ( $isMaster ) {
1221  $queryProf = 'query-m: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
1222  } else {
1223  $queryProf = 'query: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
1224  }
1225 
1226  # Include query transaction state
1227  $queryProf .= $this->trxShortId ? " [TRX#{$this->trxShortId}]" : "";
1228 
1229  $startTime = microtime( true );
1230  if ( $this->profiler ) {
1231  call_user_func( [ $this->profiler, 'profileIn' ], $queryProf );
1232  }
1233  $this->affectedRowCount = null;
1234  $ret = $this->doQuery( $commentedSql );
1235  $this->affectedRowCount = $this->affectedRows();
1236  if ( $this->profiler ) {
1237  call_user_func( [ $this->profiler, 'profileOut' ], $queryProf );
1238  }
1239  $queryRuntime = max( microtime( true ) - $startTime, 0.0 );
1240 
1241  unset( $queryProfSection ); // profile out (if set)
1242 
1243  if ( $ret !== false ) {
1244  $this->lastPing = $startTime;
1245  if ( $isWrite && $this->trxLevel ) {
1246  $this->updateTrxWriteQueryTime( $sql, $queryRuntime, $this->affectedRows() );
1247  $this->trxWriteCallers[] = $fname;
1248  }
1249  }
1250 
1251  if ( $sql === self::PING_QUERY ) {
1252  $this->rttEstimate = $queryRuntime;
1253  }
1254 
1255  $this->trxProfiler->recordQueryCompletion(
1256  $queryProf, $startTime, $isWrite, $this->affectedRows()
1257  );
1258  $this->queryLogger->debug( $sql, [
1259  'method' => $fname,
1260  'master' => $isMaster,
1261  'runtime' => $queryRuntime,
1262  ] );
1263 
1264  return $ret;
1265  }
1266 
1279  private function updateTrxWriteQueryTime( $sql, $runtime, $affected ) {
1280  // Whether this is indicative of replica DB runtime (except for RBR or ws_repl)
1281  $indicativeOfReplicaRuntime = true;
1282  if ( $runtime > self::SLOW_WRITE_SEC ) {
1283  $verb = $this->getQueryVerb( $sql );
1284  // insert(), upsert(), replace() are fast unless bulky in size or blocked on locks
1285  if ( $verb === 'INSERT' ) {
1286  $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS;
1287  } elseif ( $verb === 'REPLACE' ) {
1288  $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS / 2;
1289  }
1290  }
1291 
1292  $this->trxWriteDuration += $runtime;
1293  $this->trxWriteQueryCount += 1;
1294  $this->trxWriteAffectedRows += $affected;
1295  if ( $indicativeOfReplicaRuntime ) {
1296  $this->trxWriteAdjDuration += $runtime;
1297  $this->trxWriteAdjQueryCount += 1;
1298  }
1299  }
1300 
1306  private function assertTransactionStatus( $sql, $fname ) {
1307  if ( $this->getQueryVerb( $sql ) === 'ROLLBACK' ) { // transaction/savepoint
1308  return;
1309  }
1310 
1311  if ( $this->trxStatus < self::STATUS_TRX_OK ) {
1312  throw new DBTransactionStateError(
1313  $this,
1314  "Cannot execute query from $fname while transaction status is ERROR.",
1315  [],
1316  $this->trxStatusCause
1317  );
1318  } elseif ( $this->trxStatus === self::STATUS_TRX_OK && $this->trxStatusIgnoredCause ) {
1319  list( $iLastError, $iLastErrno, $iFname ) = $this->trxStatusIgnoredCause;
1320  call_user_func( $this->deprecationLogger,
1321  "Caller from $fname ignored an error originally raised from $iFname: " .
1322  "[$iLastErrno] $iLastError"
1323  );
1324  $this->trxStatusIgnoredCause = null;
1325  }
1326  }
1327 
1338  private function canRecoverFromDisconnect( $sql, $priorWritesPending ) {
1339  # Transaction dropped; this can mean lost writes, or REPEATABLE-READ snapshots.
1340  # Dropped connections also mean that named locks are automatically released.
1341  # Only allow error suppression in autocommit mode or when the lost transaction
1342  # didn't matter anyway (aside from DBO_TRX snapshot loss).
1343  if ( $this->namedLocksHeld ) {
1344  return false; // possible critical section violation
1345  } elseif ( $this->sessionTempTables ) {
1346  return false; // tables might be queried latter
1347  } elseif ( $sql === 'COMMIT' ) {
1348  return !$priorWritesPending; // nothing written anyway? (T127428)
1349  } elseif ( $sql === 'ROLLBACK' ) {
1350  return true; // transaction lost...which is also what was requested :)
1351  } elseif ( $this->explicitTrxActive() ) {
1352  return false; // don't drop atomocity and explicit snapshots
1353  } elseif ( $priorWritesPending ) {
1354  return false; // prior writes lost from implicit transaction
1355  }
1356 
1357  return true;
1358  }
1359 
1363  private function handleSessionLoss() {
1364  // Clean up tracking of session-level things...
1365  // https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html
1366  // https://www.postgresql.org/docs/9.2/static/sql-createtable.html (ignoring ON COMMIT)
1367  $this->sessionTempTables = [];
1368  // https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
1369  // https://www.postgresql.org/docs/9.4/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1370  $this->namedLocksHeld = [];
1371  // Session loss implies transaction loss
1372  $this->handleTransactionLoss();
1373  }
1374 
1378  private function handleTransactionLoss() {
1379  $this->trxLevel = 0;
1380  $this->trxAtomicCounter = 0;
1381  $this->trxIdleCallbacks = []; // T67263; transaction already lost
1382  $this->trxPreCommitCallbacks = []; // T67263; transaction already lost
1383  try {
1384  // Handle callbacks in trxEndCallbacks, e.g. onTransactionResolution().
1385  // If callback suppression is set then the array will remain unhandled.
1386  $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
1387  } catch ( Exception $ex ) {
1388  // Already logged; move on...
1389  }
1390  try {
1391  // Handle callbacks in trxRecurringCallbacks, e.g. setTransactionListener()
1392  $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
1393  } catch ( Exception $ex ) {
1394  // Already logged; move on...
1395  }
1396  }
1397 
1408  protected function wasQueryTimeout( $error, $errno ) {
1409  return false;
1410  }
1411 
1423  public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
1424  if ( $tempIgnore ) {
1425  $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" );
1426  } else {
1427  $exception = $this->makeQueryException( $error, $errno, $sql, $fname );
1428 
1429  throw $exception;
1430  }
1431  }
1432 
1440  private function makeQueryException( $error, $errno, $sql, $fname ) {
1441  $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
1442  $this->queryLogger->error(
1443  "{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
1444  $this->getLogContext( [
1445  'method' => __METHOD__,
1446  'errno' => $errno,
1447  'error' => $error,
1448  'sql1line' => $sql1line,
1449  'fname' => $fname,
1450  ] )
1451  );
1452  $this->queryLogger->debug( "SQL ERROR: " . $error . "\n" );
1453  $wasQueryTimeout = $this->wasQueryTimeout( $error, $errno );
1454  if ( $wasQueryTimeout ) {
1455  $e = new DBQueryTimeoutError( $this, $error, $errno, $sql, $fname );
1456  } else {
1457  $e = new DBQueryError( $this, $error, $errno, $sql, $fname );
1458  }
1459 
1460  return $e;
1461  }
1462 
1463  public function freeResult( $res ) {
1464  }
1465 
1466  public function selectField(
1467  $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1468  ) {
1469  if ( $var === '*' ) { // sanity
1470  throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
1471  }
1472 
1473  if ( !is_array( $options ) ) {
1474  $options = [ $options ];
1475  }
1476 
1477  $options['LIMIT'] = 1;
1478 
1479  $res = $this->select( $table, $var, $cond, $fname, $options, $join_conds );
1480  if ( $res === false || !$this->numRows( $res ) ) {
1481  return false;
1482  }
1483 
1484  $row = $this->fetchRow( $res );
1485 
1486  if ( $row !== false ) {
1487  return reset( $row );
1488  } else {
1489  return false;
1490  }
1491  }
1492 
1493  public function selectFieldValues(
1494  $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1495  ) {
1496  if ( $var === '*' ) { // sanity
1497  throw new DBUnexpectedError( $this, "Cannot use a * field" );
1498  } elseif ( !is_string( $var ) ) { // sanity
1499  throw new DBUnexpectedError( $this, "Cannot use an array of fields" );
1500  }
1501 
1502  if ( !is_array( $options ) ) {
1503  $options = [ $options ];
1504  }
1505 
1506  $res = $this->select( $table, $var, $cond, $fname, $options, $join_conds );
1507  if ( $res === false ) {
1508  return false;
1509  }
1510 
1511  $values = [];
1512  foreach ( $res as $row ) {
1513  $values[] = $row->$var;
1514  }
1515 
1516  return $values;
1517  }
1518 
1528  protected function makeSelectOptions( $options ) {
1529  $preLimitTail = $postLimitTail = '';
1530  $startOpts = '';
1531 
1532  $noKeyOptions = [];
1533 
1534  foreach ( $options as $key => $option ) {
1535  if ( is_numeric( $key ) ) {
1536  $noKeyOptions[$option] = true;
1537  }
1538  }
1539 
1540  $preLimitTail .= $this->makeGroupByWithHaving( $options );
1541 
1542  $preLimitTail .= $this->makeOrderBy( $options );
1543 
1544  if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1545  $postLimitTail .= ' FOR UPDATE';
1546  }
1547 
1548  if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
1549  $postLimitTail .= ' LOCK IN SHARE MODE';
1550  }
1551 
1552  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1553  $startOpts .= 'DISTINCT';
1554  }
1555 
1556  # Various MySQL extensions
1557  if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
1558  $startOpts .= ' /*! STRAIGHT_JOIN */';
1559  }
1560 
1561  if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
1562  $startOpts .= ' HIGH_PRIORITY';
1563  }
1564 
1565  if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
1566  $startOpts .= ' SQL_BIG_RESULT';
1567  }
1568 
1569  if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
1570  $startOpts .= ' SQL_BUFFER_RESULT';
1571  }
1572 
1573  if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
1574  $startOpts .= ' SQL_SMALL_RESULT';
1575  }
1576 
1577  if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
1578  $startOpts .= ' SQL_CALC_FOUND_ROWS';
1579  }
1580 
1581  if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
1582  $startOpts .= ' SQL_CACHE';
1583  }
1584 
1585  if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
1586  $startOpts .= ' SQL_NO_CACHE';
1587  }
1588 
1589  if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
1590  $useIndex = $this->useIndexClause( $options['USE INDEX'] );
1591  } else {
1592  $useIndex = '';
1593  }
1594  if ( isset( $options['IGNORE INDEX'] ) && is_string( $options['IGNORE INDEX'] ) ) {
1595  $ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
1596  } else {
1597  $ignoreIndex = '';
1598  }
1599 
1600  return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1601  }
1602 
1611  protected function makeGroupByWithHaving( $options ) {
1612  $sql = '';
1613  if ( isset( $options['GROUP BY'] ) ) {
1614  $gb = is_array( $options['GROUP BY'] )
1615  ? implode( ',', $options['GROUP BY'] )
1616  : $options['GROUP BY'];
1617  $sql .= ' GROUP BY ' . $gb;
1618  }
1619  if ( isset( $options['HAVING'] ) ) {
1620  $having = is_array( $options['HAVING'] )
1621  ? $this->makeList( $options['HAVING'], self::LIST_AND )
1622  : $options['HAVING'];
1623  $sql .= ' HAVING ' . $having;
1624  }
1625 
1626  return $sql;
1627  }
1628 
1637  protected function makeOrderBy( $options ) {
1638  if ( isset( $options['ORDER BY'] ) ) {
1639  $ob = is_array( $options['ORDER BY'] )
1640  ? implode( ',', $options['ORDER BY'] )
1641  : $options['ORDER BY'];
1642 
1643  return ' ORDER BY ' . $ob;
1644  }
1645 
1646  return '';
1647  }
1648 
1649  public function select( $table, $vars, $conds = '', $fname = __METHOD__,
1650  $options = [], $join_conds = [] ) {
1651  $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
1652 
1653  return $this->query( $sql, $fname );
1654  }
1655 
1656  public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
1657  $options = [], $join_conds = []
1658  ) {
1659  if ( is_array( $vars ) ) {
1660  $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
1661  }
1662 
1663  $options = (array)$options;
1664  $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
1665  ? $options['USE INDEX']
1666  : [];
1667  $ignoreIndexes = (
1668  isset( $options['IGNORE INDEX'] ) &&
1669  is_array( $options['IGNORE INDEX'] )
1670  )
1671  ? $options['IGNORE INDEX']
1672  : [];
1673 
1674  if ( is_array( $table ) ) {
1675  $from = ' FROM ' .
1677  $table, $useIndexes, $ignoreIndexes, $join_conds );
1678  } elseif ( $table != '' ) {
1679  $from = ' FROM ' .
1681  [ $table ], $useIndexes, $ignoreIndexes, [] );
1682  } else {
1683  $from = '';
1684  }
1685 
1686  list( $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ) =
1687  $this->makeSelectOptions( $options );
1688 
1689  if ( is_array( $conds ) ) {
1690  $conds = $this->makeList( $conds, self::LIST_AND );
1691  }
1692 
1693  if ( $conds === null || $conds === false ) {
1694  $this->queryLogger->warning(
1695  __METHOD__
1696  . ' called from '
1697  . $fname
1698  . ' with incorrect parameters: $conds must be a string or an array'
1699  );
1700  $conds = '';
1701  }
1702 
1703  if ( $conds === '' ) {
1704  $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
1705  } elseif ( is_string( $conds ) ) {
1706  $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex " .
1707  "WHERE $conds $preLimitTail";
1708  } else {
1709  throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
1710  }
1711 
1712  if ( isset( $options['LIMIT'] ) ) {
1713  $sql = $this->limitResult( $sql, $options['LIMIT'],
1714  isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
1715  }
1716  $sql = "$sql $postLimitTail";
1717 
1718  if ( isset( $options['EXPLAIN'] ) ) {
1719  $sql = 'EXPLAIN ' . $sql;
1720  }
1721 
1722  return $sql;
1723  }
1724 
1725  public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
1726  $options = [], $join_conds = []
1727  ) {
1728  $options = (array)$options;
1729  $options['LIMIT'] = 1;
1730  $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
1731 
1732  if ( $res === false ) {
1733  return false;
1734  }
1735 
1736  if ( !$this->numRows( $res ) ) {
1737  return false;
1738  }
1739 
1740  $obj = $this->fetchObject( $res );
1741 
1742  return $obj;
1743  }
1744 
1745  public function estimateRowCount(
1746  $table, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1747  ) {
1748  $conds = $this->normalizeConditions( $conds, $fname );
1749  $column = $this->extractSingleFieldFromList( $var );
1750  if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
1751  $conds[] = "$column IS NOT NULL";
1752  }
1753 
1754  $res = $this->select(
1755  $table, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options, $join_conds
1756  );
1757  $row = $res ? $this->fetchRow( $res ) : [];
1758 
1759  return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
1760  }
1761 
1762  public function selectRowCount(
1763  $tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1764  ) {
1765  $conds = $this->normalizeConditions( $conds, $fname );
1766  $column = $this->extractSingleFieldFromList( $var );
1767  if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
1768  $conds[] = "$column IS NOT NULL";
1769  }
1770 
1771  $res = $this->select(
1772  [
1773  'tmp_count' => $this->buildSelectSubquery(
1774  $tables,
1775  '1',
1776  $conds,
1777  $fname,
1778  $options,
1779  $join_conds
1780  )
1781  ],
1782  [ 'rowcount' => 'COUNT(*)' ],
1783  [],
1784  $fname
1785  );
1786  $row = $res ? $this->fetchRow( $res ) : [];
1787 
1788  return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
1789  }
1790 
1796  final protected function normalizeConditions( $conds, $fname ) {
1797  if ( $conds === null || $conds === false ) {
1798  $this->queryLogger->warning(
1799  __METHOD__
1800  . ' called from '
1801  . $fname
1802  . ' with incorrect parameters: $conds must be a string or an array'
1803  );
1804  $conds = '';
1805  }
1806 
1807  if ( !is_array( $conds ) ) {
1808  $conds = ( $conds === '' ) ? [] : [ $conds ];
1809  }
1810 
1811  return $conds;
1812  }
1813 
1819  final protected function extractSingleFieldFromList( $var ) {
1820  if ( is_array( $var ) ) {
1821  if ( !$var ) {
1822  $column = null;
1823  } elseif ( count( $var ) == 1 ) {
1824  $column = isset( $var[0] ) ? $var[0] : reset( $var );
1825  } else {
1826  throw new DBUnexpectedError( $this, __METHOD__ . ': got multiple columns.' );
1827  }
1828  } else {
1829  $column = $var;
1830  }
1831 
1832  return $column;
1833  }
1834 
1843  protected static function generalizeSQL( $sql ) {
1844  # This does the same as the regexp below would do, but in such a way
1845  # as to avoid crashing php on some large strings.
1846  # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
1847 
1848  $sql = str_replace( "\\\\", '', $sql );
1849  $sql = str_replace( "\\'", '', $sql );
1850  $sql = str_replace( "\\\"", '', $sql );
1851  $sql = preg_replace( "/'.*'/s", "'X'", $sql );
1852  $sql = preg_replace( '/".*"/s', "'X'", $sql );
1853 
1854  # All newlines, tabs, etc replaced by single space
1855  $sql = preg_replace( '/\s+/', ' ', $sql );
1856 
1857  # All numbers => N,
1858  # except the ones surrounded by characters, e.g. l10n
1859  $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
1860  $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
1861 
1862  return $sql;
1863  }
1864 
1865  public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1866  $info = $this->fieldInfo( $table, $field );
1867 
1868  return (bool)$info;
1869  }
1870 
1871  public function indexExists( $table, $index, $fname = __METHOD__ ) {
1872  if ( !$this->tableExists( $table ) ) {
1873  return null;
1874  }
1875 
1876  $info = $this->indexInfo( $table, $index, $fname );
1877  if ( is_null( $info ) ) {
1878  return null;
1879  } else {
1880  return $info !== false;
1881  }
1882  }
1883 
1884  public function tableExists( $table, $fname = __METHOD__ ) {
1885  $tableRaw = $this->tableName( $table, 'raw' );
1886  if ( isset( $this->sessionTempTables[$tableRaw] ) ) {
1887  return true; // already known to exist
1888  }
1889 
1890  $table = $this->tableName( $table );
1891  $ignoreErrors = true;
1892  $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname, $ignoreErrors );
1893 
1894  return (bool)$res;
1895  }
1896 
1897  public function indexUnique( $table, $index ) {
1898  $indexInfo = $this->indexInfo( $table, $index );
1899 
1900  if ( !$indexInfo ) {
1901  return null;
1902  }
1903 
1904  return !$indexInfo[0]->Non_unique;
1905  }
1906 
1913  protected function makeInsertOptions( $options ) {
1914  return implode( ' ', $options );
1915  }
1916 
1917  public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
1918  # No rows to insert, easy just return now
1919  if ( !count( $a ) ) {
1920  return true;
1921  }
1922 
1923  $table = $this->tableName( $table );
1924 
1925  if ( !is_array( $options ) ) {
1926  $options = [ $options ];
1927  }
1928 
1929  $fh = null;
1930  if ( isset( $options['fileHandle'] ) ) {
1931  $fh = $options['fileHandle'];
1932  }
1933  $options = $this->makeInsertOptions( $options );
1934 
1935  if ( isset( $a[0] ) && is_array( $a[0] ) ) {
1936  $multi = true;
1937  $keys = array_keys( $a[0] );
1938  } else {
1939  $multi = false;
1940  $keys = array_keys( $a );
1941  }
1942 
1943  $sql = 'INSERT ' . $options .
1944  " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
1945 
1946  if ( $multi ) {
1947  $first = true;
1948  foreach ( $a as $row ) {
1949  if ( $first ) {
1950  $first = false;
1951  } else {
1952  $sql .= ',';
1953  }
1954  $sql .= '(' . $this->makeList( $row ) . ')';
1955  }
1956  } else {
1957  $sql .= '(' . $this->makeList( $a ) . ')';
1958  }
1959 
1960  if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
1961  return false;
1962  } elseif ( $fh !== null ) {
1963  return true;
1964  }
1965 
1966  return (bool)$this->query( $sql, $fname );
1967  }
1968 
1975  protected function makeUpdateOptionsArray( $options ) {
1976  if ( !is_array( $options ) ) {
1977  $options = [ $options ];
1978  }
1979 
1980  $opts = [];
1981 
1982  if ( in_array( 'IGNORE', $options ) ) {
1983  $opts[] = 'IGNORE';
1984  }
1985 
1986  return $opts;
1987  }
1988 
1995  protected function makeUpdateOptions( $options ) {
1996  $opts = $this->makeUpdateOptionsArray( $options );
1997 
1998  return implode( ' ', $opts );
1999  }
2000 
2001  public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
2002  $table = $this->tableName( $table );
2003  $opts = $this->makeUpdateOptions( $options );
2004  $sql = "UPDATE $opts $table SET " . $this->makeList( $values, self::LIST_SET );
2005 
2006  if ( $conds !== [] && $conds !== '*' ) {
2007  $sql .= " WHERE " . $this->makeList( $conds, self::LIST_AND );
2008  }
2009 
2010  return (bool)$this->query( $sql, $fname );
2011  }
2012 
2013  public function makeList( $a, $mode = self::LIST_COMMA ) {
2014  if ( !is_array( $a ) ) {
2015  throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
2016  }
2017 
2018  $first = true;
2019  $list = '';
2020 
2021  foreach ( $a as $field => $value ) {
2022  if ( !$first ) {
2023  if ( $mode == self::LIST_AND ) {
2024  $list .= ' AND ';
2025  } elseif ( $mode == self::LIST_OR ) {
2026  $list .= ' OR ';
2027  } else {
2028  $list .= ',';
2029  }
2030  } else {
2031  $first = false;
2032  }
2033 
2034  if ( ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_numeric( $field ) ) {
2035  $list .= "($value)";
2036  } elseif ( $mode == self::LIST_SET && is_numeric( $field ) ) {
2037  $list .= "$value";
2038  } elseif (
2039  ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_array( $value )
2040  ) {
2041  // Remove null from array to be handled separately if found
2042  $includeNull = false;
2043  foreach ( array_keys( $value, null, true ) as $nullKey ) {
2044  $includeNull = true;
2045  unset( $value[$nullKey] );
2046  }
2047  if ( count( $value ) == 0 && !$includeNull ) {
2048  throw new InvalidArgumentException(
2049  __METHOD__ . ": empty input for field $field" );
2050  } elseif ( count( $value ) == 0 ) {
2051  // only check if $field is null
2052  $list .= "$field IS NULL";
2053  } else {
2054  // IN clause contains at least one valid element
2055  if ( $includeNull ) {
2056  // Group subconditions to ensure correct precedence
2057  $list .= '(';
2058  }
2059  if ( count( $value ) == 1 ) {
2060  // Special-case single values, as IN isn't terribly efficient
2061  // Don't necessarily assume the single key is 0; we don't
2062  // enforce linear numeric ordering on other arrays here.
2063  $value = array_values( $value )[0];
2064  $list .= $field . " = " . $this->addQuotes( $value );
2065  } else {
2066  $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
2067  }
2068  // if null present in array, append IS NULL
2069  if ( $includeNull ) {
2070  $list .= " OR $field IS NULL)";
2071  }
2072  }
2073  } elseif ( $value === null ) {
2074  if ( $mode == self::LIST_AND || $mode == self::LIST_OR ) {
2075  $list .= "$field IS ";
2076  } elseif ( $mode == self::LIST_SET ) {
2077  $list .= "$field = ";
2078  }
2079  $list .= 'NULL';
2080  } else {
2081  if (
2082  $mode == self::LIST_AND || $mode == self::LIST_OR || $mode == self::LIST_SET
2083  ) {
2084  $list .= "$field = ";
2085  }
2086  $list .= $mode == self::LIST_NAMES ? $value : $this->addQuotes( $value );
2087  }
2088  }
2089 
2090  return $list;
2091  }
2092 
2093  public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
2094  $conds = [];
2095 
2096  foreach ( $data as $base => $sub ) {
2097  if ( count( $sub ) ) {
2098  $conds[] = $this->makeList(
2099  [ $baseKey => $base, $subKey => array_keys( $sub ) ],
2100  self::LIST_AND );
2101  }
2102  }
2103 
2104  if ( $conds ) {
2105  return $this->makeList( $conds, self::LIST_OR );
2106  } else {
2107  // Nothing to search for...
2108  return false;
2109  }
2110  }
2111 
2112  public function aggregateValue( $valuedata, $valuename = 'value' ) {
2113  return $valuename;
2114  }
2115 
2116  public function bitNot( $field ) {
2117  return "(~$field)";
2118  }
2119 
2120  public function bitAnd( $fieldLeft, $fieldRight ) {
2121  return "($fieldLeft & $fieldRight)";
2122  }
2123 
2124  public function bitOr( $fieldLeft, $fieldRight ) {
2125  return "($fieldLeft | $fieldRight)";
2126  }
2127 
2128  public function buildConcat( $stringList ) {
2129  return 'CONCAT(' . implode( ',', $stringList ) . ')';
2130  }
2131 
2132  public function buildGroupConcatField(
2133  $delim, $table, $field, $conds = '', $join_conds = []
2134  ) {
2135  $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
2136 
2137  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
2138  }
2139 
2140  public function buildSubstring( $input, $startPosition, $length = null ) {
2141  $this->assertBuildSubstringParams( $startPosition, $length );
2142  $functionBody = "$input FROM $startPosition";
2143  if ( $length !== null ) {
2144  $functionBody .= " FOR $length";
2145  }
2146  return 'SUBSTRING(' . $functionBody . ')';
2147  }
2148 
2161  protected function assertBuildSubstringParams( $startPosition, $length ) {
2162  if ( !is_int( $startPosition ) || $startPosition <= 0 ) {
2163  throw new InvalidArgumentException(
2164  '$startPosition must be a positive integer'
2165  );
2166  }
2167  if ( !( is_int( $length ) && $length >= 0 || $length === null ) ) {
2168  throw new InvalidArgumentException(
2169  '$length must be null or an integer greater than or equal to 0'
2170  );
2171  }
2172  }
2173 
2174  public function buildStringCast( $field ) {
2175  return $field;
2176  }
2177 
2178  public function buildIntegerCast( $field ) {
2179  return 'CAST( ' . $field . ' AS INTEGER )';
2180  }
2181 
2182  public function buildSelectSubquery(
2183  $table, $vars, $conds = '', $fname = __METHOD__,
2184  $options = [], $join_conds = []
2185  ) {
2186  return new Subquery(
2187  $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds )
2188  );
2189  }
2190 
2191  public function databasesAreIndependent() {
2192  return false;
2193  }
2194 
2195  public function selectDB( $db ) {
2196  # Stub. Shouldn't cause serious problems if it's not overridden, but
2197  # if your database engine supports a concept similar to MySQL's
2198  # databases you may as well.
2199  $this->dbName = $db;
2200 
2201  return true;
2202  }
2203 
2204  public function getDBname() {
2205  return $this->dbName;
2206  }
2207 
2208  public function getServer() {
2209  return $this->server;
2210  }
2211 
2212  public function tableName( $name, $format = 'quoted' ) {
2213  if ( $name instanceof Subquery ) {
2214  throw new DBUnexpectedError(
2215  $this,
2216  __METHOD__ . ': got Subquery instance when expecting a string.'
2217  );
2218  }
2219 
2220  # Skip the entire process when we have a string quoted on both ends.
2221  # Note that we check the end so that we will still quote any use of
2222  # use of `database`.table. But won't break things if someone wants
2223  # to query a database table with a dot in the name.
2224  if ( $this->isQuotedIdentifier( $name ) ) {
2225  return $name;
2226  }
2227 
2228  # Lets test for any bits of text that should never show up in a table
2229  # name. Basically anything like JOIN or ON which are actually part of
2230  # SQL queries, but may end up inside of the table value to combine
2231  # sql. Such as how the API is doing.
2232  # Note that we use a whitespace test rather than a \b test to avoid
2233  # any remote case where a word like on may be inside of a table name
2234  # surrounded by symbols which may be considered word breaks.
2235  if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
2236  $this->queryLogger->warning(
2237  __METHOD__ . ": use of subqueries is not supported this way.",
2238  [ 'trace' => ( new RuntimeException() )->getTraceAsString() ]
2239  );
2240 
2241  return $name;
2242  }
2243 
2244  # Split database and table into proper variables.
2245  list( $database, $schema, $prefix, $table ) = $this->qualifiedTableComponents( $name );
2246 
2247  # Quote $table and apply the prefix if not quoted.
2248  # $tableName might be empty if this is called from Database::replaceVars()
2249  $tableName = "{$prefix}{$table}";
2250  if ( $format === 'quoted'
2251  && !$this->isQuotedIdentifier( $tableName )
2252  && $tableName !== ''
2253  ) {
2254  $tableName = $this->addIdentifierQuotes( $tableName );
2255  }
2256 
2257  # Quote $schema and $database and merge them with the table name if needed
2258  $tableName = $this->prependDatabaseOrSchema( $schema, $tableName, $format );
2259  $tableName = $this->prependDatabaseOrSchema( $database, $tableName, $format );
2260 
2261  return $tableName;
2262  }
2263 
2270  protected function qualifiedTableComponents( $name ) {
2271  # We reverse the explode so that database.table and table both output the correct table.
2272  $dbDetails = explode( '.', $name, 3 );
2273  if ( count( $dbDetails ) == 3 ) {
2274  list( $database, $schema, $table ) = $dbDetails;
2275  # We don't want any prefix added in this case
2276  $prefix = '';
2277  } elseif ( count( $dbDetails ) == 2 ) {
2278  list( $database, $table ) = $dbDetails;
2279  # We don't want any prefix added in this case
2280  $prefix = '';
2281  # In dbs that support it, $database may actually be the schema
2282  # but that doesn't affect any of the functionality here
2283  $schema = '';
2284  } else {
2285  list( $table ) = $dbDetails;
2286  if ( isset( $this->tableAliases[$table] ) ) {
2287  $database = $this->tableAliases[$table]['dbname'];
2288  $schema = is_string( $this->tableAliases[$table]['schema'] )
2289  ? $this->tableAliases[$table]['schema']
2290  : $this->schema;
2291  $prefix = is_string( $this->tableAliases[$table]['prefix'] )
2292  ? $this->tableAliases[$table]['prefix']
2294  } else {
2295  $database = '';
2296  $schema = $this->schema; # Default schema
2297  $prefix = $this->tablePrefix; # Default prefix
2298  }
2299  }
2300 
2301  return [ $database, $schema, $prefix, $table ];
2302  }
2303 
2310  private function prependDatabaseOrSchema( $namespace, $relation, $format ) {
2311  if ( strlen( $namespace ) ) {
2312  if ( $format === 'quoted' && !$this->isQuotedIdentifier( $namespace ) ) {
2313  $namespace = $this->addIdentifierQuotes( $namespace );
2314  }
2315  $relation = $namespace . '.' . $relation;
2316  }
2317 
2318  return $relation;
2319  }
2320 
2321  public function tableNames() {
2322  $inArray = func_get_args();
2323  $retVal = [];
2324 
2325  foreach ( $inArray as $name ) {
2326  $retVal[$name] = $this->tableName( $name );
2327  }
2328 
2329  return $retVal;
2330  }
2331 
2332  public function tableNamesN() {
2333  $inArray = func_get_args();
2334  $retVal = [];
2335 
2336  foreach ( $inArray as $name ) {
2337  $retVal[] = $this->tableName( $name );
2338  }
2339 
2340  return $retVal;
2341  }
2342 
2354  protected function tableNameWithAlias( $table, $alias = false ) {
2355  if ( is_string( $table ) ) {
2356  $quotedTable = $this->tableName( $table );
2357  } elseif ( $table instanceof Subquery ) {
2358  $quotedTable = (string)$table;
2359  } else {
2360  throw new InvalidArgumentException( "Table must be a string or Subquery." );
2361  }
2362 
2363  if ( !strlen( $alias ) || $alias === $table ) {
2364  if ( $table instanceof Subquery ) {
2365  throw new InvalidArgumentException( "Subquery table missing alias." );
2366  }
2367 
2368  return $quotedTable;
2369  } else {
2370  return $quotedTable . ' ' . $this->addIdentifierQuotes( $alias );
2371  }
2372  }
2373 
2380  protected function tableNamesWithAlias( $tables ) {
2381  $retval = [];
2382  foreach ( $tables as $alias => $table ) {
2383  if ( is_numeric( $alias ) ) {
2384  $alias = $table;
2385  }
2386  $retval[] = $this->tableNameWithAlias( $table, $alias );
2387  }
2388 
2389  return $retval;
2390  }
2391 
2400  protected function fieldNameWithAlias( $name, $alias = false ) {
2401  if ( !$alias || (string)$alias === (string)$name ) {
2402  return $name;
2403  } else {
2404  return $name . ' AS ' . $this->addIdentifierQuotes( $alias ); // PostgreSQL needs AS
2405  }
2406  }
2407 
2414  protected function fieldNamesWithAlias( $fields ) {
2415  $retval = [];
2416  foreach ( $fields as $alias => $field ) {
2417  if ( is_numeric( $alias ) ) {
2418  $alias = $field;
2419  }
2420  $retval[] = $this->fieldNameWithAlias( $field, $alias );
2421  }
2422 
2423  return $retval;
2424  }
2425 
2437  $tables, $use_index = [], $ignore_index = [], $join_conds = []
2438  ) {
2439  $ret = [];
2440  $retJOIN = [];
2441  $use_index = (array)$use_index;
2442  $ignore_index = (array)$ignore_index;
2443  $join_conds = (array)$join_conds;
2444 
2445  foreach ( $tables as $alias => $table ) {
2446  if ( !is_string( $alias ) ) {
2447  // No alias? Set it equal to the table name
2448  $alias = $table;
2449  }
2450 
2451  if ( is_array( $table ) ) {
2452  // A parenthesized group
2453  if ( count( $table ) > 1 ) {
2454  $joinedTable = '(' .
2456  $table, $use_index, $ignore_index, $join_conds ) . ')';
2457  } else {
2458  // Degenerate case
2459  $innerTable = reset( $table );
2460  $innerAlias = key( $table );
2461  $joinedTable = $this->tableNameWithAlias(
2462  $innerTable,
2463  is_string( $innerAlias ) ? $innerAlias : $innerTable
2464  );
2465  }
2466  } else {
2467  $joinedTable = $this->tableNameWithAlias( $table, $alias );
2468  }
2469 
2470  // Is there a JOIN clause for this table?
2471  if ( isset( $join_conds[$alias] ) ) {
2472  list( $joinType, $conds ) = $join_conds[$alias];
2473  $tableClause = $joinType;
2474  $tableClause .= ' ' . $joinedTable;
2475  if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
2476  $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
2477  if ( $use != '' ) {
2478  $tableClause .= ' ' . $use;
2479  }
2480  }
2481  if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX?
2482  $ignore = $this->ignoreIndexClause(
2483  implode( ',', (array)$ignore_index[$alias] ) );
2484  if ( $ignore != '' ) {
2485  $tableClause .= ' ' . $ignore;
2486  }
2487  }
2488  $on = $this->makeList( (array)$conds, self::LIST_AND );
2489  if ( $on != '' ) {
2490  $tableClause .= ' ON (' . $on . ')';
2491  }
2492 
2493  $retJOIN[] = $tableClause;
2494  } elseif ( isset( $use_index[$alias] ) ) {
2495  // Is there an INDEX clause for this table?
2496  $tableClause = $joinedTable;
2497  $tableClause .= ' ' . $this->useIndexClause(
2498  implode( ',', (array)$use_index[$alias] )
2499  );
2500 
2501  $ret[] = $tableClause;
2502  } elseif ( isset( $ignore_index[$alias] ) ) {
2503  // Is there an INDEX clause for this table?
2504  $tableClause = $joinedTable;
2505  $tableClause .= ' ' . $this->ignoreIndexClause(
2506  implode( ',', (array)$ignore_index[$alias] )
2507  );
2508 
2509  $ret[] = $tableClause;
2510  } else {
2511  $tableClause = $joinedTable;
2512 
2513  $ret[] = $tableClause;
2514  }
2515  }
2516 
2517  // We can't separate explicit JOIN clauses with ',', use ' ' for those
2518  $implicitJoins = $ret ? implode( ',', $ret ) : "";
2519  $explicitJoins = $retJOIN ? implode( ' ', $retJOIN ) : "";
2520 
2521  // Compile our final table clause
2522  return implode( ' ', [ $implicitJoins, $explicitJoins ] );
2523  }
2524 
2531  protected function indexName( $index ) {
2532  return isset( $this->indexAliases[$index] )
2533  ? $this->indexAliases[$index]
2534  : $index;
2535  }
2536 
2537  public function addQuotes( $s ) {
2538  if ( $s instanceof Blob ) {
2539  $s = $s->fetch();
2540  }
2541  if ( $s === null ) {
2542  return 'NULL';
2543  } elseif ( is_bool( $s ) ) {
2544  return (int)$s;
2545  } else {
2546  # This will also quote numeric values. This should be harmless,
2547  # and protects against weird problems that occur when they really
2548  # _are_ strings such as article titles and string->number->string
2549  # conversion is not 1:1.
2550  return "'" . $this->strencode( $s ) . "'";
2551  }
2552  }
2553 
2563  public function addIdentifierQuotes( $s ) {
2564  return '"' . str_replace( '"', '""', $s ) . '"';
2565  }
2566 
2576  public function isQuotedIdentifier( $name ) {
2577  return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
2578  }
2579 
2585  protected function escapeLikeInternal( $s, $escapeChar = '`' ) {
2586  return str_replace( [ $escapeChar, '%', '_' ],
2587  [ "{$escapeChar}{$escapeChar}", "{$escapeChar}%", "{$escapeChar}_" ],
2588  $s );
2589  }
2590 
2591  public function buildLike() {
2592  $params = func_get_args();
2593 
2594  if ( count( $params ) > 0 && is_array( $params[0] ) ) {
2595  $params = $params[0];
2596  }
2597 
2598  $s = '';
2599 
2600  // We use ` instead of \ as the default LIKE escape character, since addQuotes()
2601  // may escape backslashes, creating problems of double escaping. The `
2602  // character has good cross-DBMS compatibility, avoiding special operators
2603  // in MS SQL like ^ and %
2604  $escapeChar = '`';
2605 
2606  foreach ( $params as $value ) {
2607  if ( $value instanceof LikeMatch ) {
2608  $s .= $value->toString();
2609  } else {
2610  $s .= $this->escapeLikeInternal( $value, $escapeChar );
2611  }
2612  }
2613 
2614  return ' LIKE ' .
2615  $this->addQuotes( $s ) . ' ESCAPE ' . $this->addQuotes( $escapeChar ) . ' ';
2616  }
2617 
2618  public function anyChar() {
2619  return new LikeMatch( '_' );
2620  }
2621 
2622  public function anyString() {
2623  return new LikeMatch( '%' );
2624  }
2625 
2626  public function nextSequenceValue( $seqName ) {
2627  return null;
2628  }
2629 
2640  public function useIndexClause( $index ) {
2641  return '';
2642  }
2643 
2654  public function ignoreIndexClause( $index ) {
2655  return '';
2656  }
2657 
2658  public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
2659  if ( count( $rows ) == 0 ) {
2660  return;
2661  }
2662 
2663  // Single row case
2664  if ( !is_array( reset( $rows ) ) ) {
2665  $rows = [ $rows ];
2666  }
2667 
2668  try {
2669  $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
2670  $affectedRowCount = 0;
2671  foreach ( $rows as $row ) {
2672  // Delete rows which collide with this one
2673  $indexWhereClauses = [];
2674  foreach ( $uniqueIndexes as $index ) {
2675  $indexColumns = (array)$index;
2676  $indexRowValues = array_intersect_key( $row, array_flip( $indexColumns ) );
2677  if ( count( $indexRowValues ) != count( $indexColumns ) ) {
2678  throw new DBUnexpectedError(
2679  $this,
2680  'New record does not provide all values for unique key (' .
2681  implode( ', ', $indexColumns ) . ')'
2682  );
2683  } elseif ( in_array( null, $indexRowValues, true ) ) {
2684  throw new DBUnexpectedError(
2685  $this,
2686  'New record has a null value for unique key (' .
2687  implode( ', ', $indexColumns ) . ')'
2688  );
2689  }
2690  $indexWhereClauses[] = $this->makeList( $indexRowValues, LIST_AND );
2691  }
2692 
2693  if ( $indexWhereClauses ) {
2694  $this->delete( $table, $this->makeList( $indexWhereClauses, LIST_OR ), $fname );
2695  $affectedRowCount += $this->affectedRows();
2696  }
2697 
2698  // Now insert the row
2699  $this->insert( $table, $row, $fname );
2700  $affectedRowCount += $this->affectedRows();
2701  }
2702  $this->endAtomic( $fname );
2703  $this->affectedRowCount = $affectedRowCount;
2704  } catch ( Exception $e ) {
2705  $this->cancelAtomic( $fname );
2706  throw $e;
2707  }
2708  }
2709 
2720  protected function nativeReplace( $table, $rows, $fname ) {
2721  $table = $this->tableName( $table );
2722 
2723  # Single row case
2724  if ( !is_array( reset( $rows ) ) ) {
2725  $rows = [ $rows ];
2726  }
2727 
2728  $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
2729  $first = true;
2730 
2731  foreach ( $rows as $row ) {
2732  if ( $first ) {
2733  $first = false;
2734  } else {
2735  $sql .= ',';
2736  }
2737 
2738  $sql .= '(' . $this->makeList( $row ) . ')';
2739  }
2740 
2741  return $this->query( $sql, $fname );
2742  }
2743 
2744  public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
2745  $fname = __METHOD__
2746  ) {
2747  if ( !count( $rows ) ) {
2748  return true; // nothing to do
2749  }
2750 
2751  if ( !is_array( reset( $rows ) ) ) {
2752  $rows = [ $rows ];
2753  }
2754 
2755  if ( count( $uniqueIndexes ) ) {
2756  $clauses = []; // list WHERE clauses that each identify a single row
2757  foreach ( $rows as $row ) {
2758  foreach ( $uniqueIndexes as $index ) {
2759  $index = is_array( $index ) ? $index : [ $index ]; // columns
2760  $rowKey = []; // unique key to this row
2761  foreach ( $index as $column ) {
2762  $rowKey[$column] = $row[$column];
2763  }
2764  $clauses[] = $this->makeList( $rowKey, self::LIST_AND );
2765  }
2766  }
2767  $where = [ $this->makeList( $clauses, self::LIST_OR ) ];
2768  } else {
2769  $where = false;
2770  }
2771 
2772  $affectedRowCount = 0;
2773  try {
2774  $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
2775  # Update any existing conflicting row(s)
2776  if ( $where !== false ) {
2777  $ok = $this->update( $table, $set, $where, $fname );
2778  $affectedRowCount += $this->affectedRows();
2779  } else {
2780  $ok = true;
2781  }
2782  # Now insert any non-conflicting row(s)
2783  $ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok;
2784  $affectedRowCount += $this->affectedRows();
2785  $this->endAtomic( $fname );
2786  $this->affectedRowCount = $affectedRowCount;
2787  } catch ( Exception $e ) {
2788  $this->cancelAtomic( $fname );
2789  throw $e;
2790  }
2791 
2792  return $ok;
2793  }
2794 
2795  public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
2796  $fname = __METHOD__
2797  ) {
2798  if ( !$conds ) {
2799  throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
2800  }
2801 
2802  $delTable = $this->tableName( $delTable );
2803  $joinTable = $this->tableName( $joinTable );
2804  $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
2805  if ( $conds != '*' ) {
2806  $sql .= 'WHERE ' . $this->makeList( $conds, self::LIST_AND );
2807  }
2808  $sql .= ')';
2809 
2810  $this->query( $sql, $fname );
2811  }
2812 
2813  public function textFieldSize( $table, $field ) {
2814  $table = $this->tableName( $table );
2815  $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
2816  $res = $this->query( $sql, __METHOD__ );
2817  $row = $this->fetchObject( $res );
2818 
2819  $m = [];
2820 
2821  if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
2822  $size = $m[1];
2823  } else {
2824  $size = -1;
2825  }
2826 
2827  return $size;
2828  }
2829 
2830  public function delete( $table, $conds, $fname = __METHOD__ ) {
2831  if ( !$conds ) {
2832  throw new DBUnexpectedError( $this, __METHOD__ . ' called with no conditions' );
2833  }
2834 
2835  $table = $this->tableName( $table );
2836  $sql = "DELETE FROM $table";
2837 
2838  if ( $conds != '*' ) {
2839  if ( is_array( $conds ) ) {
2840  $conds = $this->makeList( $conds, self::LIST_AND );
2841  }
2842  $sql .= ' WHERE ' . $conds;
2843  }
2844 
2845  return $this->query( $sql, $fname );
2846  }
2847 
2848  final public function insertSelect(
2849  $destTable, $srcTable, $varMap, $conds,
2850  $fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
2851  ) {
2852  static $hints = [ 'NO_AUTO_COLUMNS' ];
2853 
2854  $insertOptions = (array)$insertOptions;
2855  $selectOptions = (array)$selectOptions;
2856 
2857  if ( $this->cliMode && $this->isInsertSelectSafe( $insertOptions, $selectOptions ) ) {
2858  // For massive migrations with downtime, we don't want to select everything
2859  // into memory and OOM, so do all this native on the server side if possible.
2860  return $this->nativeInsertSelect(
2861  $destTable,
2862  $srcTable,
2863  $varMap,
2864  $conds,
2865  $fname,
2866  array_diff( $insertOptions, $hints ),
2867  $selectOptions,
2868  $selectJoinConds
2869  );
2870  }
2871 
2872  return $this->nonNativeInsertSelect(
2873  $destTable,
2874  $srcTable,
2875  $varMap,
2876  $conds,
2877  $fname,
2878  array_diff( $insertOptions, $hints ),
2879  $selectOptions,
2880  $selectJoinConds
2881  );
2882  }
2883 
2890  protected function isInsertSelectSafe( array $insertOptions, array $selectOptions ) {
2891  return true;
2892  }
2893 
2909  protected function nonNativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
2910  $fname = __METHOD__,
2911  $insertOptions = [], $selectOptions = [], $selectJoinConds = []
2912  ) {
2913  // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
2914  // on only the master (without needing row-based-replication). It also makes it easy to
2915  // know how big the INSERT is going to be.
2916  $fields = [];
2917  foreach ( $varMap as $dstColumn => $sourceColumnOrSql ) {
2918  $fields[] = $this->fieldNameWithAlias( $sourceColumnOrSql, $dstColumn );
2919  }
2920  $selectOptions[] = 'FOR UPDATE';
2921  $res = $this->select(
2922  $srcTable, implode( ',', $fields ), $conds, $fname, $selectOptions, $selectJoinConds
2923  );
2924  if ( !$res ) {
2925  return false;
2926  }
2927 
2928  try {
2929  $affectedRowCount = 0;
2930  $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
2931  $rows = [];
2932  $ok = true;
2933  foreach ( $res as $row ) {
2934  $rows[] = (array)$row;
2935 
2936  // Avoid inserts that are too huge
2938  $ok = $this->insert( $destTable, $rows, $fname, $insertOptions );
2939  if ( !$ok ) {
2940  break;
2941  }
2942  $affectedRowCount += $this->affectedRows();
2943  $rows = [];
2944  }
2945  }
2946  if ( $rows && $ok ) {
2947  $ok = $this->insert( $destTable, $rows, $fname, $insertOptions );
2948  if ( $ok ) {
2949  $affectedRowCount += $this->affectedRows();
2950  }
2951  }
2952  if ( $ok ) {
2953  $this->endAtomic( $fname );
2954  $this->affectedRowCount = $affectedRowCount;
2955  } else {
2956  $this->cancelAtomic( $fname );
2957  }
2958  return $ok;
2959  } catch ( Exception $e ) {
2960  $this->cancelAtomic( $fname );
2961  throw $e;
2962  }
2963  }
2964 
2980  protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
2981  $fname = __METHOD__,
2982  $insertOptions = [], $selectOptions = [], $selectJoinConds = []
2983  ) {
2984  $destTable = $this->tableName( $destTable );
2985 
2986  if ( !is_array( $insertOptions ) ) {
2987  $insertOptions = [ $insertOptions ];
2988  }
2989 
2990  $insertOptions = $this->makeInsertOptions( $insertOptions );
2991 
2992  $selectSql = $this->selectSQLText(
2993  $srcTable,
2994  array_values( $varMap ),
2995  $conds,
2996  $fname,
2997  $selectOptions,
2998  $selectJoinConds
2999  );
3000 
3001  $sql = "INSERT $insertOptions" .
3002  " INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' .
3003  $selectSql;
3004 
3005  return $this->query( $sql, $fname );
3006  }
3007 
3027  public function limitResult( $sql, $limit, $offset = false ) {
3028  if ( !is_numeric( $limit ) ) {
3029  throw new DBUnexpectedError( $this,
3030  "Invalid non-numeric limit passed to limitResult()\n" );
3031  }
3032 
3033  return "$sql LIMIT "
3034  . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
3035  . "{$limit} ";
3036  }
3037 
3038  public function unionSupportsOrderAndLimit() {
3039  return true; // True for almost every DB supported
3040  }
3041 
3042  public function unionQueries( $sqls, $all ) {
3043  $glue = $all ? ') UNION ALL (' : ') UNION (';
3044 
3045  return '(' . implode( $glue, $sqls ) . ')';
3046  }
3047 
3049  $table, $vars, array $permute_conds, $extra_conds = '', $fname = __METHOD__,
3050  $options = [], $join_conds = []
3051  ) {
3052  // First, build the Cartesian product of $permute_conds
3053  $conds = [ [] ];
3054  foreach ( $permute_conds as $field => $values ) {
3055  if ( !$values ) {
3056  // Skip empty $values
3057  continue;
3058  }
3059  $values = array_unique( $values ); // For sanity
3060  $newConds = [];
3061  foreach ( $conds as $cond ) {
3062  foreach ( $values as $value ) {
3063  $cond[$field] = $value;
3064  $newConds[] = $cond; // Arrays are by-value, not by-reference, so this works
3065  }
3066  }
3067  $conds = $newConds;
3068  }
3069 
3070  $extra_conds = $extra_conds === '' ? [] : (array)$extra_conds;
3071 
3072  // If there's just one condition and no subordering, hand off to
3073  // selectSQLText directly.
3074  if ( count( $conds ) === 1 &&
3075  ( !isset( $options['INNER ORDER BY'] ) || !$this->unionSupportsOrderAndLimit() )
3076  ) {
3077  return $this->selectSQLText(
3078  $table, $vars, $conds[0] + $extra_conds, $fname, $options, $join_conds
3079  );
3080  }
3081 
3082  // Otherwise, we need to pull out the order and limit to apply after
3083  // the union. Then build the SQL queries for each set of conditions in
3084  // $conds. Then union them together (using UNION ALL, because the
3085  // product *should* already be distinct).
3086  $orderBy = $this->makeOrderBy( $options );
3087  $limit = isset( $options['LIMIT'] ) ? $options['LIMIT'] : null;
3088  $offset = isset( $options['OFFSET'] ) ? $options['OFFSET'] : false;
3089  $all = empty( $options['NOTALL'] ) && !in_array( 'NOTALL', $options );
3090  if ( !$this->unionSupportsOrderAndLimit() ) {
3091  unset( $options['ORDER BY'], $options['LIMIT'], $options['OFFSET'] );
3092  } else {
3093  if ( array_key_exists( 'INNER ORDER BY', $options ) ) {
3094  $options['ORDER BY'] = $options['INNER ORDER BY'];
3095  }
3096  if ( $limit !== null && is_numeric( $offset ) && $offset != 0 ) {
3097  // We need to increase the limit by the offset rather than
3098  // using the offset directly, otherwise it'll skip incorrectly
3099  // in the subqueries.
3100  $options['LIMIT'] = $limit + $offset;
3101  unset( $options['OFFSET'] );
3102  }
3103  }
3104 
3105  $sqls = [];
3106  foreach ( $conds as $cond ) {
3107  $sqls[] = $this->selectSQLText(
3108  $table, $vars, $cond + $extra_conds, $fname, $options, $join_conds
3109  );
3110  }
3111  $sql = $this->unionQueries( $sqls, $all ) . $orderBy;
3112  if ( $limit !== null ) {
3113  $sql = $this->limitResult( $sql, $limit, $offset );
3114  }
3115 
3116  return $sql;
3117  }
3118 
3119  public function conditional( $cond, $trueVal, $falseVal ) {
3120  if ( is_array( $cond ) ) {
3121  $cond = $this->makeList( $cond, self::LIST_AND );
3122  }
3123 
3124  return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
3125  }
3126 
3127  public function strreplace( $orig, $old, $new ) {
3128  return "REPLACE({$orig}, {$old}, {$new})";
3129  }
3130 
3131  public function getServerUptime() {
3132  return 0;
3133  }
3134 
3135  public function wasDeadlock() {
3136  return false;
3137  }
3138 
3139  public function wasLockTimeout() {
3140  return false;
3141  }
3142 
3143  public function wasConnectionLoss() {
3144  return $this->wasConnectionError( $this->lastErrno() );
3145  }
3146 
3147  public function wasReadOnlyError() {
3148  return false;
3149  }
3150 
3151  public function wasErrorReissuable() {
3152  return (
3153  $this->wasDeadlock() ||
3154  $this->wasLockTimeout() ||
3155  $this->wasConnectionLoss()
3156  );
3157  }
3158 
3165  public function wasConnectionError( $errno ) {
3166  return false;
3167  }
3168 
3175  protected function wasKnownStatementRollbackError() {
3176  return false; // don't know; it could have caused a transaction rollback
3177  }
3178 
3179  public function deadlockLoop() {
3180  $args = func_get_args();
3181  $function = array_shift( $args );
3182  $tries = self::DEADLOCK_TRIES;
3183 
3184  $this->begin( __METHOD__ );
3185 
3186  $retVal = null;
3188  $e = null;
3189  do {
3190  try {
3191  $retVal = call_user_func_array( $function, $args );
3192  break;
3193  } catch ( DBQueryError $e ) {
3194  if ( $this->wasDeadlock() ) {
3195  // Retry after a randomized delay
3196  usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
3197  } else {
3198  // Throw the error back up
3199  throw $e;
3200  }
3201  }
3202  } while ( --$tries > 0 );
3203 
3204  if ( $tries <= 0 ) {
3205  // Too many deadlocks; give up
3206  $this->rollback( __METHOD__ );
3207  throw $e;
3208  } else {
3209  $this->commit( __METHOD__ );
3210 
3211  return $retVal;
3212  }
3213  }
3214 
3215  public function masterPosWait( DBMasterPos $pos, $timeout ) {
3216  # Real waits are implemented in the subclass.
3217  return 0;
3218  }
3219 
3220  public function getReplicaPos() {
3221  # Stub
3222  return false;
3223  }
3224 
3225  public function getMasterPos() {
3226  # Stub
3227  return false;
3228  }
3229 
3230  public function serverIsReadOnly() {
3231  return false;
3232  }
3233 
3234  final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
3235  if ( !$this->trxLevel ) {
3236  throw new DBUnexpectedError( $this, "No transaction is active." );
3237  }
3238  $this->trxEndCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
3239  }
3240 
3241  final public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
3242  if ( !$this->trxLevel && $this->getTransactionRoundId() ) {
3243  // Start an implicit transaction similar to how query() does
3244  $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
3245  $this->trxAutomatic = true;
3246  }
3247 
3248  $this->trxIdleCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
3249  if ( !$this->trxLevel ) {
3250  $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
3251  }
3252  }
3253 
3254  final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
3255  if ( !$this->trxLevel && $this->getTransactionRoundId() ) {
3256  // Start an implicit transaction similar to how query() does
3257  $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
3258  $this->trxAutomatic = true;
3259  }
3260 
3261  if ( $this->trxLevel ) {
3262  $this->trxPreCommitCallbacks[] = [ $callback, $fname, $this->currentAtomicSectionId() ];
3263  } else {
3264  // No transaction is active nor will start implicitly, so make one for this callback
3265  $this->startAtomic( __METHOD__, self::ATOMIC_CANCELABLE );
3266  try {
3267  call_user_func( $callback );
3268  $this->endAtomic( __METHOD__ );
3269  } catch ( Exception $e ) {
3270  $this->cancelAtomic( __METHOD__ );
3271  throw $e;
3272  }
3273  }
3274  }
3275 
3279  private function currentAtomicSectionId() {
3280  if ( $this->trxLevel && $this->trxAtomicLevels ) {
3281  $levelInfo = end( $this->trxAtomicLevels );
3282 
3283  return $levelInfo[1];
3284  }
3285 
3286  return null;
3287  }
3288 
3295  ) {
3296  foreach ( $this->trxPreCommitCallbacks as $key => $info ) {
3297  if ( $info[2] === $old ) {
3298  $this->trxPreCommitCallbacks[$key][2] = $new;
3299  }
3300  }
3301  foreach ( $this->trxIdleCallbacks as $key => $info ) {
3302  if ( $info[2] === $old ) {
3303  $this->trxIdleCallbacks[$key][2] = $new;
3304  }
3305  }
3306  foreach ( $this->trxEndCallbacks as $key => $info ) {
3307  if ( $info[2] === $old ) {
3308  $this->trxEndCallbacks[$key][2] = $new;
3309  }
3310  }
3311  }
3312 
3317  private function modifyCallbacksForCancel( array $sectionIds ) {
3318  // Cancel the "on commit" callbacks owned by this savepoint
3319  $this->trxIdleCallbacks = array_filter(
3320  $this->trxIdleCallbacks,
3321  function ( $entry ) use ( $sectionIds ) {
3322  return !in_array( $entry[2], $sectionIds, true );
3323  }
3324  );
3325  $this->trxPreCommitCallbacks = array_filter(
3326  $this->trxPreCommitCallbacks,
3327  function ( $entry ) use ( $sectionIds ) {
3328  return !in_array( $entry[2], $sectionIds, true );
3329  }
3330  );
3331  // Make "on resolution" callbacks owned by this savepoint to perceive a rollback
3332  foreach ( $this->trxEndCallbacks as $key => $entry ) {
3333  if ( in_array( $entry[2], $sectionIds, true ) ) {
3334  $callback = $entry[0];
3335  $this->trxEndCallbacks[$key][0] = function () use ( $callback ) {
3336  return $callback( self::TRIGGER_ROLLBACK );
3337  };
3338  }
3339  }
3340  }
3341 
3342  final public function setTransactionListener( $name, callable $callback = null ) {
3343  if ( $callback ) {
3344  $this->trxRecurringCallbacks[$name] = $callback;
3345  } else {
3346  unset( $this->trxRecurringCallbacks[$name] );
3347  }
3348  }
3349 
3358  final public function setTrxEndCallbackSuppression( $suppress ) {
3359  $this->trxEndCallbacksSuppressed = $suppress;
3360  }
3361 
3371  public function runOnTransactionIdleCallbacks( $trigger ) {
3372  if ( $this->trxEndCallbacksSuppressed ) {
3373  return;
3374  }
3375 
3376  $autoTrx = $this->getFlag( self::DBO_TRX ); // automatic begin() enabled?
3378  $e = null; // first exception
3379  do { // callbacks may add callbacks :)
3380  $callbacks = array_merge(
3381  $this->trxIdleCallbacks,
3382  $this->trxEndCallbacks // include "transaction resolution" callbacks
3383  );
3384  $this->trxIdleCallbacks = []; // consumed (and recursion guard)
3385  $this->trxEndCallbacks = []; // consumed (recursion guard)
3386  foreach ( $callbacks as $callback ) {
3387  try {
3388  list( $phpCallback ) = $callback;
3389  $this->clearFlag( self::DBO_TRX ); // make each query its own transaction
3390  call_user_func_array( $phpCallback, [ $trigger ] );
3391  if ( $autoTrx ) {
3392  $this->setFlag( self::DBO_TRX ); // restore automatic begin()
3393  } else {
3394  $this->clearFlag( self::DBO_TRX ); // restore auto-commit
3395  }
3396  } catch ( Exception $ex ) {
3397  call_user_func( $this->errorLogger, $ex );
3398  $e = $e ?: $ex;
3399  // Some callbacks may use startAtomic/endAtomic, so make sure
3400  // their transactions are ended so other callbacks don't fail
3401  if ( $this->trxLevel() ) {
3402  $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
3403  }
3404  }
3405  }
3406  } while ( count( $this->trxIdleCallbacks ) );
3407 
3408  if ( $e instanceof Exception ) {
3409  throw $e; // re-throw any first exception
3410  }
3411  }
3412 
3422  $e = null; // first exception
3423  do { // callbacks may add callbacks :)
3424  $callbacks = $this->trxPreCommitCallbacks;
3425  $this->trxPreCommitCallbacks = []; // consumed (and recursion guard)
3426  foreach ( $callbacks as $callback ) {
3427  try {
3428  list( $phpCallback ) = $callback;
3429  call_user_func( $phpCallback );
3430  } catch ( Exception $ex ) {
3431  call_user_func( $this->errorLogger, $ex );
3432  $e = $e ?: $ex;
3433  }
3434  }
3435  } while ( count( $this->trxPreCommitCallbacks ) );
3436 
3437  if ( $e instanceof Exception ) {
3438  throw $e; // re-throw any first exception
3439  }
3440  }
3441 
3451  public function runTransactionListenerCallbacks( $trigger ) {
3452  if ( $this->trxEndCallbacksSuppressed ) {
3453  return;
3454  }
3455 
3457  $e = null; // first exception
3458 
3459  foreach ( $this->trxRecurringCallbacks as $phpCallback ) {
3460  try {
3461  $phpCallback( $trigger, $this );
3462  } catch ( Exception $ex ) {
3463  call_user_func( $this->errorLogger, $ex );
3464  $e = $e ?: $ex;
3465  }
3466  }
3467 
3468  if ( $e instanceof Exception ) {
3469  throw $e; // re-throw any first exception
3470  }
3471  }
3472 
3483  protected function doSavepoint( $identifier, $fname ) {
3484  $this->query( 'SAVEPOINT ' . $this->addIdentifierQuotes( $identifier ), $fname );
3485  }
3486 
3497  protected function doReleaseSavepoint( $identifier, $fname ) {
3498  $this->query( 'RELEASE SAVEPOINT ' . $this->addIdentifierQuotes( $identifier ), $fname );
3499  }
3500 
3511  protected function doRollbackToSavepoint( $identifier, $fname ) {
3512  $this->query( 'ROLLBACK TO SAVEPOINT ' . $this->addIdentifierQuotes( $identifier ), $fname );
3513  }
3514 
3519  private function nextSavepointId( $fname ) {
3520  $savepointId = self::$SAVEPOINT_PREFIX . ++$this->trxAtomicCounter;
3521  if ( strlen( $savepointId ) > 30 ) {
3522  // 30 == Oracle's identifier length limit (pre 12c)
3523  // With a 22 character prefix, that puts the highest number at 99999999.
3524  throw new DBUnexpectedError(
3525  $this,
3526  'There have been an excessively large number of atomic sections in a transaction'
3527  . " started by $this->trxFname (at $fname)"
3528  );
3529  }
3530 
3531  return $savepointId;
3532  }
3533 
3534  final public function startAtomic(
3535  $fname = __METHOD__, $cancelable = self::ATOMIC_NOT_CANCELABLE
3536  ) {
3537  $savepointId = $cancelable === self::ATOMIC_CANCELABLE ? self::$NOT_APPLICABLE : null;
3538 
3539  if ( !$this->trxLevel ) {
3540  $this->begin( $fname, self::TRANSACTION_INTERNAL );
3541  // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
3542  // in all changes being in one transaction to keep requests transactional.
3543  if ( $this->getFlag( self::DBO_TRX ) ) {
3544  // Since writes could happen in between the topmost atomic sections as part
3545  // of the transaction, those sections will need savepoints.
3546  $savepointId = $this->nextSavepointId( $fname );
3547  $this->doSavepoint( $savepointId, $fname );
3548  } else {
3549  $this->trxAutomaticAtomic = true;
3550  }
3551  } elseif ( $cancelable === self::ATOMIC_CANCELABLE ) {
3552  $savepointId = $this->nextSavepointId( $fname );
3553  $this->doSavepoint( $savepointId, $fname );
3554  }
3555 
3556  $sectionId = new AtomicSectionIdentifier;
3557  $this->trxAtomicLevels[] = [ $fname, $sectionId, $savepointId ];
3558 
3559  return $sectionId;
3560  }
3561 
3562  final public function endAtomic( $fname = __METHOD__ ) {
3563  if ( !$this->trxLevel || !$this->trxAtomicLevels ) {
3564  throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
3565  }
3566 
3567  // Check if the current section matches $fname
3568  $pos = count( $this->trxAtomicLevels ) - 1;
3569  list( $savedFname, $sectionId, $savepointId ) = $this->trxAtomicLevels[$pos];
3570 
3571  if ( $savedFname !== $fname ) {
3572  throw new DBUnexpectedError(
3573  $this,
3574  "Invalid atomic section ended (got $fname but expected $savedFname)."
3575  );
3576  }
3577 
3578  // Remove the last section (no need to re-index the array)
3579  array_pop( $this->trxAtomicLevels );
3580 
3581  if ( !$this->trxAtomicLevels && $this->trxAutomaticAtomic ) {
3582  $this->commit( $fname, self::FLUSHING_INTERNAL );
3583  } elseif ( $savepointId !== null && $savepointId !== self::$NOT_APPLICABLE ) {
3584  $this->doReleaseSavepoint( $savepointId, $fname );
3585  }
3586 
3587  // Hoist callback ownership for callbacks in the section that just ended;
3588  // all callbacks should have an owner that is present in trxAtomicLevels.
3589  $currentSectionId = $this->currentAtomicSectionId();
3590  if ( $currentSectionId ) {
3591  $this->reassignCallbacksForSection( $sectionId, $currentSectionId );
3592  }
3593  }
3594 
3595  final public function cancelAtomic(
3596  $fname = __METHOD__, AtomicSectionIdentifier $sectionId = null
3597  ) {
3598  if ( !$this->trxLevel || !$this->trxAtomicLevels ) {
3599  throw new DBUnexpectedError( $this, "No atomic section is open (got $fname)." );
3600  }
3601 
3602  if ( $sectionId !== null ) {
3603  // Find the (last) section with the given $sectionId
3604  $pos = -1;
3605  foreach ( $this->trxAtomicLevels as $i => list( $asFname, $asId, $spId ) ) {
3606  if ( $asId === $sectionId ) {
3607  $pos = $i;
3608  }
3609  }
3610  if ( $pos < 0 ) {
3611  throw new DBUnexpectedError( "Atomic section not found (for $fname)" );
3612  }
3613  // Remove all descendant sections and re-index the array
3614  $excisedIds = [];
3615  $len = count( $this->trxAtomicLevels );
3616  for ( $i = $pos + 1; $i < $len; ++$i ) {
3617  $excisedIds[] = $this->trxAtomicLevels[$i][1];
3618  }
3619  $this->trxAtomicLevels = array_slice( $this->trxAtomicLevels, 0, $pos + 1 );
3620  $this->modifyCallbacksForCancel( $excisedIds );
3621  }
3622 
3623  // Check if the current section matches $fname
3624  $pos = count( $this->trxAtomicLevels ) - 1;
3625  list( $savedFname, $savedSectionId, $savepointId ) = $this->trxAtomicLevels[$pos];
3626 
3627  if ( $savedFname !== $fname ) {
3628  throw new DBUnexpectedError(
3629  $this,
3630  "Invalid atomic section ended (got $fname but expected $savedFname)."
3631  );
3632  }
3633 
3634  // Remove the last section (no need to re-index the array)
3635  array_pop( $this->trxAtomicLevels );
3636  $this->modifyCallbacksForCancel( [ $savedSectionId ] );
3637 
3638  if ( $savepointId !== null ) {
3639  // Rollback the transaction to the state just before this atomic section
3640  if ( $savepointId === self::$NOT_APPLICABLE ) {
3641  $this->rollback( $fname, self::FLUSHING_INTERNAL );
3642  } else {
3643  $this->doRollbackToSavepoint( $savepointId, $fname );
3644  $this->trxStatus = self::STATUS_TRX_OK; // no exception; recovered
3645  $this->trxStatusIgnoredCause = null;
3646  }
3647  } elseif ( $this->trxStatus > self::STATUS_TRX_ERROR ) {
3648  // Put the transaction into an error state if it's not already in one
3649  $this->trxStatus = self::STATUS_TRX_ERROR;
3650  $this->trxStatusCause = new DBUnexpectedError(
3651  $this,
3652  "Uncancelable atomic section canceled (got $fname)."
3653  );
3654  }
3655 
3656  $this->affectedRowCount = 0; // for the sake of consistency
3657  }
3658 
3659  final public function doAtomicSection(
3660  $fname, callable $callback, $cancelable = self::ATOMIC_NOT_CANCELABLE
3661  ) {
3662  $sectionId = $this->startAtomic( $fname, $cancelable );
3663  try {
3664  $res = call_user_func_array( $callback, [ $this, $fname ] );
3665  } catch ( Exception $e ) {
3666  $this->cancelAtomic( $fname, $sectionId );
3667 
3668  throw $e;
3669  }
3670  $this->endAtomic( $fname );
3671 
3672  return $res;
3673  }
3674 
3675  final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
3676  static $modes = [ self::TRANSACTION_EXPLICIT, self::TRANSACTION_INTERNAL ];
3677  if ( !in_array( $mode, $modes, true ) ) {
3678  throw new DBUnexpectedError( $this, "$fname: invalid mode parameter '$mode'." );
3679  }
3680 
3681  // Protect against mismatched atomic section, transaction nesting, and snapshot loss
3682  if ( $this->trxLevel ) {
3683  if ( $this->trxAtomicLevels ) {
3684  $levels = $this->flatAtomicSectionList();
3685  $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
3686  throw new DBUnexpectedError( $this, $msg );
3687  } elseif ( !$this->trxAutomatic ) {
3688  $msg = "$fname: Explicit transaction already active (from {$this->trxFname}).";
3689  throw new DBUnexpectedError( $this, $msg );
3690  } else {
3691  $msg = "$fname: Implicit transaction already active (from {$this->trxFname}).";
3692  throw new DBUnexpectedError( $this, $msg );
3693  }
3694  } elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
3695  $msg = "$fname: Implicit transaction expected (DBO_TRX set).";
3696  throw new DBUnexpectedError( $this, $msg );
3697  }
3698 
3699  // Avoid fatals if close() was called
3700  $this->assertOpen();
3701 
3702  $this->doBegin( $fname );
3703  $this->trxStatus = self::STATUS_TRX_OK;
3704  $this->trxStatusIgnoredCause = null;
3705  $this->trxAtomicCounter = 0;
3706  $this->trxTimestamp = microtime( true );
3707  $this->trxFname = $fname;
3708  $this->trxDoneWrites = false;
3709  $this->trxAutomaticAtomic = false;
3710  $this->trxAtomicLevels = [];
3711  $this->trxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
3712  $this->trxWriteDuration = 0.0;
3713  $this->trxWriteQueryCount = 0;
3714  $this->trxWriteAffectedRows = 0;
3715  $this->trxWriteAdjDuration = 0.0;
3716  $this->trxWriteAdjQueryCount = 0;
3717  $this->trxWriteCallers = [];
3718  // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
3719  // Get an estimate of the replication lag before any such queries.
3720  $this->trxReplicaLag = null; // clear cached value first
3721  $this->trxReplicaLag = $this->getApproximateLagStatus()['lag'];
3722  // T147697: make explicitTrxActive() return true until begin() finishes. This way, no
3723  // caller will think its OK to muck around with the transaction just because startAtomic()
3724  // has not yet completed (e.g. setting trxAtomicLevels).
3725  $this->trxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
3726  }
3727 
3734  protected function doBegin( $fname ) {
3735  $this->query( 'BEGIN', $fname );
3736  $this->trxLevel = 1;
3737  }
3738 
3739  final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
3740  static $modes = [ self::FLUSHING_ONE, self::FLUSHING_ALL_PEERS, self::FLUSHING_INTERNAL ];
3741  if ( !in_array( $flush, $modes, true ) ) {
3742  throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'." );
3743  }
3744 
3745  if ( $this->trxLevel && $this->trxAtomicLevels ) {
3746  // There are still atomic sections open; this cannot be ignored
3747  $levels = $this->flatAtomicSectionList();
3748  throw new DBUnexpectedError(
3749  $this,
3750  "$fname: Got COMMIT while atomic sections $levels are still open."
3751  );
3752  }
3753 
3754  if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
3755  if ( !$this->trxLevel ) {
3756  return; // nothing to do
3757  } elseif ( !$this->trxAutomatic ) {
3758  throw new DBUnexpectedError(
3759  $this,
3760  "$fname: Flushing an explicit transaction, getting out of sync."
3761  );
3762  }
3763  } else {
3764  if ( !$this->trxLevel ) {
3765  $this->queryLogger->error(
3766  "$fname: No transaction to commit, something got out of sync." );
3767  return; // nothing to do
3768  } elseif ( $this->trxAutomatic ) {
3769  throw new DBUnexpectedError(
3770  $this,
3771  "$fname: Expected mass commit of all peer transactions (DBO_TRX set)."
3772  );
3773  }
3774  }
3775 
3776  // Avoid fatals if close() was called
3777  $this->assertOpen();
3778 
3780  $writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY );
3781  $this->doCommit( $fname );
3782  $this->trxStatus = self::STATUS_TRX_NONE;
3783  if ( $this->trxDoneWrites ) {
3784  $this->lastWriteTime = microtime( true );
3785  $this->trxProfiler->transactionWritingOut(
3786  $this->server,
3787  $this->dbName,
3788  $this->trxShortId,
3789  $writeTime,
3790  $this->trxWriteAffectedRows
3791  );
3792  }
3793 
3794  $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
3795  $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT );
3796  }
3797 
3804  protected function doCommit( $fname ) {
3805  if ( $this->trxLevel ) {
3806  $this->query( 'COMMIT', $fname );
3807  $this->trxLevel = 0;
3808  }
3809  }
3810 
3811  final public function rollback( $fname = __METHOD__, $flush = '' ) {
3812  $trxActive = $this->trxLevel;
3813 
3814  if ( $flush !== self::FLUSHING_INTERNAL && $flush !== self::FLUSHING_ALL_PEERS ) {
3815  if ( $this->getFlag( self::DBO_TRX ) ) {
3816  throw new DBUnexpectedError(
3817  $this,
3818  "$fname: Expected mass rollback of all peer transactions (DBO_TRX set)."
3819  );
3820  }
3821  }
3822 
3823  if ( $trxActive ) {
3824  // Avoid fatals if close() was called
3825  $this->assertOpen();
3826 
3827  $this->doRollback( $fname );
3828  $this->trxStatus = self::STATUS_TRX_NONE;
3829  $this->trxAtomicLevels = [];
3830  if ( $this->trxDoneWrites ) {
3831  $this->trxProfiler->transactionWritingOut(
3832  $this->server,
3833  $this->dbName,
3834  $this->trxShortId
3835  );
3836  }
3837  }
3838 
3839  // Clear any commit-dependant callbacks. They might even be present
3840  // only due to transaction rounds, with no SQL transaction being active
3841  $this->trxIdleCallbacks = [];
3842  $this->trxPreCommitCallbacks = [];
3843 
3844  if ( $trxActive ) {
3845  try {
3846  $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
3847  } catch ( Exception $e ) {
3848  // already logged; finish and let LoadBalancer move on during mass-rollback
3849  }
3850  try {
3851  $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
3852  } catch ( Exception $e ) {
3853  // already logged; let LoadBalancer move on during mass-rollback
3854  }
3855 
3856  $this->affectedRowCount = 0; // for the sake of consistency
3857  }
3858  }
3859 
3866  protected function doRollback( $fname ) {
3867  if ( $this->trxLevel ) {
3868  # Disconnects cause rollback anyway, so ignore those errors
3869  $ignoreErrors = true;
3870  $this->query( 'ROLLBACK', $fname, $ignoreErrors );
3871  $this->trxLevel = 0;
3872  }
3873  }
3874 
3875  public function flushSnapshot( $fname = __METHOD__ ) {
3876  if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
3877  // This only flushes transactions to clear snapshots, not to write data
3878  $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
3879  throw new DBUnexpectedError(
3880  $this,
3881  "$fname: Cannot flush snapshot because writes are pending ($fnames)."
3882  );
3883  }
3884 
3885  $this->commit( $fname, self::FLUSHING_INTERNAL );
3886  }
3887 
3888  public function explicitTrxActive() {
3889  return $this->trxLevel && ( $this->trxAtomicLevels || !$this->trxAutomatic );
3890  }
3891 
3892  public function duplicateTableStructure(
3893  $oldName, $newName, $temporary = false, $fname = __METHOD__
3894  ) {
3895  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
3896  }
3897 
3898  public function listTables( $prefix = null, $fname = __METHOD__ ) {
3899  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
3900  }
3901 
3902  public function listViews( $prefix = null, $fname = __METHOD__ ) {
3903  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
3904  }
3905 
3906  public function timestamp( $ts = 0 ) {
3907  $t = new ConvertibleTimestamp( $ts );
3908  // Let errors bubble up to avoid putting garbage in the DB
3909  return $t->getTimestamp( TS_MW );
3910  }
3911 
3912  public function timestampOrNull( $ts = null ) {
3913  if ( is_null( $ts ) ) {
3914  return null;
3915  } else {
3916  return $this->timestamp( $ts );
3917  }
3918  }
3919 
3920  public function affectedRows() {
3921  return ( $this->affectedRowCount === null )
3922  ? $this->fetchAffectedRowCount() // default to driver value
3924  }
3925 
3929  abstract protected function fetchAffectedRowCount();
3930 
3944  protected function resultObject( $result ) {
3945  if ( !$result ) {
3946  return false;
3947  } elseif ( $result instanceof ResultWrapper ) {
3948  return $result;
3949  } elseif ( $result === true ) {
3950  // Successful write query
3951  return $result;
3952  } else {
3953  return new ResultWrapper( $this, $result );
3954  }
3955  }
3956 
3957  public function ping( &$rtt = null ) {
3958  // Avoid hitting the server if it was hit recently
3959  if ( $this->isOpen() && ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
3960  if ( !func_num_args() || $this->rttEstimate > 0 ) {
3961  $rtt = $this->rttEstimate;
3962  return true; // don't care about $rtt
3963  }
3964  }
3965 
3966  // This will reconnect if possible or return false if not
3967  $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
3968  $ok = ( $this->query( self::PING_QUERY, __METHOD__, true ) !== false );
3969  $this->restoreFlags( self::RESTORE_PRIOR );
3970 
3971  if ( $ok ) {
3972  $rtt = $this->rttEstimate;
3973  }
3974 
3975  return $ok;
3976  }
3977 
3984  protected function replaceLostConnection( $fname ) {
3985  $this->closeConnection();
3986  $this->opened = false;
3987  $this->conn = false;
3988  try {
3989  $this->open( $this->server, $this->user, $this->password, $this->dbName );
3990  $this->lastPing = microtime( true );
3991  $ok = true;
3992 
3993  $this->connLogger->warning(
3994  $fname . ': lost connection to {dbserver}; reconnected',
3995  [
3996  'dbserver' => $this->getServer(),
3997  'trace' => ( new RuntimeException() )->getTraceAsString()
3998  ]
3999  );
4000  } catch ( DBConnectionError $e ) {
4001  $ok = false;
4002 
4003  $this->connLogger->error(
4004  $fname . ': lost connection to {dbserver} permanently',
4005  [ 'dbserver' => $this->getServer() ]
4006  );
4007  }
4008 
4009  $this->handleSessionLoss();
4010 
4011  return $ok;
4012  }
4013 
4014  public function getSessionLagStatus() {
4015  return $this->getRecordedTransactionLagStatus() ?: $this->getApproximateLagStatus();
4016  }
4017 
4031  final protected function getRecordedTransactionLagStatus() {
4032  return ( $this->trxLevel && $this->trxReplicaLag !== null )
4033  ? [ 'lag' => $this->trxReplicaLag, 'since' => $this->trxTimestamp() ]
4034  : null;
4035  }
4036 
4043  protected function getApproximateLagStatus() {
4044  return [
4045  'lag' => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
4046  'since' => microtime( true )
4047  ];
4048  }
4049 
4069  public static function getCacheSetOptions( IDatabase $db1, IDatabase $db2 = null ) {
4070  $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
4071  foreach ( func_get_args() as $db ) {
4073  $status = $db->getSessionLagStatus();
4074  if ( $status['lag'] === false ) {
4075  $res['lag'] = false;
4076  } elseif ( $res['lag'] !== false ) {
4077  $res['lag'] = max( $res['lag'], $status['lag'] );
4078  }
4079  $res['since'] = min( $res['since'], $status['since'] );
4080  $res['pending'] = $res['pending'] ?: $db->writesPending();
4081  }
4082 
4083  return $res;
4084  }
4085 
4086  public function getLag() {
4087  return 0;
4088  }
4089 
4090  public function maxListLen() {
4091  return 0;
4092  }
4093 
4094  public function encodeBlob( $b ) {
4095  return $b;
4096  }
4097 
4098  public function decodeBlob( $b ) {
4099  if ( $b instanceof Blob ) {
4100  $b = $b->fetch();
4101  }
4102  return $b;
4103  }
4104 
4105  public function setSessionOptions( array $options ) {
4106  }
4107 
4108  public function sourceFile(
4109  $filename,
4110  callable $lineCallback = null,
4111  callable $resultCallback = null,
4112  $fname = false,
4113  callable $inputCallback = null
4114  ) {
4115  Wikimedia\suppressWarnings();
4116  $fp = fopen( $filename, 'r' );
4117  Wikimedia\restoreWarnings();
4118 
4119  if ( false === $fp ) {
4120  throw new RuntimeException( "Could not open \"{$filename}\".\n" );
4121  }
4122 
4123  if ( !$fname ) {
4124  $fname = __METHOD__ . "( $filename )";
4125  }
4126 
4127  try {
4128  $error = $this->sourceStream(
4129  $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
4130  } catch ( Exception $e ) {
4131  fclose( $fp );
4132  throw $e;
4133  }
4134 
4135  fclose( $fp );
4136 
4137  return $error;
4138  }
4139 
4140  public function setSchemaVars( $vars ) {
4141  $this->schemaVars = $vars;
4142  }
4143 
4144  public function sourceStream(
4145  $fp,
4146  callable $lineCallback = null,
4147  callable $resultCallback = null,
4148  $fname = __METHOD__,
4149  callable $inputCallback = null
4150  ) {
4151  $delimiterReset = new ScopedCallback(
4152  function ( $delimiter ) {
4153  $this->delimiter = $delimiter;
4154  },
4155  [ $this->delimiter ]
4156  );
4157  $cmd = '';
4158 
4159  while ( !feof( $fp ) ) {
4160  if ( $lineCallback ) {
4161  call_user_func( $lineCallback );
4162  }
4163 
4164  $line = trim( fgets( $fp ) );
4165 
4166  if ( $line == '' ) {
4167  continue;
4168  }
4169 
4170  if ( '-' == $line[0] && '-' == $line[1] ) {
4171  continue;
4172  }
4173 
4174  if ( $cmd != '' ) {
4175  $cmd .= ' ';
4176  }
4177 
4178  $done = $this->streamStatementEnd( $cmd, $line );
4179 
4180  $cmd .= "$line\n";
4181 
4182  if ( $done || feof( $fp ) ) {
4183  $cmd = $this->replaceVars( $cmd );
4184 
4185  if ( $inputCallback ) {
4186  $callbackResult = call_user_func( $inputCallback, $cmd );
4187 
4188  if ( is_string( $callbackResult ) || !$callbackResult ) {
4189  $cmd = $callbackResult;
4190  }
4191  }
4192 
4193  if ( $cmd ) {
4194  $res = $this->query( $cmd, $fname );
4195 
4196  if ( $resultCallback ) {
4197  call_user_func( $resultCallback, $res, $this );
4198  }
4199 
4200  if ( false === $res ) {
4201  $err = $this->lastError();
4202 
4203  return "Query \"{$cmd}\" failed with error code \"$err\".\n";
4204  }
4205  }
4206  $cmd = '';
4207  }
4208  }
4209 
4210  ScopedCallback::consume( $delimiterReset );
4211  return true;
4212  }
4213 
4221  public function streamStatementEnd( &$sql, &$newLine ) {
4222  if ( $this->delimiter ) {
4223  $prev = $newLine;
4224  $newLine = preg_replace(
4225  '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
4226  if ( $newLine != $prev ) {
4227  return true;
4228  }
4229  }
4230 
4231  return false;
4232  }
4233 
4254  protected function replaceVars( $ins ) {
4255  $vars = $this->getSchemaVars();
4256  return preg_replace_callback(
4257  '!
4258  /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
4259  \'\{\$ (\w+) }\' | # 3. addQuotes
4260  `\{\$ (\w+) }` | # 4. addIdentifierQuotes
4261  /\*\$ (\w+) \*/ # 5. leave unencoded
4262  !x',
4263  function ( $m ) use ( $vars ) {
4264  // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
4265  // check for both nonexistent keys *and* the empty string.
4266  if ( isset( $m[1] ) && $m[1] !== '' ) {
4267  if ( $m[1] === 'i' ) {
4268  return $this->indexName( $m[2] );
4269  } else {
4270  return $this->tableName( $m[2] );
4271  }
4272  } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
4273  return $this->addQuotes( $vars[$m[3]] );
4274  } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
4275  return $this->addIdentifierQuotes( $vars[$m[4]] );
4276  } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
4277  return $vars[$m[5]];
4278  } else {
4279  return $m[0];
4280  }
4281  },
4282  $ins
4283  );
4284  }
4285 
4292  protected function getSchemaVars() {
4293  if ( $this->schemaVars ) {
4294  return $this->schemaVars;
4295  } else {
4296  return $this->getDefaultSchemaVars();
4297  }
4298  }
4299 
4308  protected function getDefaultSchemaVars() {
4309  return [];
4310  }
4311 
4312  public function lockIsFree( $lockName, $method ) {
4313  // RDBMs methods for checking named locks may or may not count this thread itself.
4314  // In MySQL, IS_FREE_LOCK() returns 0 if the thread already has the lock. This is
4315  // the behavior choosen by the interface for this method.
4316  return !isset( $this->namedLocksHeld[$lockName] );
4317  }
4318 
4319  public function lock( $lockName, $method, $timeout = 5 ) {
4320  $this->namedLocksHeld[$lockName] = 1;
4321 
4322  return true;
4323  }
4324 
4325  public function unlock( $lockName, $method ) {
4326  unset( $this->namedLocksHeld[$lockName] );
4327 
4328  return true;
4329  }
4330 
4331  public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
4332  if ( $this->writesOrCallbacksPending() ) {
4333  // This only flushes transactions to clear snapshots, not to write data
4334  $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
4335  throw new DBUnexpectedError(
4336  $this,
4337  "$fname: Cannot flush pre-lock snapshot because writes are pending ($fnames)."
4338  );
4339  }
4340 
4341  if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
4342  return null;
4343  }
4344 
4345  $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
4346  if ( $this->trxLevel() ) {
4347  // There is a good chance an exception was thrown, causing any early return
4348  // from the caller. Let any error handler get a chance to issue rollback().
4349  // If there isn't one, let the error bubble up and trigger server-side rollback.
4350  $this->onTransactionResolution(
4351  function () use ( $lockKey, $fname ) {
4352  $this->unlock( $lockKey, $fname );
4353  },
4354  $fname
4355  );
4356  } else {
4357  $this->unlock( $lockKey, $fname );
4358  }
4359  } );
4360 
4361  $this->commit( $fname, self::FLUSHING_INTERNAL );
4362 
4363  return $unlocker;
4364  }
4365 
4366  public function namedLocksEnqueue() {
4367  return false;
4368  }
4369 
4371  return true;
4372  }
4373 
4374  final public function lockTables( array $read, array $write, $method ) {
4375  if ( $this->writesOrCallbacksPending() ) {
4376  throw new DBUnexpectedError( $this, "Transaction writes or callbacks still pending." );
4377  }
4378 
4379  if ( $this->tableLocksHaveTransactionScope() ) {
4380  $this->startAtomic( $method );
4381  }
4382 
4383  return $this->doLockTables( $read, $write, $method );
4384  }
4385 
4394  protected function doLockTables( array $read, array $write, $method ) {
4395  return true;
4396  }
4397 
4398  final public function unlockTables( $method ) {
4399  if ( $this->tableLocksHaveTransactionScope() ) {
4400  $this->endAtomic( $method );
4401 
4402  return true; // locks released on COMMIT/ROLLBACK
4403  }
4404 
4405  return $this->doUnlockTables( $method );
4406  }
4407 
4414  protected function doUnlockTables( $method ) {
4415  return true;
4416  }
4417 
4425  public function dropTable( $tableName, $fName = __METHOD__ ) {
4426  if ( !$this->tableExists( $tableName, $fName ) ) {
4427  return false;
4428  }
4429  $sql = "DROP TABLE " . $this->tableName( $tableName ) . " CASCADE";
4430 
4431  return $this->query( $sql, $fName );
4432  }
4433 
4434  public function getInfinity() {
4435  return 'infinity';
4436  }
4437 
4438  public function encodeExpiry( $expiry ) {
4439  return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
4440  ? $this->getInfinity()
4441  : $this->timestamp( $expiry );
4442  }
4443 
4444  public function decodeExpiry( $expiry, $format = TS_MW ) {
4445  if ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) {
4446  return 'infinity';
4447  }
4448 
4449  return ConvertibleTimestamp::convert( $format, $expiry );
4450  }
4451 
4452  public function setBigSelects( $value = true ) {
4453  // no-op
4454  }
4455 
4456  public function isReadOnly() {
4457  return ( $this->getReadOnlyReason() !== false );
4458  }
4459 
4463  protected function getReadOnlyReason() {
4464  $reason = $this->getLBInfo( 'readOnlyReason' );
4465 
4466  return is_string( $reason ) ? $reason : false;
4467  }
4468 
4469  public function setTableAliases( array $aliases ) {
4470  $this->tableAliases = $aliases;
4471  }
4472 
4473  public function setIndexAliases( array $aliases ) {
4474  $this->indexAliases = $aliases;
4475  }
4476 
4488  protected function getBindingHandle() {
4489  if ( !$this->conn ) {
4490  throw new DBUnexpectedError(
4491  $this,
4492  'DB connection was already closed or the connection dropped.'
4493  );
4494  }
4495 
4496  return $this->conn;
4497  }
4498 
4503  public function __toString() {
4504  return (string)$this->conn;
4505  }
4506 
4511  public function __clone() {
4512  $this->connLogger->warning(
4513  "Cloning " . static::class . " is not recomended; forking connection:\n" .
4514  ( new RuntimeException() )->getTraceAsString()
4515  );
4516 
4517  if ( $this->isOpen() ) {
4518  // Open a new connection resource without messing with the old one
4519  $this->opened = false;
4520  $this->conn = false;
4521  $this->trxEndCallbacks = []; // don't copy
4522  $this->handleSessionLoss(); // no trx or locks anymore
4523  $this->open( $this->server, $this->user, $this->password, $this->dbName );
4524  $this->lastPing = microtime( true );
4525  }
4526  }
4527 
4533  public function __sleep() {
4534  throw new RuntimeException( 'Database serialization may cause problems, since ' .
4535  'the connection is not restored on wakeup.' );
4536  }
4537 
4541  public function __destruct() {
4542  if ( $this->trxLevel && $this->trxDoneWrites ) {
4543  trigger_error( "Uncommitted DB writes (transaction from {$this->trxFname})." );
4544  }
4545 
4546  $danglingWriters = $this->pendingWriteAndCallbackCallers();
4547  if ( $danglingWriters ) {
4548  $fnames = implode( ', ', $danglingWriters );
4549  trigger_error( "DB transaction writes or callbacks still pending ($fnames)." );
4550  }
4551 
4552  if ( $this->conn ) {
4553  // Avoid connection leaks for sanity. Normally, resources close at script completion.
4554  // The connection might already be closed in zend/hhvm by now, so suppress warnings.
4555  Wikimedia\suppressWarnings();
4556  $this->closeConnection();
4557  Wikimedia\restoreWarnings();
4558  $this->conn = false;
4559  $this->opened = false;
4560  }
4561  }
4562 }
4563 
4564 class_alias( Database::class, 'DatabaseBase' ); // b/c for old name
4565 class_alias( Database::class, 'Database' ); // b/c global alias
Wikimedia\Rdbms\Database\registerTempTableOperation
registerTempTableOperation( $sql)
Definition: Database.php:1059
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:651
Wikimedia\Rdbms\Database\tablePrefix
tablePrefix( $prefix=null)
Get/set the table prefix.
Definition: Database.php:593
Wikimedia\Rdbms\Database\$profiler
object string $profiler
Class name or object With profileIn/profileOut methods.
Definition: Database.php:270
Wikimedia\Rdbms\Database\$trxFname
string $trxFname
Remembers the function name given for starting the most recent transaction via begin().
Definition: Database.php:191
Wikimedia\Rdbms\Database\handleTransactionLoss
handleTransactionLoss()
Clean things up after transaction loss.
Definition: Database.php:1378
Wikimedia\Rdbms\Database\getLastPHPError
getLastPHPError()
Definition: Database.php:861
Wikimedia\Rdbms\Database\insert
insert( $table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
Definition: Database.php:1917
Wikimedia\Rdbms\Database\setSessionOptions
setSessionOptions(array $options)
Override database's default behavior.
Definition: Database.php:4105
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:3027
Wikimedia\Rdbms\Database\$errorLogger
callback $errorLogger
Error logging callback.
Definition: Database.php:103
Wikimedia\Rdbms\Database\$trxDoneWrites
bool $trxDoneWrites
Record if possible write queries were done in the last transaction started.
Definition: Database.php:198
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:1975
Wikimedia\Rdbms\Database\replaceLostConnection
replaceLostConnection( $fname)
Close any existing (dead) database connection and open a new connection.
Definition: Database.php:3984
Wikimedia\Rdbms\Database\$trxWriteAdjQueryCount
int $trxWriteAdjQueryCount
Number of write queries counted in trxWriteAdjDuration.
Definition: Database.php:249
Wikimedia\Rdbms\Database\getBindingHandle
getBindingHandle()
Get the underlying binding connection handle.
Definition: Database.php:4488
Wikimedia\Rdbms\Database\$tablePrefix
string $tablePrefix
Definition: Database.php:124
Wikimedia\Rdbms\Database\$trxLevel
int $trxLevel
Either 1 if a transaction is active or 0 otherwise.
Definition: Database.php:165
Wikimedia\Rdbms\Database\canRecoverFromDisconnect
canRecoverFromDisconnect( $sql, $priorWritesPending)
Determine whether or not it is safe to retry queries after a database connection is lost.
Definition: Database.php:1338
Wikimedia\Rdbms\Database\buildGroupConcatField
buildGroupConcatField( $delim, $table, $field, $conds='', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
Definition: Database.php:2132
Wikimedia\Rdbms\Database\doInitConnection
doInitConnection()
Actually connect to the database over the wire (or to local files)
Definition: Database.php:365
Wikimedia\Rdbms\Database\$trxReplicaLag
float $trxReplicaLag
Lag estimate at the time of BEGIN.
Definition: Database.php:183
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:2576
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:1865
Wikimedia\Rdbms\Database\bitOr
bitOr( $fieldLeft, $fieldRight)
Definition: Database.php:2124
$tables
this hook is for auditing only RecentChangesLinked and Watchlist 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:990
Wikimedia\Rdbms\Database\selectRowCount
selectRowCount( $tables, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Get the number of rows in dataset.
Definition: Database.php:1762
Wikimedia\Rdbms\Database\ignoreIndexClause
ignoreIndexClause( $index)
IGNORE INDEX clause.
Definition: Database.php:2654
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:422
Wikimedia\Rdbms\Database\trxStatus
trxStatus()
Definition: Database.php:589
Wikimedia\Rdbms\Database\estimateRowCount
estimateRowCount( $table, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Estimate the number of rows in dataset.
Definition: Database.php:1745
Wikimedia\Rdbms\Database\listTables
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
Definition: Database.php:3898
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:1279
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:2093
Wikimedia\Rdbms\Database\$queryLogger
LoggerInterface $queryLogger
Definition: Database.php:101
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:4094
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()
Definition: Database.php:810
Wikimedia\Rdbms\Database\getClass
static getClass( $dbType, $driver=null)
Definition: Database.php:493
Wikimedia\Rdbms\Database\getProperty
getProperty( $name)
Definition: Database.php:806
Wikimedia\Rdbms\IDatabase\numRows
numRows( $res)
Get the number of rows in a result object.
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:3254
Wikimedia\Rdbms\Database\tableLocksHaveTransactionScope
tableLocksHaveTransactionScope()
Checks if table locks acquired by lockTables() are transaction-bound in their scope.
Definition: Database.php:4370
Wikimedia\Rdbms\Database\wasErrorReissuable
wasErrorReissuable()
Determines if the last query error was due to something outside of the query itself.
Definition: Database.php:3151
Wikimedia\Rdbms\Database\deleteJoin
deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
Definition: Database.php:2795
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:4473
Wikimedia\Rdbms\Database\$password
string $password
Password used to establish the current connection.
Definition: Database.php:83
Wikimedia\Rdbms\Database\nativeReplace
nativeReplace( $table, $rows, $fname)
REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE statement.
Definition: Database.php:2720
Wikimedia\Rdbms\Database\decodeBlob
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects.
Definition: Database.php:4098
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:3048
Wikimedia\Rdbms\Database\maxListLen
maxListLen()
Return the maximum number of items allowed in a list, or 0 for unlimited.
Definition: Database.php:4090
Wikimedia\Rdbms\Database\buildIntegerCast
buildIntegerCast( $field)
Definition: Database.php:2178
$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. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links: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! 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:1985
Wikimedia\Rdbms\Database\buildLike
buildLike()
LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match conta...
Definition: Database.php:2591
Wikimedia\Rdbms\Database\getServerUptime
getServerUptime()
Determines how long the server has been up.
Definition: Database.php:3131
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:2321
Wikimedia\Rdbms\Database\prependDatabaseOrSchema
prependDatabaseOrSchema( $namespace, $relation, $format)
Definition: Database.php:2310
Wikimedia\Rdbms\Database\$delimiter
string $delimiter
Definition: Database.php:140
Wikimedia\Rdbms\Database\__destruct
__destruct()
Run a few simple sanity checks and close dangling connections.
Definition: Database.php:4541
Wikimedia\Rdbms\Database\endAtomic
endAtomic( $fname=__METHOD__)
Ends an atomic section of SQL statements.
Definition: Database.php:3562
Wikimedia\Rdbms\Database\indexName
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
Definition: Database.php:2531
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:4452
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\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:3912
Wikimedia\Rdbms\Database\decodeExpiry
decodeExpiry( $expiry, $format=TS_MW)
Decode an expiry time into a DBMS independent format.
Definition: Database.php:4444
Wikimedia\Rdbms\Database\$trxAutomaticAtomic
bool $trxAutomaticAtomic
Record if the current transaction was started implicitly by Database::startAtomic.
Definition: Database.php:223
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:1796
Wikimedia\Rdbms\Database\anyString
anyString()
Returns a token for buildLike() that denotes a '' to be used in a LIKE query.
Definition: Database.php:2622
Wikimedia\Rdbms\Database\unionSupportsOrderAndLimit
unionSupportsOrderAndLimit()
Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries within th...
Definition: Database.php:3038
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:40
Wikimedia\Rdbms\Database\isInsertSelectSafe
isInsertSelectSafe(array $insertOptions, array $selectOptions)
Definition: Database.php:2890
Wikimedia\Rdbms\Database\fieldNameWithAlias
fieldNameWithAlias( $name, $alias=false)
Get an aliased field name e.g.
Definition: Database.php:2400
Wikimedia\Rdbms\Database\getDefaultSchemaVars
getDefaultSchemaVars()
Get schema variables to use if none have been set via setSchemaVars().
Definition: Database.php:4308
BagOStuff
interface is intended to be more or less compatible with the PHP memcached client.
Definition: BagOStuff.php:47
$s
$s
Definition: mergeMessageFileList.php:187
Wikimedia\Rdbms\Database\extractSingleFieldFromList
extractSingleFieldFromList( $var)
Definition: Database.php:1819
Wikimedia\Rdbms\Database\duplicateTableStructure
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Creates a new table with structure copied from existing table.
Definition: Database.php:3892
$res
$res
Definition: database.txt:21
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
Wikimedia\Rdbms\Database\isWriteQuery
isWriteQuery( $sql)
Determine whether a query writes to the DB.
Definition: Database.php:1025
Wikimedia\Rdbms\Database\namedLocksEnqueue
namedLocksEnqueue()
Check to see if a named lock used by lock() use blocking queues.
Definition: Database.php:4366
DBO_IGNORE
const DBO_IGNORE
Definition: defines.php:11
Wikimedia\Rdbms\Database\buildSubstring
buildSubstring( $input, $startPosition, $length=null)
Definition: Database.php:2140
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:634
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:2436
Wikimedia\Rdbms\Database\getReplicaPos
getReplicaPos()
Get the replication position of this replica DB.
Definition: Database.php:3220
Wikimedia\Rdbms\Database\getSchemaVars
getSchemaVars()
Get schema variables.
Definition: Database.php:4292
Wikimedia\Rdbms\Database\setLBInfo
setLBInfo( $name, $value=null)
Set the LB info array, or a member of it.
Definition: Database.php:626
Wikimedia\Rdbms\Database\setLogger
setLogger(LoggerInterface $logger)
Set the PSR-3 logger interface to use for query logging.
Definition: Database.php:558
Wikimedia\Rdbms\Database\__clone
__clone()
Make sure that copies do not share the same client binding handle.
Definition: Database.php:4511
Wikimedia\Rdbms\Database\cancelAtomic
cancelAtomic( $fname=__METHOD__, AtomicSectionIdentifier $sectionId=null)
Cancel an atomic section of SQL statements.
Definition: Database.php:3595
$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:797
Wikimedia\Rdbms\Database\conditional
conditional( $cond, $trueVal, $falseVal)
Returns an SQL expression for a simple conditional.
Definition: Database.php:3119
Wikimedia\Rdbms\Database\$trxAtomicLevels
array $trxAtomicLevels
Array of levels of atomicity within transactions.
Definition: Database.php:217
Wikimedia\Rdbms\DBError
Database error base class.
Definition: DBError.php:30
Wikimedia\Rdbms\Database\reportQueryError
reportQueryError( $error, $errno, $sql, $fname, $tempIgnore=false)
Report a query error.
Definition: Database.php:1423
Wikimedia\Rdbms\Database\$trxStatusIgnoredCause
array null $trxStatusIgnoredCause
If wasKnownStatementRollbackError() prevented trxStatus from being set, the relevant details are stor...
Definition: Database.php:158
Wikimedia\Rdbms\Database\$lastQuery
string $lastQuery
SQL query.
Definition: Database.php:73
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:2813
Wikimedia\Rdbms\Database\buildConcat
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.
Definition: Database.php:2128
Wikimedia\Rdbms\Database\deadlockLoop
deadlockLoop()
Perform a deadlock-prone transaction.
Definition: Database.php:3179
Wikimedia\Rdbms\Database\bitNot
bitNot( $field)
Definition: Database.php:2116
Wikimedia\Rdbms\Database\nextSavepointId
nextSavepointId( $fname)
Definition: Database.php:3519
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:44
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:647
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:4533
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:3734
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:773
Wikimedia\Rdbms\Database\begin
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
Definition: Database.php:3675
Wikimedia\Rdbms\DatabaseDomain\newUnspecified
static newUnspecified()
Definition: DatabaseDomain.php:93
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:3944
key
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database key
Definition: design.txt:25
Wikimedia\Rdbms\Database\$lastWriteTime
float bool $lastWriteTime
UNIX timestamp of last write query.
Definition: Database.php:75
Wikimedia\Rdbms\Database\doQuery
doQuery( $sql)
Run a query and return a DBMS-dependent wrapper (that has all IResultWrapper methods)
Wikimedia\Rdbms\Database\runOnTransactionIdleCallbacks
runOnTransactionIdleCallbacks( $trigger)
Actually run and consume any "on transaction idle/resolution" callbacks.
Definition: Database.php:3371
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:1528
Wikimedia\Rdbms\Database\$trxStatusCause
Exception null $trxStatusCause
The last error that caused the status to become STATUS_TRX_ERROR.
Definition: Database.php:153
Wikimedia\Rdbms\Database\replaceVars
replaceVars( $ins)
Database independent variable replacement.
Definition: Database.php:4254
Wikimedia\Rdbms\Database\$NOT_APPLICABLE
static string $NOT_APPLICABLE
Idiom used when a cancelable atomic section started the transaction.
Definition: Database.php:278
Wikimedia\Rdbms\Database\$trxStatus
int $trxStatus
Transaction status.
Definition: Database.php:149
Wikimedia\Rdbms\Database\runOnTransactionPreCommitCallbacks
runOnTransactionPreCommitCallbacks()
Actually run and consume any "on transaction pre-commit" callbacks.
Definition: Database.php:3421
Wikimedia\Rdbms\Database\wasLockTimeout
wasLockTimeout()
Determines if the last failure was due to a lock timeout.
Definition: Database.php:3139
Wikimedia\Rdbms\Database\$affectedRowCount
integer null $affectedRowCount
Rows affected by the last query to query() or its CRUD wrappers.
Definition: Database.php:144
Wikimedia\Rdbms\Database\$currentDomain
DatabaseDomain $currentDomain
Definition: Database.php:142
LIST_OR
const LIST_OR
Definition: Defines.php:47
Wikimedia\Rdbms\Database\getLag
getLag()
Get the amount of replication lag for this database server.
Definition: Database.php:4086
Wikimedia\Rdbms\Database\modifyCallbacksForCancel
modifyCallbacksForCancel(array $sectionIds)
Definition: Database.php:3317
Wikimedia\Rdbms\Database\$sessionTempTables
array $sessionTempTables
Map of (table name => 1) for TEMPORARY tables.
Definition: Database.php:258
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:2182
Wikimedia\Rdbms\Database\rollback
rollback( $fname=__METHOD__, $flush='')
Rollback a transaction previously started using begin().
Definition: Database.php:3811
Wikimedia\Rdbms\Database\assertBuildSubstringParams
assertBuildSubstringParams( $startPosition, $length)
Check type and bounds for parameters to self::buildSubstring()
Definition: Database.php:2161
Wikimedia\Rdbms\Database\isReadOnly
isReadOnly()
Definition: Database.php:4456
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:2174
Wikimedia\Rdbms\Database\$lazyMasterHandle
IDatabase null $lazyMasterHandle
Lazy handle to the master DB this server replicates from.
Definition: Database.php:261
Wikimedia\Rdbms\Database\unlockTables
unlockTables( $method)
Unlock all tables locked via lockTables()
Definition: Database.php:4398
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:4014
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:2909
Wikimedia\Rdbms\Database\$trxWriteDuration
float $trxWriteDuration
Seconds spent in write queries for the current transaction.
Definition: Database.php:233
Wikimedia\Rdbms\Database\doRollbackToSavepoint
doRollbackToSavepoint( $identifier, $fname)
Rollback to a savepoint.
Definition: Database.php:3511
Wikimedia\Rdbms\Database\startAtomic
startAtomic( $fname=__METHOD__, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Begin an atomic section of SQL statements.
Definition: Database.php:3534
Wikimedia\Rdbms\DBQueryTimeoutError
Error thrown when a query times out.
Definition: DBQueryTimeoutError.php:29
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:4069
Wikimedia\Rdbms\Database\lock
lock( $lockName, $method, $timeout=5)
Acquire a named lock.
Definition: Database.php:4319
Wikimedia\Rdbms\Database\getTransactionRoundId
getTransactionRoundId()
Definition: Database.php:683
Wikimedia\Rdbms\Database\upsert
upsert( $table, array $rows, array $uniqueIndexes, array $set, $fname=__METHOD__)
INSERT ON DUPLICATE KEY UPDATE wrapper, upserts an array into a table.
Definition: Database.php:2744
Wikimedia\Rdbms\Database\trxLevel
trxLevel()
Gets the current transaction level.
Definition: Database.php:577
$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:762
$matches
$matches
Definition: NoLocalSettings.php:24
Wikimedia\Rdbms\Database\setTransactionListener
setTransactionListener( $name, callable $callback=null)
Run a callback each time any transaction commits or rolls back.
Definition: Database.php:3342
Wikimedia\Rdbms\Database\getWikiID
getWikiID()
Alias for getDomainID()
Definition: Database.php:814
Wikimedia\Rdbms\Database\escapeLikeInternal
escapeLikeInternal( $s, $escapeChar='`')
Definition: Database.php:2585
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:172
Wikimedia\Rdbms\Database\isTransactableQuery
isTransactableQuery( $sql)
Determine whether a SQL statement is sensitive to isolation level.
Definition: Database.php:1047
LIST_SET
const LIST_SET
Definition: Defines.php:45
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:1871
Wikimedia\Rdbms\Database\writesOrCallbacksPending
writesOrCallbacksPending()
Returns true if there is a transaction/round open with possible write queries or transaction pre-comm...
Definition: Database.php:671
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:4144
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:1656
Wikimedia\Rdbms\Database\$trxWriteCallers
string[] $trxWriteCallers
Track the write query callers of the current transaction.
Definition: Database.php:229
Wikimedia\Rdbms\Database\$lastPing
float $lastPing
UNIX timestamp.
Definition: Database.php:264
Wikimedia\Rdbms\Database\bitAnd
bitAnd( $fieldLeft, $fieldRight)
Definition: Database.php:2120
Wikimedia\Rdbms\Database\$phpError
string bool $phpError
Definition: Database.php:77
Wikimedia\Rdbms\Database\$trxProfiler
TransactionProfiler $trxProfiler
Definition: Database.php:272
Wikimedia\Rdbms\Database\$user
string $user
User that this instance is currently connected under the name of.
Definition: Database.php:81
Wikimedia\Rdbms\Database\SMALL_WRITE_ROWS
const SMALL_WRITE_ROWS
Definition: Database.php:62
Wikimedia\Rdbms\Database\selectDB
selectDB( $db)
Change the current database.
Definition: Database.php:2195
Wikimedia\Rdbms\Database\reportConnectionError
reportConnectionError( $error='Unknown error')
Definition: Database.php:997
Wikimedia\Rdbms\Database\wasQueryTimeout
wasQueryTimeout( $error, $errno)
Checks whether the cause of the error is detected to be a timeout.
Definition: Database.php:1408
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:663
$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:4425
Wikimedia\Rdbms\Database\wasReadOnlyError
wasReadOnlyError()
Determines if the last failure was due to the database being read-only.
Definition: Database.php:3147
Wikimedia\Rdbms\IDatabase\open
open( $server, $user, $password, $dbName)
Open a new connection to the database (closing any existing one)
Wikimedia\Rdbms\Database\getServerInfo
getServerInfo()
A string describing the current software version, and possibly other details in a user-friendly way.
Definition: Database.php:562
Wikimedia\Rdbms\Database\commit
commit( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Commits a transaction previously started using begin().
Definition: Database.php:3739
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:784
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:879
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:4331
Wikimedia\Rdbms\Database\fieldNamesWithAlias
fieldNamesWithAlias( $fields)
Gets an array of aliased field names.
Definition: Database.php:2414
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\assertOpen
assertOpen()
Make sure isOpen() returns true as a sanity check.
Definition: Database.php:980
Wikimedia\Rdbms\Database\getLBInfo
getLBInfo( $name=null)
Get properties passed down from the server info array of the load balancer.
Definition: Database.php:614
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:241
Wikimedia\Rdbms\Database\$trxWriteAdjDuration
float $trxWriteAdjDuration
Like trxWriteQueryCount but excludes lock-bound, easy to replicate, queries.
Definition: Database.php:245
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:2848
Wikimedia\Rdbms\Database\makeInsertOptions
makeInsertOptions( $options)
Helper for Database::insert().
Definition: Database.php:1913
Wikimedia\Rdbms\Database\selectRow
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Single row SELECT wrapper.
Definition: Database.php:1725
Wikimedia\Rdbms\Database\handleSessionLoss
handleSessionLoss()
Clean things up after session (and thus transaction) loss.
Definition: Database.php:1363
Wikimedia\Rdbms\DBQueryError
Definition: DBQueryError.php:27
Wikimedia\Rdbms\Database\useIndexClause
useIndexClause( $index)
USE INDEX clause.
Definition: Database.php:2640
Wikimedia\Rdbms\Database\makeUpdateOptions
makeUpdateOptions( $options)
Make UPDATE options for the Database::update function.
Definition: Database.php:1995
LIST_COMMA
const LIST_COMMA
Definition: Defines.php:43
Wikimedia\Rdbms\Database\getReadOnlyReason
getReadOnlyReason()
Definition: Database.php:4463
$fname
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition: Setup.php:112
Wikimedia\Rdbms\Database\$priorFlags
int[] $priorFlags
Prior flags member variable values.
Definition: Database.php:267
Wikimedia\Rdbms\Database\installErrorHandler
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition: Database.php:838
Wikimedia\Rdbms\Database\aggregateValue
aggregateValue( $valuedata, $valuename='value')
Return aggregated value alias.
Definition: Database.php:2112
Wikimedia\Rdbms\Database\restoreErrorHandler
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition: Database.php:849
$line
$line
Definition: cdb.php:59
Wikimedia\Rdbms\Database\replace
replace( $table, $uniqueIndexes, $rows, $fname=__METHOD__)
REPLACE query wrapper.
Definition: Database.php:2658
Wikimedia\Rdbms\Database\wasKnownStatementRollbackError
wasKnownStatementRollbackError()
Definition: Database.php:3175
Wikimedia\Rdbms\Database\pendingWriteAndCallbackCallers
pendingWriteAndCallbackCallers()
Get the list of method names that have pending write queries or callbacks for this transaction.
Definition: Database.php:730
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:138
Wikimedia\Rdbms\Database\tableExists
tableExists( $table, $fname=__METHOD__)
Query whether a given table exists.
Definition: Database.php:1884
Wikimedia\Rdbms\Database\$SAVEPOINT_PREFIX
static string $SAVEPOINT_PREFIX
Prefix to the atomic section counter used to make savepoint IDs.
Definition: Database.php:280
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2163
Wikimedia\Rdbms\Database\pendingWriteCallers
pendingWriteCallers()
Get the list of method names that did write queries for this transaction.
Definition: Database.php:716
Wikimedia\Rdbms\Database\$deprecationLogger
callback $deprecationLogger
Deprecation logging callback.
Definition: Database.php:105
Wikimedia\Rdbms\Database\makeQueryException
makeQueryException( $error, $errno, $sql, $fname)
Definition: Database.php:1440
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:2332
$value
$value
Definition: styleTest.css.php:45
Wikimedia\Rdbms\Database\$opened
bool $opened
Definition: Database.php:110
Wikimedia\Rdbms\Database\__toString
__toString()
Definition: Database.php:4503
Wikimedia\Rdbms\Database\getLogContext
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition: Database.php:889
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:205
$retval
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account incomplete not yet checked for validity & $retval
Definition: hooks.txt:244
Wikimedia\Rdbms\Database\$namedLocksHeld
array $namedLocksHeld
Map of (name => 1) for locks obtained via lock()
Definition: Database.php:256
Wikimedia\Rdbms\Database\pendingWriteRowsAffected
pendingWriteRowsAffected()
Get the number of affected rows from pending write queries.
Definition: Database.php:720
Wikimedia\Rdbms\Database\doneWrites
doneWrites()
Returns true if the connection may have been used for write queries.
Definition: Database.php:659
Wikimedia\Rdbms\Database\$trxWriteQueryCount
int $trxWriteQueryCount
Number of write queries for the current transaction.
Definition: Database.php:237
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:3497
Wikimedia\Rdbms\Database\currentAtomicSectionId
currentAtomicSectionId()
Definition: Database.php:3279
Wikimedia\Rdbms\Database\setTrxEndCallbackSuppression
setTrxEndCallbackSuppression( $suppress)
Whether to disable running of post-COMMIT/ROLLBACK callbacks.
Definition: Database.php:3358
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:3225
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\pendingWriteQueryDuration
pendingWriteQueryDuration( $type=self::ESTIMATE_TOTAL)
Get the time spend running write queries for this transaction.
Definition: Database.php:694
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:1987
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:2212
Wikimedia\Rdbms\Database\$conn
resource null $conn
Database connection.
Definition: Database.php:108
Wikimedia\Rdbms\Database\wasConnectionError
wasConnectionError( $errno)
Do not use this method outside of Database/DBError classes.
Definition: Database.php:3165
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:3659
Wikimedia\Rdbms\Database\query
query( $sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition: Database.php:1094
Wikimedia\Rdbms\Database\getRecordedTransactionLagStatus
getRecordedTransactionLagStatus()
Get the replica DB lag when the current transaction started.
Definition: Database.php:4031
Wikimedia\Rdbms\Database\strencode
strencode( $s)
Wrapper for addslashes()
Wikimedia\Rdbms\Database\getServer
getServer()
Get the server hostname or IP address.
Definition: Database.php:2208
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:4394
Wikimedia\Rdbms\Database\doUnlockTables
doUnlockTables( $method)
Helper function for unlockTables() that handles the actual table unlocking.
Definition: Database.php:4414
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:3906
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:1466
Wikimedia\Rdbms\Database\bufferResults
bufferResults( $buffer=null)
Turns buffering of SQL result sets on (true) or off (false).
Definition: Database.php:566
Wikimedia\Rdbms\Database\makeList
makeList( $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Definition: Database.php:2013
Wikimedia\Rdbms\Database\anyChar
anyChar()
Returns a token for buildLike() that denotes a '_' to be used in a LIKE query.
Definition: Database.php:2618
Wikimedia\Rdbms\Database\onTransactionIdle
onTransactionIdle(callable $callback, $fname=__METHOD__)
Run a callback as soon as there is no transaction pending.
Definition: Database.php:3241
Wikimedia\Rdbms\Database\unlock
unlock( $lockName, $method)
Release a lock.
Definition: Database.php:4325
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)
Definition: Database.php:1306
Wikimedia\Rdbms\Database\addIdentifierQuotes
addIdentifierQuotes( $s)
Quotes an identifier using backticks or "double quotes" depending on the database type.
Definition: Database.php:2563
Wikimedia\Rdbms\Database\getLazyMasterHandle
getLazyMasterHandle()
Definition: Database.php:643
Wikimedia\Rdbms\Database\indexUnique
indexUnique( $table, $index)
Determines if a given index is unique.
Definition: Database.php:1897
Wikimedia\Rdbms\Database\$schema
string $schema
Definition: Database.php:126
$args
if( $line===false) $args
Definition: cdb.php:64
Wikimedia\Rdbms\Database\$preparedArgs
array null $preparedArgs
Definition: Database.php:136
Wikimedia\Rdbms\Database\dbSchema
dbSchema( $schema=null)
Get/set the db schema.
Definition: Database.php:605
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:900
Wikimedia\Rdbms\Database\getQueryVerb
getQueryVerb( $sql)
Definition: Database.php:1034
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:3042
flags
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add in any and then calling but I prefer the flexibility This should also do the output encoding The system allocates a global one in $wgOut Title Represents the title of an and does all the work of translating among various forms such as plain database etc For and for historical it also represents a few features of articles that don t involve their such as access rights See also title txt Article Encapsulates access to the page table of the database The object represents a an and maintains state such as flags
Definition: design.txt:34
Wikimedia\Rdbms\Database\reassignCallbacksForSection
reassignCallbacksForSection(AtomicSectionIdentifier $old, AtomicSectionIdentifier $new)
Definition: Database.php:3293
Wikimedia\Rdbms\IDatabase\lastErrno
lastErrno()
Get the last error number.
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:4374
Wikimedia\Rdbms\Database\$trxAtomicCounter
int $trxAtomicCounter
Counter for atomic savepoint identifiers.
Definition: Database.php:211
$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:2604
Wikimedia\Rdbms\Database\encodeExpiry
encodeExpiry( $expiry)
Encode an expiry time into the DBMS dependent format.
Definition: Database.php:4438
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:1987
Wikimedia\Rdbms\Database\doSavepoint
doSavepoint( $identifier, $fname)
Create a savepoint.
Definition: Database.php:3483
Wikimedia\Rdbms\Database\setSchemaVars
setSchemaVars( $vars)
Set variables to be used in sourceFile/sourceStream, in preference to the ones in $GLOBALS.
Definition: Database.php:4140
Wikimedia\Rdbms\Database\__construct
__construct(array $params)
Definition: Database.php:293
Wikimedia\Rdbms\Database\trxTimestamp
trxTimestamp()
Get the UNIX timestamp of the time that the transaction was established.
Definition: Database.php:581
Wikimedia\Rdbms\Database\$trxTimestamp
float null $trxTimestamp
The UNIX time that the transaction started.
Definition: Database.php:181
Wikimedia\Rdbms\Database\flushSnapshot
flushSnapshot( $fname=__METHOD__)
Commit any transaction but error out if writes or callbacks are pending.
Definition: Database.php:3875
Wikimedia\Rdbms\Database\makeGroupByWithHaving
makeGroupByWithHaving( $options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1611
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:2354
Wikimedia\Rdbms\Database\getAttributes
static getAttributes()
Definition: Database.php:547
Wikimedia\Rdbms\Database\writesPending
writesPending()
Definition: Database.php:667
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\lockIsFree
lockIsFree( $lockName, $method)
Check to see if a named lock is not locked by any thread (non-blocking)
Definition: Database.php:4312
Wikimedia\Rdbms\Database\ping
ping(&$rtt=null)
Ping the server and try to reconnect if it there is no connection.
Definition: Database.php:3957
Wikimedia\Rdbms\Database\$nonNativeInsertSelectBatchSize
int $nonNativeInsertSelectBatchSize
Definition: Database.php:275
Wikimedia\Rdbms\Database\runTransactionListenerCallbacks
runTransactionListenerCallbacks( $trigger)
Actually run any "transaction listener" callbacks.
Definition: Database.php:3451
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
Wikimedia
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:4469
Wikimedia\Rdbms\Database\masterPosWait
masterPosWait(DBMasterPos $pos, $timeout)
Wait for the replica DB to catch up to a given master position.
Definition: Database.php:3215
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:3234
Wikimedia\Rdbms\Database\explicitTrxActive
explicitTrxActive()
Definition: Database.php:3888
Wikimedia\Rdbms\Database\$server
string $server
Server that this instance is currently connected to.
Definition: Database.php:79
$keys
$keys
Definition: testCompression.php:67
Wikimedia\Rdbms\Database\freeResult
freeResult( $res)
Free a result object returned by query() or select().
Definition: Database.php:1463
Wikimedia\Rdbms\Database\$schemaVars
array bool $schemaVars
Definition: Database.php:132
Wikimedia\Rdbms\Database\isOpen
isOpen()
Is a connection to the database open?
Definition: Database.php:758
Wikimedia\Rdbms\Database\doCommit
doCommit( $fname)
Issues the COMMIT command to the database server.
Definition: Database.php:3804
Wikimedia\Rdbms\IMaintainableDatabase\fieldInfo
fieldInfo( $table, $field)
mysql_fetch_field() wrapper Returns false if the field doesn't exist
Wikimedia\Rdbms\Database\makeOrderBy
makeOrderBy( $options)
Returns an optional ORDER BY.
Definition: Database.php:1637
Wikimedia\Rdbms\Database\$flags
int $flags
Definition: Database.php:128
Wikimedia\Rdbms\Database\serverIsReadOnly
serverIsReadOnly()
Definition: Database.php:3230
$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. '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:1255
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:1649
Wikimedia\Rdbms\Database\attributesFromType
static attributesFromType( $dbType, $driver=null)
Definition: Database.php:479
Wikimedia\Rdbms\Database\lastQuery
lastQuery()
Return the last query that went through IDatabase::query()
Definition: Database.php:655
Wikimedia\Rdbms\Database\wasConnectionLoss
wasConnectionLoss()
Determines if the last query error was due to a dropped connection.
Definition: Database.php:3143
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:3135
$t
$t
Definition: testCompression.php:69
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:2001
Wikimedia\Rdbms\Database\addQuotes
addQuotes( $s)
Adds quotes and backslashes.
Definition: Database.php:2537
LIST_NAMES
const LIST_NAMES
Definition: Defines.php:46
Wikimedia\Rdbms\Database\getApproximateLagStatus
getApproximateLagStatus()
Get a replica DB lag estimate for this server.
Definition: Database.php:4043
Wikimedia\Rdbms\Database\qualifiedTableComponents
qualifiedTableComponents( $name)
Get the table components needed for a query given the currently selected database.
Definition: Database.php:2270
Wikimedia\Rdbms\Database\affectedRows
affectedRows()
Get the number of rows affected by the last write query.
Definition: Database.php:3920
Wikimedia\Rdbms\Database\$sessionVars
array $sessionVars
Definition: Database.php:134
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:2191
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:3127
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:4108
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:3902
Wikimedia\Rdbms\Database\streamStatementEnd
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
Definition: Database.php:4221
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:2980
Wikimedia\Rdbms\IMaintainableDatabase
Advanced database interface for IDatabase handles that include maintenance methods.
Definition: IMaintainableDatabase.php:38
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:2380
Wikimedia\Rdbms\Database\nextSequenceValue
nextSequenceValue( $seqName)
Deprecated method, calls should be removed.
Definition: Database.php:2626
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:1843
Wikimedia\Rdbms\Subquery
Definition: Subquery.php:27
Wikimedia\Rdbms\Database\$rttEstimate
float $rttEstimate
RTT time estimate.
Definition: Database.php:253
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:1493
Wikimedia\Rdbms\Database\flatAtomicSectionList
flatAtomicSectionList()
Definition: Database.php:752
$buffer
$buffer
Definition: mwdoc-filter.php:49
array
the array() calling protocol came about after MediaWiki 1.4rc1.
Wikimedia\Rdbms\Database\doRollback
doRollback( $fname)
Issues the ROLLBACK command to the database server.
Definition: Database.php:3866
Wikimedia\Rdbms\Database\$lbInfo
array $lbInfo
Definition: Database.php:130
Wikimedia\Rdbms\Database\doProfiledQuery
doProfiledQuery( $sql, $commentedSql, $isWrite, $fname)
Wrapper for query() that also handles profiling, logging, and affected row count updates.
Definition: Database.php:1216
Wikimedia\Rdbms\Database\getDBname
getDBname()
Get the current DB name.
Definition: Database.php:2204
Wikimedia\Rdbms\Blob
Definition: Blob.php:5
Wikimedia\Rdbms\Database\getInfinity
getInfinity()
Find out when 'infinity' is.
Definition: Database.php:4434
$type
$type
Definition: testCompression.php:48
Wikimedia\Rdbms\Database\$dbName
string $dbName
Database that this instance is currently connected to.
Definition: Database.php:85