MediaWiki  1.28.1
Database.php
Go to the documentation of this file.
1 <?php
29 
36 abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAwareInterface {
38  const DEADLOCK_TRIES = 4;
40  const DEADLOCK_DELAY_MIN = 500000;
42  const DEADLOCK_DELAY_MAX = 1500000;
43 
45  const PING_TTL = 1.0;
46  const PING_QUERY = 'SELECT 1 AS ping';
47 
48  const TINY_WRITE_SEC = .010;
49  const SLOW_WRITE_SEC = .500;
50  const SMALL_WRITE_ROWS = 100;
51 
53  protected $mLastQuery = '';
55  protected $mLastWriteTime = false;
57  protected $mPHPError = false;
59  protected $mServer;
61  protected $mUser;
63  protected $mPassword;
65  protected $mDBname;
67  protected $tableAliases = [];
69  protected $cliMode;
71  protected $agent;
72 
74  protected $srvCache;
76  protected $connLogger;
78  protected $queryLogger;
80  protected $errorLogger;
81 
83  protected $mConn = null;
85  protected $mOpened = false;
86 
88  protected $mTrxIdleCallbacks = [];
90  protected $mTrxPreCommitCallbacks = [];
92  protected $mTrxEndCallbacks = [];
94  protected $mTrxRecurringCallbacks = [];
96  protected $mTrxEndCallbacksSuppressed = false;
97 
99  protected $mTablePrefix = '';
101  protected $mSchema = '';
103  protected $mFlags;
105  protected $mLBInfo = [];
107  protected $mDefaultBigSelects = null;
109  protected $mSchemaVars = false;
111  protected $mSessionVars = [];
113  protected $preparedArgs;
115  protected $htmlErrors;
117  protected $delimiter = ';';
119  protected $currentDomain;
120 
127  protected $mTrxLevel = 0;
134  protected $mTrxShortId = '';
143  private $mTrxTimestamp = null;
145  private $mTrxReplicaLag = null;
153  private $mTrxFname = null;
160  private $mTrxDoneWrites = false;
167  private $mTrxAutomatic = false;
173  private $mTrxAtomicLevels = [];
179  private $mTrxAutomaticAtomic = false;
185  private $mTrxWriteCallers = [];
189  private $mTrxWriteDuration = 0.0;
193  private $mTrxWriteQueryCount = 0;
197  private $mTrxWriteAdjDuration = 0.0;
205  private $mRTTEstimate = 0.0;
206 
208  private $mNamedLocksHeld = [];
210  protected $mSessionTempTables = [];
211 
214 
216  protected $lastPing = 0.0;
217 
219  private $priorFlags = [];
220 
222  protected $profiler;
224  protected $trxProfiler;
225 
234  function __construct( array $params ) {
235  $server = $params['host'];
236  $user = $params['user'];
237  $password = $params['password'];
238  $dbName = $params['dbname'];
239 
240  $this->mSchema = $params['schema'];
241  $this->mTablePrefix = $params['tablePrefix'];
242 
243  $this->cliMode = $params['cliMode'];
244  // Agent name is added to SQL queries in a comment, so make sure it can't break out
245  $this->agent = str_replace( '/', '-', $params['agent'] );
246 
247  $this->mFlags = $params['flags'];
248  if ( $this->mFlags & self::DBO_DEFAULT ) {
249  if ( $this->cliMode ) {
250  $this->mFlags &= ~self::DBO_TRX;
251  } else {
252  $this->mFlags |= self::DBO_TRX;
253  }
254  }
255 
256  $this->mSessionVars = $params['variables'];
257 
258  $this->srvCache = isset( $params['srvCache'] )
259  ? $params['srvCache']
260  : new HashBagOStuff();
261 
262  $this->profiler = $params['profiler'];
263  $this->trxProfiler = $params['trxProfiler'];
264  $this->connLogger = $params['connLogger'];
265  $this->queryLogger = $params['queryLogger'];
266  $this->errorLogger = $params['errorLogger'];
267 
268  // Set initial dummy domain until open() sets the final DB/prefix
269  $this->currentDomain = DatabaseDomain::newUnspecified();
270 
271  if ( $user ) {
272  $this->open( $server, $user, $password, $dbName );
273  } elseif ( $this->requiresDatabaseUser() ) {
274  throw new InvalidArgumentException( "No database user provided." );
275  }
276 
277  // Set the domain object after open() sets the relevant fields
278  if ( $this->mDBname != '' ) {
279  // Domains with server scope but a table prefix are not used by IDatabase classes
280  $this->currentDomain = new DatabaseDomain( $this->mDBname, null, $this->mTablePrefix );
281  }
282  }
283 
325  final public static function factory( $dbType, $p = [] ) {
326  static $canonicalDBTypes = [
327  'mysql' => [ 'mysqli', 'mysql' ],
328  'postgres' => [],
329  'sqlite' => [],
330  'oracle' => [],
331  'mssql' => [],
332  ];
333 
334  $driver = false;
335  $dbType = strtolower( $dbType );
336  if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
337  $possibleDrivers = $canonicalDBTypes[$dbType];
338  if ( !empty( $p['driver'] ) ) {
339  if ( in_array( $p['driver'], $possibleDrivers ) ) {
340  $driver = $p['driver'];
341  } else {
342  throw new InvalidArgumentException( __METHOD__ .
343  " type '$dbType' does not support driver '{$p['driver']}'" );
344  }
345  } else {
346  foreach ( $possibleDrivers as $posDriver ) {
347  if ( extension_loaded( $posDriver ) ) {
348  $driver = $posDriver;
349  break;
350  }
351  }
352  }
353  } else {
354  $driver = $dbType;
355  }
356  if ( $driver === false || $driver === '' ) {
357  throw new InvalidArgumentException( __METHOD__ .
358  " no viable database extension found for type '$dbType'" );
359  }
360 
361  $class = 'Database' . ucfirst( $driver );
362  if ( class_exists( $class ) && is_subclass_of( $class, 'IDatabase' ) ) {
363  // Resolve some defaults for b/c
364  $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
365  $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
366  $p['password'] = isset( $p['password'] ) ? $p['password'] : false;
367  $p['dbname'] = isset( $p['dbname'] ) ? $p['dbname'] : false;
368  $p['flags'] = isset( $p['flags'] ) ? $p['flags'] : 0;
369  $p['variables'] = isset( $p['variables'] ) ? $p['variables'] : [];
370  $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : '';
371  $p['schema'] = isset( $p['schema'] ) ? $p['schema'] : '';
372  $p['cliMode'] = isset( $p['cliMode'] ) ? $p['cliMode'] : ( PHP_SAPI === 'cli' );
373  $p['agent'] = isset( $p['agent'] ) ? $p['agent'] : '';
374  if ( !isset( $p['connLogger'] ) ) {
375  $p['connLogger'] = new \Psr\Log\NullLogger();
376  }
377  if ( !isset( $p['queryLogger'] ) ) {
378  $p['queryLogger'] = new \Psr\Log\NullLogger();
379  }
380  $p['profiler'] = isset( $p['profiler'] ) ? $p['profiler'] : null;
381  if ( !isset( $p['trxProfiler'] ) ) {
382  $p['trxProfiler'] = new TransactionProfiler();
383  }
384  if ( !isset( $p['errorLogger'] ) ) {
385  $p['errorLogger'] = function ( Exception $e ) {
386  trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
387  };
388  }
389 
390  $conn = new $class( $p );
391  } else {
392  $conn = null;
393  }
394 
395  return $conn;
396  }
397 
398  public function setLogger( LoggerInterface $logger ) {
399  $this->queryLogger = $logger;
400  }
401 
402  public function getServerInfo() {
403  return $this->getServerVersion();
404  }
405 
406  public function bufferResults( $buffer = null ) {
407  $res = !$this->getFlag( self::DBO_NOBUFFER );
408  if ( $buffer !== null ) {
409  $buffer
410  ? $this->clearFlag( self::DBO_NOBUFFER )
411  : $this->setFlag( self::DBO_NOBUFFER );
412  }
413 
414  return $res;
415  }
416 
429  protected function ignoreErrors( $ignoreErrors = null ) {
430  $res = $this->getFlag( self::DBO_IGNORE );
431  if ( $ignoreErrors !== null ) {
432  $ignoreErrors
433  ? $this->setFlag( self::DBO_IGNORE )
434  : $this->clearFlag( self::DBO_IGNORE );
435  }
436 
437  return $res;
438  }
439 
440  public function trxLevel() {
441  return $this->mTrxLevel;
442  }
443 
444  public function trxTimestamp() {
445  return $this->mTrxLevel ? $this->mTrxTimestamp : null;
446  }
447 
448  public function tablePrefix( $prefix = null ) {
449  $old = $this->mTablePrefix;
450  if ( $prefix !== null ) {
451  $this->mTablePrefix = $prefix;
452  $this->currentDomain = ( $this->mDBname != '' )
453  ? new DatabaseDomain( $this->mDBname, null, $this->mTablePrefix )
455  }
456 
457  return $old;
458  }
459 
460  public function dbSchema( $schema = null ) {
461  $old = $this->mSchema;
462  if ( $schema !== null ) {
463  $this->mSchema = $schema;
464  }
465 
466  return $old;
467  }
468 
469  public function getLBInfo( $name = null ) {
470  if ( is_null( $name ) ) {
471  return $this->mLBInfo;
472  } else {
473  if ( array_key_exists( $name, $this->mLBInfo ) ) {
474  return $this->mLBInfo[$name];
475  } else {
476  return null;
477  }
478  }
479  }
480 
481  public function setLBInfo( $name, $value = null ) {
482  if ( is_null( $value ) ) {
483  $this->mLBInfo = $name;
484  } else {
485  $this->mLBInfo[$name] = $value;
486  }
487  }
488 
489  public function setLazyMasterHandle( IDatabase $conn ) {
490  $this->lazyMasterHandle = $conn;
491  }
492 
498  protected function getLazyMasterHandle() {
500  }
501 
502  public function implicitGroupby() {
503  return true;
504  }
505 
506  public function implicitOrderby() {
507  return true;
508  }
509 
510  public function lastQuery() {
511  return $this->mLastQuery;
512  }
513 
514  public function doneWrites() {
515  return (bool)$this->mLastWriteTime;
516  }
517 
518  public function lastDoneWrites() {
519  return $this->mLastWriteTime ?: false;
520  }
521 
522  public function writesPending() {
523  return $this->mTrxLevel && $this->mTrxDoneWrites;
524  }
525 
526  public function writesOrCallbacksPending() {
527  return $this->mTrxLevel && (
528  $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
529  );
530  }
531 
532  public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
533  if ( !$this->mTrxLevel ) {
534  return false;
535  } elseif ( !$this->mTrxDoneWrites ) {
536  return 0.0;
537  }
538 
539  switch ( $type ) {
540  case self::ESTIMATE_DB_APPLY:
541  $this->ping( $rtt );
542  $rttAdjTotal = $this->mTrxWriteAdjQueryCount * $rtt;
543  $applyTime = max( $this->mTrxWriteAdjDuration - $rttAdjTotal, 0 );
544  // For omitted queries, make them count as something at least
545  $omitted = $this->mTrxWriteQueryCount - $this->mTrxWriteAdjQueryCount;
546  $applyTime += self::TINY_WRITE_SEC * $omitted;
547 
548  return $applyTime;
549  default: // everything
551  }
552  }
553 
554  public function pendingWriteCallers() {
555  return $this->mTrxLevel ? $this->mTrxWriteCallers : [];
556  }
557 
558  protected function pendingWriteAndCallbackCallers() {
559  if ( !$this->mTrxLevel ) {
560  return [];
561  }
562 
563  $fnames = $this->mTrxWriteCallers;
564  foreach ( [
565  $this->mTrxIdleCallbacks,
566  $this->mTrxPreCommitCallbacks,
567  $this->mTrxEndCallbacks
568  ] as $callbacks ) {
569  foreach ( $callbacks as $callback ) {
570  $fnames[] = $callback[1];
571  }
572  }
573 
574  return $fnames;
575  }
576 
577  public function isOpen() {
578  return $this->mOpened;
579  }
580 
581  public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
582  if ( $remember === self::REMEMBER_PRIOR ) {
583  array_push( $this->priorFlags, $this->mFlags );
584  }
585  $this->mFlags |= $flag;
586  }
587 
588  public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
589  if ( $remember === self::REMEMBER_PRIOR ) {
590  array_push( $this->priorFlags, $this->mFlags );
591  }
592  $this->mFlags &= ~$flag;
593  }
594 
595  public function restoreFlags( $state = self::RESTORE_PRIOR ) {
596  if ( !$this->priorFlags ) {
597  return;
598  }
599 
600  if ( $state === self::RESTORE_INITIAL ) {
601  $this->mFlags = reset( $this->priorFlags );
602  $this->priorFlags = [];
603  } else {
604  $this->mFlags = array_pop( $this->priorFlags );
605  }
606  }
607 
608  public function getFlag( $flag ) {
609  return !!( $this->mFlags & $flag );
610  }
611 
617  public function getProperty( $name ) {
618  return $this->$name;
619  }
620 
621  public function getDomainID() {
622  return $this->currentDomain->getId();
623  }
624 
625  final public function getWikiID() {
626  return $this->getDomainID();
627  }
628 
636  abstract function indexInfo( $table, $index, $fname = __METHOD__ );
637 
644  abstract function strencode( $s );
645 
646  protected function installErrorHandler() {
647  $this->mPHPError = false;
648  $this->htmlErrors = ini_set( 'html_errors', '0' );
649  set_error_handler( [ $this, 'connectionErrorLogger' ] );
650  }
651 
655  protected function restoreErrorHandler() {
656  restore_error_handler();
657  if ( $this->htmlErrors !== false ) {
658  ini_set( 'html_errors', $this->htmlErrors );
659  }
660 
661  return $this->getLastPHPError();
662  }
663 
667  protected function getLastPHPError() {
668  if ( $this->mPHPError ) {
669  $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
670  $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
671 
672  return $error;
673  }
674 
675  return false;
676  }
677 
684  public function connectionErrorLogger( $errno, $errstr ) {
685  $this->mPHPError = $errstr;
686  }
687 
694  protected function getLogContext( array $extras = [] ) {
695  return array_merge(
696  [
697  'db_server' => $this->mServer,
698  'db_name' => $this->mDBname,
699  'db_user' => $this->mUser,
700  ],
701  $extras
702  );
703  }
704 
705  public function close() {
706  if ( $this->mConn ) {
707  if ( $this->trxLevel() ) {
708  $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
709  }
710 
711  $closed = $this->closeConnection();
712  $this->mConn = false;
713  } elseif ( $this->mTrxIdleCallbacks || $this->mTrxEndCallbacks ) { // sanity
714  throw new RuntimeException( "Transaction callbacks still pending." );
715  } else {
716  $closed = true;
717  }
718  $this->mOpened = false;
719 
720  return $closed;
721  }
722 
728  protected function assertOpen() {
729  if ( !$this->isOpen() ) {
730  throw new DBUnexpectedError( $this, "DB connection was already closed." );
731  }
732  }
733 
739  abstract protected function closeConnection();
740 
741  public function reportConnectionError( $error = 'Unknown error' ) {
742  $myError = $this->lastError();
743  if ( $myError ) {
744  $error = $myError;
745  }
746 
747  # New method
748  throw new DBConnectionError( $this, $error );
749  }
750 
758  abstract protected function doQuery( $sql );
759 
767  protected function isWriteQuery( $sql ) {
768  return !preg_match(
769  '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
770  }
771 
776  protected function getQueryVerb( $sql ) {
777  return preg_match( '/^\s*([a-z]+)/i', $sql, $m ) ? strtoupper( $m[1] ) : null;
778  }
779 
789  protected function isTransactableQuery( $sql ) {
790  return !in_array(
791  $this->getQueryVerb( $sql ),
792  [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET', 'CREATE', 'ALTER' ],
793  true
794  );
795  }
796 
801  protected function registerTempTableOperation( $sql ) {
802  if ( preg_match(
803  '/^CREATE\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
804  $sql,
805  $matches
806  ) ) {
807  $this->mSessionTempTables[$matches[1]] = 1;
808 
809  return true;
810  } elseif ( preg_match(
811  '/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
812  $sql,
813  $matches
814  ) ) {
815  unset( $this->mSessionTempTables[$matches[1]] );
816 
817  return true;
818  } elseif ( preg_match(
819  '/^(?:INSERT\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+[`"\']?(\w+)[`"\']?/i',
820  $sql,
821  $matches
822  ) ) {
823  return isset( $this->mSessionTempTables[$matches[1]] );
824  }
825 
826  return false;
827  }
828 
829  public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
830  $priorWritesPending = $this->writesOrCallbacksPending();
831  $this->mLastQuery = $sql;
832 
833  $isWrite = $this->isWriteQuery( $sql ) && !$this->registerTempTableOperation( $sql );
834  if ( $isWrite ) {
835  $reason = $this->getReadOnlyReason();
836  if ( $reason !== false ) {
837  throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
838  }
839  # Set a flag indicating that writes have been done
840  $this->mLastWriteTime = microtime( true );
841  }
842 
843  // Add trace comment to the begin of the sql string, right after the operator.
844  // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
845  $commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
846 
847  # Start implicit transactions that wrap the request if DBO_TRX is enabled
848  if ( !$this->mTrxLevel && $this->getFlag( self::DBO_TRX )
849  && $this->isTransactableQuery( $sql )
850  ) {
851  $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
852  $this->mTrxAutomatic = true;
853  }
854 
855  # Keep track of whether the transaction has write queries pending
856  if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWrite ) {
857  $this->mTrxDoneWrites = true;
858  $this->trxProfiler->transactionWritingIn(
859  $this->mServer, $this->mDBname, $this->mTrxShortId );
860  }
861 
862  if ( $this->getFlag( self::DBO_DEBUG ) ) {
863  $this->queryLogger->debug( "{$this->mDBname} {$commentedSql}" );
864  }
865 
866  # Avoid fatals if close() was called
867  $this->assertOpen();
868 
869  # Send the query to the server
870  $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
871 
872  # Try reconnecting if the connection was lost
873  if ( false === $ret && $this->wasErrorReissuable() ) {
874  $recoverable = $this->canRecoverFromDisconnect( $sql, $priorWritesPending );
875  # Stash the last error values before anything might clear them
876  $lastError = $this->lastError();
877  $lastErrno = $this->lastErrno();
878  # Update state tracking to reflect transaction loss due to disconnection
879  $this->handleSessionLoss();
880  if ( $this->reconnect() ) {
881  $msg = __METHOD__ . ": lost connection to {$this->getServer()}; reconnected";
882  $this->connLogger->warning( $msg );
883  $this->queryLogger->warning(
884  "$msg:\n" . ( new RuntimeException() )->getTraceAsString() );
885 
886  if ( !$recoverable ) {
887  # Callers may catch the exception and continue to use the DB
888  $this->reportQueryError( $lastError, $lastErrno, $sql, $fname );
889  } else {
890  # Should be safe to silently retry the query
891  $ret = $this->doProfiledQuery( $sql, $commentedSql, $isWrite, $fname );
892  }
893  } else {
894  $msg = __METHOD__ . ": lost connection to {$this->getServer()} permanently";
895  $this->connLogger->error( $msg );
896  }
897  }
898 
899  if ( false === $ret ) {
900  # Deadlocks cause the entire transaction to abort, not just the statement.
901  # http://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html
902  # https://www.postgresql.org/docs/9.1/static/explicit-locking.html
903  if ( $this->wasDeadlock() ) {
904  if ( $this->explicitTrxActive() || $priorWritesPending ) {
905  $tempIgnore = false; // not recoverable
906  }
907  # Update state tracking to reflect transaction loss
908  $this->handleSessionLoss();
909  }
910 
911  $this->reportQueryError(
912  $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
913  }
914 
915  $res = $this->resultObject( $ret );
916 
917  return $res;
918  }
919 
920  private function doProfiledQuery( $sql, $commentedSql, $isWrite, $fname ) {
921  $isMaster = !is_null( $this->getLBInfo( 'master' ) );
922  # generalizeSQL() will probably cut down the query to reasonable
923  # logging size most of the time. The substr is really just a sanity check.
924  if ( $isMaster ) {
925  $queryProf = 'query-m: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
926  } else {
927  $queryProf = 'query: ' . substr( self::generalizeSQL( $sql ), 0, 255 );
928  }
929 
930  # Include query transaction state
931  $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
932 
933  $startTime = microtime( true );
934  if ( $this->profiler ) {
935  call_user_func( [ $this->profiler, 'profileIn' ], $queryProf );
936  }
937  $ret = $this->doQuery( $commentedSql );
938  if ( $this->profiler ) {
939  call_user_func( [ $this->profiler, 'profileOut' ], $queryProf );
940  }
941  $queryRuntime = max( microtime( true ) - $startTime, 0.0 );
942 
943  unset( $queryProfSection ); // profile out (if set)
944 
945  if ( $ret !== false ) {
946  $this->lastPing = $startTime;
947  if ( $isWrite && $this->mTrxLevel ) {
948  $this->updateTrxWriteQueryTime( $sql, $queryRuntime );
949  $this->mTrxWriteCallers[] = $fname;
950  }
951  }
952 
953  if ( $sql === self::PING_QUERY ) {
954  $this->mRTTEstimate = $queryRuntime;
955  }
956 
957  $this->trxProfiler->recordQueryCompletion(
958  $queryProf, $startTime, $isWrite, $this->affectedRows()
959  );
960  $this->queryLogger->debug( $sql, [
961  'method' => $fname,
962  'master' => $isMaster,
963  'runtime' => $queryRuntime,
964  ] );
965 
966  return $ret;
967  }
968 
980  private function updateTrxWriteQueryTime( $sql, $runtime ) {
981  // Whether this is indicative of replica DB runtime (except for RBR or ws_repl)
982  $indicativeOfReplicaRuntime = true;
983  if ( $runtime > self::SLOW_WRITE_SEC ) {
984  $verb = $this->getQueryVerb( $sql );
985  // insert(), upsert(), replace() are fast unless bulky in size or blocked on locks
986  if ( $verb === 'INSERT' ) {
987  $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS;
988  } elseif ( $verb === 'REPLACE' ) {
989  $indicativeOfReplicaRuntime = $this->affectedRows() > self::SMALL_WRITE_ROWS / 2;
990  }
991  }
992 
993  $this->mTrxWriteDuration += $runtime;
994  $this->mTrxWriteQueryCount += 1;
995  if ( $indicativeOfReplicaRuntime ) {
996  $this->mTrxWriteAdjDuration += $runtime;
997  $this->mTrxWriteAdjQueryCount += 1;
998  }
999  }
1000 
1001  private function canRecoverFromDisconnect( $sql, $priorWritesPending ) {
1002  # Transaction dropped; this can mean lost writes, or REPEATABLE-READ snapshots.
1003  # Dropped connections also mean that named locks are automatically released.
1004  # Only allow error suppression in autocommit mode or when the lost transaction
1005  # didn't matter anyway (aside from DBO_TRX snapshot loss).
1006  if ( $this->mNamedLocksHeld ) {
1007  return false; // possible critical section violation
1008  } elseif ( $sql === 'COMMIT' ) {
1009  return !$priorWritesPending; // nothing written anyway? (T127428)
1010  } elseif ( $sql === 'ROLLBACK' ) {
1011  return true; // transaction lost...which is also what was requested :)
1012  } elseif ( $this->explicitTrxActive() ) {
1013  return false; // don't drop atomocity
1014  } elseif ( $priorWritesPending ) {
1015  return false; // prior writes lost from implicit transaction
1016  }
1017 
1018  return true;
1019  }
1020 
1021  private function handleSessionLoss() {
1022  $this->mTrxLevel = 0;
1023  $this->mTrxIdleCallbacks = []; // bug 65263
1024  $this->mTrxPreCommitCallbacks = []; // bug 65263
1025  $this->mSessionTempTables = [];
1026  $this->mNamedLocksHeld = [];
1027  try {
1028  // Handle callbacks in mTrxEndCallbacks
1029  $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
1030  $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
1031  return null;
1032  } catch ( Exception $e ) {
1033  // Already logged; move on...
1034  return $e;
1035  }
1036  }
1037 
1038  public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
1039  if ( $this->ignoreErrors() || $tempIgnore ) {
1040  $this->queryLogger->debug( "SQL ERROR (ignored): $error\n" );
1041  } else {
1042  $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
1043  $this->queryLogger->error(
1044  "{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
1045  $this->getLogContext( [
1046  'method' => __METHOD__,
1047  'errno' => $errno,
1048  'error' => $error,
1049  'sql1line' => $sql1line,
1050  'fname' => $fname,
1051  ] )
1052  );
1053  $this->queryLogger->debug( "SQL ERROR: " . $error . "\n" );
1054  throw new DBQueryError( $this, $error, $errno, $sql, $fname );
1055  }
1056  }
1057 
1058  public function freeResult( $res ) {
1059  }
1060 
1061  public function selectField(
1062  $table, $var, $cond = '', $fname = __METHOD__, $options = []
1063  ) {
1064  if ( $var === '*' ) { // sanity
1065  throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
1066  }
1067 
1068  if ( !is_array( $options ) ) {
1069  $options = [ $options ];
1070  }
1071 
1072  $options['LIMIT'] = 1;
1073 
1074  $res = $this->select( $table, $var, $cond, $fname, $options );
1075  if ( $res === false || !$this->numRows( $res ) ) {
1076  return false;
1077  }
1078 
1079  $row = $this->fetchRow( $res );
1080 
1081  if ( $row !== false ) {
1082  return reset( $row );
1083  } else {
1084  return false;
1085  }
1086  }
1087 
1088  public function selectFieldValues(
1089  $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1090  ) {
1091  if ( $var === '*' ) { // sanity
1092  throw new DBUnexpectedError( $this, "Cannot use a * field" );
1093  } elseif ( !is_string( $var ) ) { // sanity
1094  throw new DBUnexpectedError( $this, "Cannot use an array of fields" );
1095  }
1096 
1097  if ( !is_array( $options ) ) {
1098  $options = [ $options ];
1099  }
1100 
1101  $res = $this->select( $table, $var, $cond, $fname, $options, $join_conds );
1102  if ( $res === false ) {
1103  return false;
1104  }
1105 
1106  $values = [];
1107  foreach ( $res as $row ) {
1108  $values[] = $row->$var;
1109  }
1110 
1111  return $values;
1112  }
1113 
1123  protected function makeSelectOptions( $options ) {
1124  $preLimitTail = $postLimitTail = '';
1125  $startOpts = '';
1126 
1127  $noKeyOptions = [];
1128 
1129  foreach ( $options as $key => $option ) {
1130  if ( is_numeric( $key ) ) {
1131  $noKeyOptions[$option] = true;
1132  }
1133  }
1134 
1135  $preLimitTail .= $this->makeGroupByWithHaving( $options );
1136 
1137  $preLimitTail .= $this->makeOrderBy( $options );
1138 
1139  // if (isset($options['LIMIT'])) {
1140  // $tailOpts .= $this->limitResult('', $options['LIMIT'],
1141  // isset($options['OFFSET']) ? $options['OFFSET']
1142  // : false);
1143  // }
1144 
1145  if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1146  $postLimitTail .= ' FOR UPDATE';
1147  }
1148 
1149  if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
1150  $postLimitTail .= ' LOCK IN SHARE MODE';
1151  }
1152 
1153  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1154  $startOpts .= 'DISTINCT';
1155  }
1156 
1157  # Various MySQL extensions
1158  if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
1159  $startOpts .= ' /*! STRAIGHT_JOIN */';
1160  }
1161 
1162  if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
1163  $startOpts .= ' HIGH_PRIORITY';
1164  }
1165 
1166  if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
1167  $startOpts .= ' SQL_BIG_RESULT';
1168  }
1169 
1170  if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
1171  $startOpts .= ' SQL_BUFFER_RESULT';
1172  }
1173 
1174  if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
1175  $startOpts .= ' SQL_SMALL_RESULT';
1176  }
1177 
1178  if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
1179  $startOpts .= ' SQL_CALC_FOUND_ROWS';
1180  }
1181 
1182  if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
1183  $startOpts .= ' SQL_CACHE';
1184  }
1185 
1186  if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
1187  $startOpts .= ' SQL_NO_CACHE';
1188  }
1189 
1190  if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
1191  $useIndex = $this->useIndexClause( $options['USE INDEX'] );
1192  } else {
1193  $useIndex = '';
1194  }
1195  if ( isset( $options['IGNORE INDEX'] ) && is_string( $options['IGNORE INDEX'] ) ) {
1196  $ignoreIndex = $this->ignoreIndexClause( $options['IGNORE INDEX'] );
1197  } else {
1198  $ignoreIndex = '';
1199  }
1200 
1201  return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ];
1202  }
1203 
1212  protected function makeGroupByWithHaving( $options ) {
1213  $sql = '';
1214  if ( isset( $options['GROUP BY'] ) ) {
1215  $gb = is_array( $options['GROUP BY'] )
1216  ? implode( ',', $options['GROUP BY'] )
1217  : $options['GROUP BY'];
1218  $sql .= ' GROUP BY ' . $gb;
1219  }
1220  if ( isset( $options['HAVING'] ) ) {
1221  $having = is_array( $options['HAVING'] )
1222  ? $this->makeList( $options['HAVING'], self::LIST_AND )
1223  : $options['HAVING'];
1224  $sql .= ' HAVING ' . $having;
1225  }
1226 
1227  return $sql;
1228  }
1229 
1238  protected function makeOrderBy( $options ) {
1239  if ( isset( $options['ORDER BY'] ) ) {
1240  $ob = is_array( $options['ORDER BY'] )
1241  ? implode( ',', $options['ORDER BY'] )
1242  : $options['ORDER BY'];
1243 
1244  return ' ORDER BY ' . $ob;
1245  }
1246 
1247  return '';
1248  }
1249 
1250  public function select( $table, $vars, $conds = '', $fname = __METHOD__,
1251  $options = [], $join_conds = [] ) {
1252  $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
1253 
1254  return $this->query( $sql, $fname );
1255  }
1256 
1257  public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
1258  $options = [], $join_conds = []
1259  ) {
1260  if ( is_array( $vars ) ) {
1261  $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
1262  }
1263 
1264  $options = (array)$options;
1265  $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
1266  ? $options['USE INDEX']
1267  : [];
1268  $ignoreIndexes = (
1269  isset( $options['IGNORE INDEX'] ) &&
1270  is_array( $options['IGNORE INDEX'] )
1271  )
1272  ? $options['IGNORE INDEX']
1273  : [];
1274 
1275  if ( is_array( $table ) ) {
1276  $from = ' FROM ' .
1278  $table, $useIndexes, $ignoreIndexes, $join_conds );
1279  } elseif ( $table != '' ) {
1280  if ( $table[0] == ' ' ) {
1281  $from = ' FROM ' . $table;
1282  } else {
1283  $from = ' FROM ' .
1285  [ $table ], $useIndexes, $ignoreIndexes, [] );
1286  }
1287  } else {
1288  $from = '';
1289  }
1290 
1291  list( $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ) =
1292  $this->makeSelectOptions( $options );
1293 
1294  if ( !empty( $conds ) ) {
1295  if ( is_array( $conds ) ) {
1296  $conds = $this->makeList( $conds, self::LIST_AND );
1297  }
1298  $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex " .
1299  "WHERE $conds $preLimitTail";
1300  } else {
1301  $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
1302  }
1303 
1304  if ( isset( $options['LIMIT'] ) ) {
1305  $sql = $this->limitResult( $sql, $options['LIMIT'],
1306  isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
1307  }
1308  $sql = "$sql $postLimitTail";
1309 
1310  if ( isset( $options['EXPLAIN'] ) ) {
1311  $sql = 'EXPLAIN ' . $sql;
1312  }
1313 
1314  return $sql;
1315  }
1316 
1317  public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
1318  $options = [], $join_conds = []
1319  ) {
1320  $options = (array)$options;
1321  $options['LIMIT'] = 1;
1322  $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
1323 
1324  if ( $res === false ) {
1325  return false;
1326  }
1327 
1328  if ( !$this->numRows( $res ) ) {
1329  return false;
1330  }
1331 
1332  $obj = $this->fetchObject( $res );
1333 
1334  return $obj;
1335  }
1336 
1337  public function estimateRowCount(
1338  $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
1339  ) {
1340  $rows = 0;
1341  $res = $this->select( $table, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options );
1342 
1343  if ( $res ) {
1344  $row = $this->fetchRow( $res );
1345  $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
1346  }
1347 
1348  return $rows;
1349  }
1350 
1351  public function selectRowCount(
1352  $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1353  ) {
1354  $rows = 0;
1355  $sql = $this->selectSQLText( $tables, '1', $conds, $fname, $options, $join_conds );
1356  // The identifier quotes is primarily for MSSQL.
1357  $rowCountCol = $this->addIdentifierQuotes( "rowcount" );
1358  $tableName = $this->addIdentifierQuotes( "tmp_count" );
1359  $res = $this->query( "SELECT COUNT(*) AS $rowCountCol FROM ($sql) $tableName", $fname );
1360 
1361  if ( $res ) {
1362  $row = $this->fetchRow( $res );
1363  $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
1364  }
1365 
1366  return $rows;
1367  }
1368 
1377  protected static function generalizeSQL( $sql ) {
1378  # This does the same as the regexp below would do, but in such a way
1379  # as to avoid crashing php on some large strings.
1380  # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
1381 
1382  $sql = str_replace( "\\\\", '', $sql );
1383  $sql = str_replace( "\\'", '', $sql );
1384  $sql = str_replace( "\\\"", '', $sql );
1385  $sql = preg_replace( "/'.*'/s", "'X'", $sql );
1386  $sql = preg_replace( '/".*"/s', "'X'", $sql );
1387 
1388  # All newlines, tabs, etc replaced by single space
1389  $sql = preg_replace( '/\s+/', ' ', $sql );
1390 
1391  # All numbers => N,
1392  # except the ones surrounded by characters, e.g. l10n
1393  $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
1394  $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
1395 
1396  return $sql;
1397  }
1398 
1399  public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1400  $info = $this->fieldInfo( $table, $field );
1401 
1402  return (bool)$info;
1403  }
1404 
1405  public function indexExists( $table, $index, $fname = __METHOD__ ) {
1406  if ( !$this->tableExists( $table ) ) {
1407  return null;
1408  }
1409 
1410  $info = $this->indexInfo( $table, $index, $fname );
1411  if ( is_null( $info ) ) {
1412  return null;
1413  } else {
1414  return $info !== false;
1415  }
1416  }
1417 
1418  public function tableExists( $table, $fname = __METHOD__ ) {
1419  $tableRaw = $this->tableName( $table, 'raw' );
1420  if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
1421  return true; // already known to exist
1422  }
1423 
1424  $table = $this->tableName( $table );
1425  $old = $this->ignoreErrors( true );
1426  $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
1427  $this->ignoreErrors( $old );
1428 
1429  return (bool)$res;
1430  }
1431 
1432  public function indexUnique( $table, $index ) {
1433  $indexInfo = $this->indexInfo( $table, $index );
1434 
1435  if ( !$indexInfo ) {
1436  return null;
1437  }
1438 
1439  return !$indexInfo[0]->Non_unique;
1440  }
1441 
1448  protected function makeInsertOptions( $options ) {
1449  return implode( ' ', $options );
1450  }
1451 
1452  public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
1453  # No rows to insert, easy just return now
1454  if ( !count( $a ) ) {
1455  return true;
1456  }
1457 
1458  $table = $this->tableName( $table );
1459 
1460  if ( !is_array( $options ) ) {
1461  $options = [ $options ];
1462  }
1463 
1464  $fh = null;
1465  if ( isset( $options['fileHandle'] ) ) {
1466  $fh = $options['fileHandle'];
1467  }
1468  $options = $this->makeInsertOptions( $options );
1469 
1470  if ( isset( $a[0] ) && is_array( $a[0] ) ) {
1471  $multi = true;
1472  $keys = array_keys( $a[0] );
1473  } else {
1474  $multi = false;
1475  $keys = array_keys( $a );
1476  }
1477 
1478  $sql = 'INSERT ' . $options .
1479  " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
1480 
1481  if ( $multi ) {
1482  $first = true;
1483  foreach ( $a as $row ) {
1484  if ( $first ) {
1485  $first = false;
1486  } else {
1487  $sql .= ',';
1488  }
1489  $sql .= '(' . $this->makeList( $row ) . ')';
1490  }
1491  } else {
1492  $sql .= '(' . $this->makeList( $a ) . ')';
1493  }
1494 
1495  if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
1496  return false;
1497  } elseif ( $fh !== null ) {
1498  return true;
1499  }
1500 
1501  return (bool)$this->query( $sql, $fname );
1502  }
1503 
1510  protected function makeUpdateOptionsArray( $options ) {
1511  if ( !is_array( $options ) ) {
1512  $options = [ $options ];
1513  }
1514 
1515  $opts = [];
1516 
1517  if ( in_array( 'IGNORE', $options ) ) {
1518  $opts[] = 'IGNORE';
1519  }
1520 
1521  return $opts;
1522  }
1523 
1530  protected function makeUpdateOptions( $options ) {
1531  $opts = $this->makeUpdateOptionsArray( $options );
1532 
1533  return implode( ' ', $opts );
1534  }
1535 
1536  public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
1537  $table = $this->tableName( $table );
1538  $opts = $this->makeUpdateOptions( $options );
1539  $sql = "UPDATE $opts $table SET " . $this->makeList( $values, self::LIST_SET );
1540 
1541  if ( $conds !== [] && $conds !== '*' ) {
1542  $sql .= " WHERE " . $this->makeList( $conds, self::LIST_AND );
1543  }
1544 
1545  return $this->query( $sql, $fname );
1546  }
1547 
1548  public function makeList( $a, $mode = self::LIST_COMMA ) {
1549  if ( !is_array( $a ) ) {
1550  throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
1551  }
1552 
1553  $first = true;
1554  $list = '';
1555 
1556  foreach ( $a as $field => $value ) {
1557  if ( !$first ) {
1558  if ( $mode == self::LIST_AND ) {
1559  $list .= ' AND ';
1560  } elseif ( $mode == self::LIST_OR ) {
1561  $list .= ' OR ';
1562  } else {
1563  $list .= ',';
1564  }
1565  } else {
1566  $first = false;
1567  }
1568 
1569  if ( ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_numeric( $field ) ) {
1570  $list .= "($value)";
1571  } elseif ( $mode == self::LIST_SET && is_numeric( $field ) ) {
1572  $list .= "$value";
1573  } elseif (
1574  ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_array( $value )
1575  ) {
1576  // Remove null from array to be handled separately if found
1577  $includeNull = false;
1578  foreach ( array_keys( $value, null, true ) as $nullKey ) {
1579  $includeNull = true;
1580  unset( $value[$nullKey] );
1581  }
1582  if ( count( $value ) == 0 && !$includeNull ) {
1583  throw new InvalidArgumentException(
1584  __METHOD__ . ": empty input for field $field" );
1585  } elseif ( count( $value ) == 0 ) {
1586  // only check if $field is null
1587  $list .= "$field IS NULL";
1588  } else {
1589  // IN clause contains at least one valid element
1590  if ( $includeNull ) {
1591  // Group subconditions to ensure correct precedence
1592  $list .= '(';
1593  }
1594  if ( count( $value ) == 1 ) {
1595  // Special-case single values, as IN isn't terribly efficient
1596  // Don't necessarily assume the single key is 0; we don't
1597  // enforce linear numeric ordering on other arrays here.
1598  $value = array_values( $value )[0];
1599  $list .= $field . " = " . $this->addQuotes( $value );
1600  } else {
1601  $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
1602  }
1603  // if null present in array, append IS NULL
1604  if ( $includeNull ) {
1605  $list .= " OR $field IS NULL)";
1606  }
1607  }
1608  } elseif ( $value === null ) {
1609  if ( $mode == self::LIST_AND || $mode == self::LIST_OR ) {
1610  $list .= "$field IS ";
1611  } elseif ( $mode == self::LIST_SET ) {
1612  $list .= "$field = ";
1613  }
1614  $list .= 'NULL';
1615  } else {
1616  if (
1617  $mode == self::LIST_AND || $mode == self::LIST_OR || $mode == self::LIST_SET
1618  ) {
1619  $list .= "$field = ";
1620  }
1621  $list .= $mode == self::LIST_NAMES ? $value : $this->addQuotes( $value );
1622  }
1623  }
1624 
1625  return $list;
1626  }
1627 
1628  public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
1629  $conds = [];
1630 
1631  foreach ( $data as $base => $sub ) {
1632  if ( count( $sub ) ) {
1633  $conds[] = $this->makeList(
1634  [ $baseKey => $base, $subKey => array_keys( $sub ) ],
1635  self::LIST_AND );
1636  }
1637  }
1638 
1639  if ( $conds ) {
1640  return $this->makeList( $conds, self::LIST_OR );
1641  } else {
1642  // Nothing to search for...
1643  return false;
1644  }
1645  }
1646 
1647  public function aggregateValue( $valuedata, $valuename = 'value' ) {
1648  return $valuename;
1649  }
1650 
1651  public function bitNot( $field ) {
1652  return "(~$field)";
1653  }
1654 
1655  public function bitAnd( $fieldLeft, $fieldRight ) {
1656  return "($fieldLeft & $fieldRight)";
1657  }
1658 
1659  public function bitOr( $fieldLeft, $fieldRight ) {
1660  return "($fieldLeft | $fieldRight)";
1661  }
1662 
1663  public function buildConcat( $stringList ) {
1664  return 'CONCAT(' . implode( ',', $stringList ) . ')';
1665  }
1666 
1667  public function buildGroupConcatField(
1668  $delim, $table, $field, $conds = '', $join_conds = []
1669  ) {
1670  $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
1671 
1672  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1673  }
1674 
1675  public function buildStringCast( $field ) {
1676  return $field;
1677  }
1678 
1679  public function selectDB( $db ) {
1680  # Stub. Shouldn't cause serious problems if it's not overridden, but
1681  # if your database engine supports a concept similar to MySQL's
1682  # databases you may as well.
1683  $this->mDBname = $db;
1684 
1685  return true;
1686  }
1687 
1688  public function getDBname() {
1689  return $this->mDBname;
1690  }
1691 
1692  public function getServer() {
1693  return $this->mServer;
1694  }
1695 
1696  public function tableName( $name, $format = 'quoted' ) {
1697  # Skip the entire process when we have a string quoted on both ends.
1698  # Note that we check the end so that we will still quote any use of
1699  # use of `database`.table. But won't break things if someone wants
1700  # to query a database table with a dot in the name.
1701  if ( $this->isQuotedIdentifier( $name ) ) {
1702  return $name;
1703  }
1704 
1705  # Lets test for any bits of text that should never show up in a table
1706  # name. Basically anything like JOIN or ON which are actually part of
1707  # SQL queries, but may end up inside of the table value to combine
1708  # sql. Such as how the API is doing.
1709  # Note that we use a whitespace test rather than a \b test to avoid
1710  # any remote case where a word like on may be inside of a table name
1711  # surrounded by symbols which may be considered word breaks.
1712  if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
1713  return $name;
1714  }
1715 
1716  # Split database and table into proper variables.
1717  # We reverse the explode so that database.table and table both output
1718  # the correct table.
1719  $dbDetails = explode( '.', $name, 3 );
1720  if ( count( $dbDetails ) == 3 ) {
1721  list( $database, $schema, $table ) = $dbDetails;
1722  # We don't want any prefix added in this case
1723  $prefix = '';
1724  } elseif ( count( $dbDetails ) == 2 ) {
1725  list( $database, $table ) = $dbDetails;
1726  # We don't want any prefix added in this case
1727  $prefix = '';
1728  # In dbs that support it, $database may actually be the schema
1729  # but that doesn't affect any of the functionality here
1730  $schema = '';
1731  } else {
1732  list( $table ) = $dbDetails;
1733  if ( isset( $this->tableAliases[$table] ) ) {
1734  $database = $this->tableAliases[$table]['dbname'];
1735  $schema = is_string( $this->tableAliases[$table]['schema'] )
1736  ? $this->tableAliases[$table]['schema']
1737  : $this->mSchema;
1738  $prefix = is_string( $this->tableAliases[$table]['prefix'] )
1739  ? $this->tableAliases[$table]['prefix']
1741  } else {
1742  $database = '';
1743  $schema = $this->mSchema; # Default schema
1744  $prefix = $this->mTablePrefix; # Default prefix
1745  }
1746  }
1747 
1748  # Quote $table and apply the prefix if not quoted.
1749  # $tableName might be empty if this is called from Database::replaceVars()
1750  $tableName = "{$prefix}{$table}";
1751  if ( $format === 'quoted'
1752  && !$this->isQuotedIdentifier( $tableName )
1753  && $tableName !== ''
1754  ) {
1755  $tableName = $this->addIdentifierQuotes( $tableName );
1756  }
1757 
1758  # Quote $schema and $database and merge them with the table name if needed
1759  $tableName = $this->prependDatabaseOrSchema( $schema, $tableName, $format );
1760  $tableName = $this->prependDatabaseOrSchema( $database, $tableName, $format );
1761 
1762  return $tableName;
1763  }
1764 
1771  private function prependDatabaseOrSchema( $namespace, $relation, $format ) {
1772  if ( strlen( $namespace ) ) {
1773  if ( $format === 'quoted' && !$this->isQuotedIdentifier( $namespace ) ) {
1774  $namespace = $this->addIdentifierQuotes( $namespace );
1775  }
1776  $relation = $namespace . '.' . $relation;
1777  }
1778 
1779  return $relation;
1780  }
1781 
1782  public function tableNames() {
1783  $inArray = func_get_args();
1784  $retVal = [];
1785 
1786  foreach ( $inArray as $name ) {
1787  $retVal[$name] = $this->tableName( $name );
1788  }
1789 
1790  return $retVal;
1791  }
1792 
1793  public function tableNamesN() {
1794  $inArray = func_get_args();
1795  $retVal = [];
1796 
1797  foreach ( $inArray as $name ) {
1798  $retVal[] = $this->tableName( $name );
1799  }
1800 
1801  return $retVal;
1802  }
1803 
1812  protected function tableNameWithAlias( $name, $alias = false ) {
1813  if ( !$alias || $alias == $name ) {
1814  return $this->tableName( $name );
1815  } else {
1816  return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
1817  }
1818  }
1819 
1826  protected function tableNamesWithAlias( $tables ) {
1827  $retval = [];
1828  foreach ( $tables as $alias => $table ) {
1829  if ( is_numeric( $alias ) ) {
1830  $alias = $table;
1831  }
1832  $retval[] = $this->tableNameWithAlias( $table, $alias );
1833  }
1834 
1835  return $retval;
1836  }
1837 
1846  protected function fieldNameWithAlias( $name, $alias = false ) {
1847  if ( !$alias || (string)$alias === (string)$name ) {
1848  return $name;
1849  } else {
1850  return $name . ' AS ' . $this->addIdentifierQuotes( $alias ); // PostgreSQL needs AS
1851  }
1852  }
1853 
1860  protected function fieldNamesWithAlias( $fields ) {
1861  $retval = [];
1862  foreach ( $fields as $alias => $field ) {
1863  if ( is_numeric( $alias ) ) {
1864  $alias = $field;
1865  }
1866  $retval[] = $this->fieldNameWithAlias( $field, $alias );
1867  }
1868 
1869  return $retval;
1870  }
1871 
1883  $tables, $use_index = [], $ignore_index = [], $join_conds = []
1884  ) {
1885  $ret = [];
1886  $retJOIN = [];
1887  $use_index = (array)$use_index;
1888  $ignore_index = (array)$ignore_index;
1889  $join_conds = (array)$join_conds;
1890 
1891  foreach ( $tables as $alias => $table ) {
1892  if ( !is_string( $alias ) ) {
1893  // No alias? Set it equal to the table name
1894  $alias = $table;
1895  }
1896  // Is there a JOIN clause for this table?
1897  if ( isset( $join_conds[$alias] ) ) {
1898  list( $joinType, $conds ) = $join_conds[$alias];
1899  $tableClause = $joinType;
1900  $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
1901  if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
1902  $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
1903  if ( $use != '' ) {
1904  $tableClause .= ' ' . $use;
1905  }
1906  }
1907  if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX?
1908  $ignore = $this->ignoreIndexClause(
1909  implode( ',', (array)$ignore_index[$alias] ) );
1910  if ( $ignore != '' ) {
1911  $tableClause .= ' ' . $ignore;
1912  }
1913  }
1914  $on = $this->makeList( (array)$conds, self::LIST_AND );
1915  if ( $on != '' ) {
1916  $tableClause .= ' ON (' . $on . ')';
1917  }
1918 
1919  $retJOIN[] = $tableClause;
1920  } elseif ( isset( $use_index[$alias] ) ) {
1921  // Is there an INDEX clause for this table?
1922  $tableClause = $this->tableNameWithAlias( $table, $alias );
1923  $tableClause .= ' ' . $this->useIndexClause(
1924  implode( ',', (array)$use_index[$alias] )
1925  );
1926 
1927  $ret[] = $tableClause;
1928  } elseif ( isset( $ignore_index[$alias] ) ) {
1929  // Is there an INDEX clause for this table?
1930  $tableClause = $this->tableNameWithAlias( $table, $alias );
1931  $tableClause .= ' ' . $this->ignoreIndexClause(
1932  implode( ',', (array)$ignore_index[$alias] )
1933  );
1934 
1935  $ret[] = $tableClause;
1936  } else {
1937  $tableClause = $this->tableNameWithAlias( $table, $alias );
1938 
1939  $ret[] = $tableClause;
1940  }
1941  }
1942 
1943  // We can't separate explicit JOIN clauses with ',', use ' ' for those
1944  $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
1945  $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
1946 
1947  // Compile our final table clause
1948  return implode( ' ', [ $implicitJoins, $explicitJoins ] );
1949  }
1950 
1957  protected function indexName( $index ) {
1958  // Backwards-compatibility hack
1959  $renamed = [
1960  'ar_usertext_timestamp' => 'usertext_timestamp',
1961  'un_user_id' => 'user_id',
1962  'un_user_ip' => 'user_ip',
1963  ];
1964 
1965  if ( isset( $renamed[$index] ) ) {
1966  return $renamed[$index];
1967  } else {
1968  return $index;
1969  }
1970  }
1971 
1972  public function addQuotes( $s ) {
1973  if ( $s instanceof Blob ) {
1974  $s = $s->fetch();
1975  }
1976  if ( $s === null ) {
1977  return 'NULL';
1978  } elseif ( is_bool( $s ) ) {
1979  return (int)$s;
1980  } else {
1981  # This will also quote numeric values. This should be harmless,
1982  # and protects against weird problems that occur when they really
1983  # _are_ strings such as article titles and string->number->string
1984  # conversion is not 1:1.
1985  return "'" . $this->strencode( $s ) . "'";
1986  }
1987  }
1988 
1998  public function addIdentifierQuotes( $s ) {
1999  return '"' . str_replace( '"', '""', $s ) . '"';
2000  }
2001 
2011  public function isQuotedIdentifier( $name ) {
2012  return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
2013  }
2014 
2019  protected function escapeLikeInternal( $s ) {
2020  return addcslashes( $s, '\%_' );
2021  }
2022 
2023  public function buildLike() {
2024  $params = func_get_args();
2025 
2026  if ( count( $params ) > 0 && is_array( $params[0] ) ) {
2027  $params = $params[0];
2028  }
2029 
2030  $s = '';
2031 
2032  foreach ( $params as $value ) {
2033  if ( $value instanceof LikeMatch ) {
2034  $s .= $value->toString();
2035  } else {
2036  $s .= $this->escapeLikeInternal( $value );
2037  }
2038  }
2039 
2040  return " LIKE {$this->addQuotes( $s )} ";
2041  }
2042 
2043  public function anyChar() {
2044  return new LikeMatch( '_' );
2045  }
2046 
2047  public function anyString() {
2048  return new LikeMatch( '%' );
2049  }
2050 
2051  public function nextSequenceValue( $seqName ) {
2052  return null;
2053  }
2054 
2065  public function useIndexClause( $index ) {
2066  return '';
2067  }
2068 
2079  public function ignoreIndexClause( $index ) {
2080  return '';
2081  }
2082 
2083  public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
2084  $quotedTable = $this->tableName( $table );
2085 
2086  if ( count( $rows ) == 0 ) {
2087  return;
2088  }
2089 
2090  # Single row case
2091  if ( !is_array( reset( $rows ) ) ) {
2092  $rows = [ $rows ];
2093  }
2094 
2095  // @FXIME: this is not atomic, but a trx would break affectedRows()
2096  foreach ( $rows as $row ) {
2097  # Delete rows which collide
2098  if ( $uniqueIndexes ) {
2099  $sql = "DELETE FROM $quotedTable WHERE ";
2100  $first = true;
2101  foreach ( $uniqueIndexes as $index ) {
2102  if ( $first ) {
2103  $first = false;
2104  $sql .= '( ';
2105  } else {
2106  $sql .= ' ) OR ( ';
2107  }
2108  if ( is_array( $index ) ) {
2109  $first2 = true;
2110  foreach ( $index as $col ) {
2111  if ( $first2 ) {
2112  $first2 = false;
2113  } else {
2114  $sql .= ' AND ';
2115  }
2116  $sql .= $col . '=' . $this->addQuotes( $row[$col] );
2117  }
2118  } else {
2119  $sql .= $index . '=' . $this->addQuotes( $row[$index] );
2120  }
2121  }
2122  $sql .= ' )';
2123  $this->query( $sql, $fname );
2124  }
2125 
2126  # Now insert the row
2127  $this->insert( $table, $row, $fname );
2128  }
2129  }
2130 
2141  protected function nativeReplace( $table, $rows, $fname ) {
2142  $table = $this->tableName( $table );
2143 
2144  # Single row case
2145  if ( !is_array( reset( $rows ) ) ) {
2146  $rows = [ $rows ];
2147  }
2148 
2149  $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
2150  $first = true;
2151 
2152  foreach ( $rows as $row ) {
2153  if ( $first ) {
2154  $first = false;
2155  } else {
2156  $sql .= ',';
2157  }
2158 
2159  $sql .= '(' . $this->makeList( $row ) . ')';
2160  }
2161 
2162  return $this->query( $sql, $fname );
2163  }
2164 
2165  public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
2166  $fname = __METHOD__
2167  ) {
2168  if ( !count( $rows ) ) {
2169  return true; // nothing to do
2170  }
2171 
2172  if ( !is_array( reset( $rows ) ) ) {
2173  $rows = [ $rows ];
2174  }
2175 
2176  if ( count( $uniqueIndexes ) ) {
2177  $clauses = []; // list WHERE clauses that each identify a single row
2178  foreach ( $rows as $row ) {
2179  foreach ( $uniqueIndexes as $index ) {
2180  $index = is_array( $index ) ? $index : [ $index ]; // columns
2181  $rowKey = []; // unique key to this row
2182  foreach ( $index as $column ) {
2183  $rowKey[$column] = $row[$column];
2184  }
2185  $clauses[] = $this->makeList( $rowKey, self::LIST_AND );
2186  }
2187  }
2188  $where = [ $this->makeList( $clauses, self::LIST_OR ) ];
2189  } else {
2190  $where = false;
2191  }
2192 
2193  $useTrx = !$this->mTrxLevel;
2194  if ( $useTrx ) {
2195  $this->begin( $fname, self::TRANSACTION_INTERNAL );
2196  }
2197  try {
2198  # Update any existing conflicting row(s)
2199  if ( $where !== false ) {
2200  $ok = $this->update( $table, $set, $where, $fname );
2201  } else {
2202  $ok = true;
2203  }
2204  # Now insert any non-conflicting row(s)
2205  $ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok;
2206  } catch ( Exception $e ) {
2207  if ( $useTrx ) {
2208  $this->rollback( $fname, self::FLUSHING_INTERNAL );
2209  }
2210  throw $e;
2211  }
2212  if ( $useTrx ) {
2213  $this->commit( $fname, self::FLUSHING_INTERNAL );
2214  }
2215 
2216  return $ok;
2217  }
2218 
2219  public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
2220  $fname = __METHOD__
2221  ) {
2222  if ( !$conds ) {
2223  throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
2224  }
2225 
2226  $delTable = $this->tableName( $delTable );
2227  $joinTable = $this->tableName( $joinTable );
2228  $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
2229  if ( $conds != '*' ) {
2230  $sql .= 'WHERE ' . $this->makeList( $conds, self::LIST_AND );
2231  }
2232  $sql .= ')';
2233 
2234  $this->query( $sql, $fname );
2235  }
2236 
2237  public function textFieldSize( $table, $field ) {
2238  $table = $this->tableName( $table );
2239  $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
2240  $res = $this->query( $sql, __METHOD__ );
2241  $row = $this->fetchObject( $res );
2242 
2243  $m = [];
2244 
2245  if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
2246  $size = $m[1];
2247  } else {
2248  $size = -1;
2249  }
2250 
2251  return $size;
2252  }
2253 
2254  public function delete( $table, $conds, $fname = __METHOD__ ) {
2255  if ( !$conds ) {
2256  throw new DBUnexpectedError( $this, __METHOD__ . ' called with no conditions' );
2257  }
2258 
2259  $table = $this->tableName( $table );
2260  $sql = "DELETE FROM $table";
2261 
2262  if ( $conds != '*' ) {
2263  if ( is_array( $conds ) ) {
2264  $conds = $this->makeList( $conds, self::LIST_AND );
2265  }
2266  $sql .= ' WHERE ' . $conds;
2267  }
2268 
2269  return $this->query( $sql, $fname );
2270  }
2271 
2272  public function insertSelect(
2273  $destTable, $srcTable, $varMap, $conds,
2274  $fname = __METHOD__, $insertOptions = [], $selectOptions = []
2275  ) {
2276  if ( $this->cliMode ) {
2277  // For massive migrations with downtime, we don't want to select everything
2278  // into memory and OOM, so do all this native on the server side if possible.
2279  return $this->nativeInsertSelect(
2280  $destTable,
2281  $srcTable,
2282  $varMap,
2283  $conds,
2284  $fname,
2285  $insertOptions,
2286  $selectOptions
2287  );
2288  }
2289 
2290  // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
2291  // on only the master (without needing row-based-replication). It also makes it easy to
2292  // know how big the INSERT is going to be.
2293  $fields = [];
2294  foreach ( $varMap as $dstColumn => $sourceColumnOrSql ) {
2295  $fields[] = $this->fieldNameWithAlias( $sourceColumnOrSql, $dstColumn );
2296  }
2297  $selectOptions[] = 'FOR UPDATE';
2298  $res = $this->select( $srcTable, implode( ',', $fields ), $conds, $fname, $selectOptions );
2299  if ( !$res ) {
2300  return false;
2301  }
2302 
2303  $rows = [];
2304  foreach ( $res as $row ) {
2305  $rows[] = (array)$row;
2306  }
2307 
2308  return $this->insert( $destTable, $rows, $fname, $insertOptions );
2309  }
2310 
2311  protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
2312  $fname = __METHOD__,
2313  $insertOptions = [], $selectOptions = []
2314  ) {
2315  $destTable = $this->tableName( $destTable );
2316 
2317  if ( !is_array( $insertOptions ) ) {
2318  $insertOptions = [ $insertOptions ];
2319  }
2320 
2321  $insertOptions = $this->makeInsertOptions( $insertOptions );
2322 
2323  if ( !is_array( $selectOptions ) ) {
2324  $selectOptions = [ $selectOptions ];
2325  }
2326 
2327  list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) = $this->makeSelectOptions(
2328  $selectOptions );
2329 
2330  if ( is_array( $srcTable ) ) {
2331  $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
2332  } else {
2333  $srcTable = $this->tableName( $srcTable );
2334  }
2335 
2336  $sql = "INSERT $insertOptions" .
2337  " INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
2338  " SELECT $startOpts " . implode( ',', $varMap ) .
2339  " FROM $srcTable $useIndex $ignoreIndex ";
2340 
2341  if ( $conds != '*' ) {
2342  if ( is_array( $conds ) ) {
2343  $conds = $this->makeList( $conds, self::LIST_AND );
2344  }
2345  $sql .= " WHERE $conds";
2346  }
2347 
2348  $sql .= " $tailOpts";
2349 
2350  return $this->query( $sql, $fname );
2351  }
2352 
2372  public function limitResult( $sql, $limit, $offset = false ) {
2373  if ( !is_numeric( $limit ) ) {
2374  throw new DBUnexpectedError( $this,
2375  "Invalid non-numeric limit passed to limitResult()\n" );
2376  }
2377 
2378  return "$sql LIMIT "
2379  . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
2380  . "{$limit} ";
2381  }
2382 
2383  public function unionSupportsOrderAndLimit() {
2384  return true; // True for almost every DB supported
2385  }
2386 
2387  public function unionQueries( $sqls, $all ) {
2388  $glue = $all ? ') UNION ALL (' : ') UNION (';
2389 
2390  return '(' . implode( $glue, $sqls ) . ')';
2391  }
2392 
2393  public function conditional( $cond, $trueVal, $falseVal ) {
2394  if ( is_array( $cond ) ) {
2395  $cond = $this->makeList( $cond, self::LIST_AND );
2396  }
2397 
2398  return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
2399  }
2400 
2401  public function strreplace( $orig, $old, $new ) {
2402  return "REPLACE({$orig}, {$old}, {$new})";
2403  }
2404 
2405  public function getServerUptime() {
2406  return 0;
2407  }
2408 
2409  public function wasDeadlock() {
2410  return false;
2411  }
2412 
2413  public function wasLockTimeout() {
2414  return false;
2415  }
2416 
2417  public function wasErrorReissuable() {
2418  return false;
2419  }
2420 
2421  public function wasReadOnlyError() {
2422  return false;
2423  }
2424 
2431  public function wasConnectionError( $errno ) {
2432  return false;
2433  }
2434 
2435  public function deadlockLoop() {
2436  $args = func_get_args();
2437  $function = array_shift( $args );
2438  $tries = self::DEADLOCK_TRIES;
2439 
2440  $this->begin( __METHOD__ );
2441 
2442  $retVal = null;
2444  $e = null;
2445  do {
2446  try {
2447  $retVal = call_user_func_array( $function, $args );
2448  break;
2449  } catch ( DBQueryError $e ) {
2450  if ( $this->wasDeadlock() ) {
2451  // Retry after a randomized delay
2452  usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
2453  } else {
2454  // Throw the error back up
2455  throw $e;
2456  }
2457  }
2458  } while ( --$tries > 0 );
2459 
2460  if ( $tries <= 0 ) {
2461  // Too many deadlocks; give up
2462  $this->rollback( __METHOD__ );
2463  throw $e;
2464  } else {
2465  $this->commit( __METHOD__ );
2466 
2467  return $retVal;
2468  }
2469  }
2470 
2471  public function masterPosWait( DBMasterPos $pos, $timeout ) {
2472  # Real waits are implemented in the subclass.
2473  return 0;
2474  }
2475 
2476  public function getReplicaPos() {
2477  # Stub
2478  return false;
2479  }
2480 
2481  public function getMasterPos() {
2482  # Stub
2483  return false;
2484  }
2485 
2486  public function serverIsReadOnly() {
2487  return false;
2488  }
2489 
2490  final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
2491  if ( !$this->mTrxLevel ) {
2492  throw new DBUnexpectedError( $this, "No transaction is active." );
2493  }
2494  $this->mTrxEndCallbacks[] = [ $callback, $fname ];
2495  }
2496 
2497  final public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
2498  $this->mTrxIdleCallbacks[] = [ $callback, $fname ];
2499  if ( !$this->mTrxLevel ) {
2500  $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
2501  }
2502  }
2503 
2504  final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
2505  if ( $this->mTrxLevel ) {
2506  $this->mTrxPreCommitCallbacks[] = [ $callback, $fname ];
2507  } else {
2508  // If no transaction is active, then make one for this callback
2509  $this->startAtomic( __METHOD__ );
2510  try {
2511  call_user_func( $callback );
2512  $this->endAtomic( __METHOD__ );
2513  } catch ( Exception $e ) {
2514  $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
2515  throw $e;
2516  }
2517  }
2518  }
2519 
2520  final public function setTransactionListener( $name, callable $callback = null ) {
2521  if ( $callback ) {
2522  $this->mTrxRecurringCallbacks[$name] = $callback;
2523  } else {
2524  unset( $this->mTrxRecurringCallbacks[$name] );
2525  }
2526  }
2527 
2536  final public function setTrxEndCallbackSuppression( $suppress ) {
2537  $this->mTrxEndCallbacksSuppressed = $suppress;
2538  }
2539 
2549  public function runOnTransactionIdleCallbacks( $trigger ) {
2550  if ( $this->mTrxEndCallbacksSuppressed ) {
2551  return;
2552  }
2553 
2554  $autoTrx = $this->getFlag( self::DBO_TRX ); // automatic begin() enabled?
2556  $e = null; // first exception
2557  do { // callbacks may add callbacks :)
2558  $callbacks = array_merge(
2559  $this->mTrxIdleCallbacks,
2560  $this->mTrxEndCallbacks // include "transaction resolution" callbacks
2561  );
2562  $this->mTrxIdleCallbacks = []; // consumed (and recursion guard)
2563  $this->mTrxEndCallbacks = []; // consumed (recursion guard)
2564  foreach ( $callbacks as $callback ) {
2565  try {
2566  list( $phpCallback ) = $callback;
2567  $this->clearFlag( self::DBO_TRX ); // make each query its own transaction
2568  call_user_func_array( $phpCallback, [ $trigger ] );
2569  if ( $autoTrx ) {
2570  $this->setFlag( self::DBO_TRX ); // restore automatic begin()
2571  } else {
2572  $this->clearFlag( self::DBO_TRX ); // restore auto-commit
2573  }
2574  } catch ( Exception $ex ) {
2575  call_user_func( $this->errorLogger, $ex );
2576  $e = $e ?: $ex;
2577  // Some callbacks may use startAtomic/endAtomic, so make sure
2578  // their transactions are ended so other callbacks don't fail
2579  if ( $this->trxLevel() ) {
2580  $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
2581  }
2582  }
2583  }
2584  } while ( count( $this->mTrxIdleCallbacks ) );
2585 
2586  if ( $e instanceof Exception ) {
2587  throw $e; // re-throw any first exception
2588  }
2589  }
2590 
2600  $e = null; // first exception
2601  do { // callbacks may add callbacks :)
2602  $callbacks = $this->mTrxPreCommitCallbacks;
2603  $this->mTrxPreCommitCallbacks = []; // consumed (and recursion guard)
2604  foreach ( $callbacks as $callback ) {
2605  try {
2606  list( $phpCallback ) = $callback;
2607  call_user_func( $phpCallback );
2608  } catch ( Exception $ex ) {
2609  call_user_func( $this->errorLogger, $ex );
2610  $e = $e ?: $ex;
2611  }
2612  }
2613  } while ( count( $this->mTrxPreCommitCallbacks ) );
2614 
2615  if ( $e instanceof Exception ) {
2616  throw $e; // re-throw any first exception
2617  }
2618  }
2619 
2629  public function runTransactionListenerCallbacks( $trigger ) {
2630  if ( $this->mTrxEndCallbacksSuppressed ) {
2631  return;
2632  }
2633 
2635  $e = null; // first exception
2636 
2637  foreach ( $this->mTrxRecurringCallbacks as $phpCallback ) {
2638  try {
2639  $phpCallback( $trigger, $this );
2640  } catch ( Exception $ex ) {
2641  call_user_func( $this->errorLogger, $ex );
2642  $e = $e ?: $ex;
2643  }
2644  }
2645 
2646  if ( $e instanceof Exception ) {
2647  throw $e; // re-throw any first exception
2648  }
2649  }
2650 
2651  final public function startAtomic( $fname = __METHOD__ ) {
2652  if ( !$this->mTrxLevel ) {
2653  $this->begin( $fname, self::TRANSACTION_INTERNAL );
2654  // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
2655  // in all changes being in one transaction to keep requests transactional.
2656  if ( !$this->getFlag( self::DBO_TRX ) ) {
2657  $this->mTrxAutomaticAtomic = true;
2658  }
2659  }
2660 
2661  $this->mTrxAtomicLevels[] = $fname;
2662  }
2663 
2664  final public function endAtomic( $fname = __METHOD__ ) {
2665  if ( !$this->mTrxLevel ) {
2666  throw new DBUnexpectedError( $this, "No atomic transaction is open (got $fname)." );
2667  }
2668  if ( !$this->mTrxAtomicLevels ||
2669  array_pop( $this->mTrxAtomicLevels ) !== $fname
2670  ) {
2671  throw new DBUnexpectedError( $this, "Invalid atomic section ended (got $fname)." );
2672  }
2673 
2674  if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) {
2675  $this->commit( $fname, self::FLUSHING_INTERNAL );
2676  }
2677  }
2678 
2679  final public function doAtomicSection( $fname, callable $callback ) {
2680  $this->startAtomic( $fname );
2681  try {
2682  $res = call_user_func_array( $callback, [ $this, $fname ] );
2683  } catch ( Exception $e ) {
2684  $this->rollback( $fname, self::FLUSHING_INTERNAL );
2685  throw $e;
2686  }
2687  $this->endAtomic( $fname );
2688 
2689  return $res;
2690  }
2691 
2692  final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
2693  // Protect against mismatched atomic section, transaction nesting, and snapshot loss
2694  if ( $this->mTrxLevel ) {
2695  if ( $this->mTrxAtomicLevels ) {
2696  $levels = implode( ', ', $this->mTrxAtomicLevels );
2697  $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
2698  throw new DBUnexpectedError( $this, $msg );
2699  } elseif ( !$this->mTrxAutomatic ) {
2700  $msg = "$fname: Explicit transaction already active (from {$this->mTrxFname}).";
2701  throw new DBUnexpectedError( $this, $msg );
2702  } else {
2703  // @TODO: make this an exception at some point
2704  $msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
2705  $this->queryLogger->error( $msg );
2706  return; // join the main transaction set
2707  }
2708  } elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
2709  // @TODO: make this an exception at some point
2710  $msg = "$fname: Implicit transaction expected (DBO_TRX set).";
2711  $this->queryLogger->error( $msg );
2712  return; // let any writes be in the main transaction
2713  }
2714 
2715  // Avoid fatals if close() was called
2716  $this->assertOpen();
2717 
2718  $this->doBegin( $fname );
2719  $this->mTrxTimestamp = microtime( true );
2720  $this->mTrxFname = $fname;
2721  $this->mTrxDoneWrites = false;
2722  $this->mTrxAutomaticAtomic = false;
2723  $this->mTrxAtomicLevels = [];
2724  $this->mTrxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
2725  $this->mTrxWriteDuration = 0.0;
2726  $this->mTrxWriteQueryCount = 0;
2727  $this->mTrxWriteAdjDuration = 0.0;
2728  $this->mTrxWriteAdjQueryCount = 0;
2729  $this->mTrxWriteCallers = [];
2730  // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
2731  // Get an estimate of the replica DB lag before then, treating estimate staleness
2732  // as lag itself just to be safe
2733  $status = $this->getApproximateLagStatus();
2734  $this->mTrxReplicaLag = $status['lag'] + ( microtime( true ) - $status['since'] );
2735  // T147697: make explicitTrxActive() return true until begin() finishes. This way, no
2736  // caller will think its OK to muck around with the transaction just because startAtomic()
2737  // has not yet completed (e.g. setting mTrxAtomicLevels).
2738  $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
2739  }
2740 
2747  protected function doBegin( $fname ) {
2748  $this->query( 'BEGIN', $fname );
2749  $this->mTrxLevel = 1;
2750  }
2751 
2752  final public function commit( $fname = __METHOD__, $flush = '' ) {
2753  if ( $this->mTrxLevel && $this->mTrxAtomicLevels ) {
2754  // There are still atomic sections open. This cannot be ignored
2755  $levels = implode( ', ', $this->mTrxAtomicLevels );
2756  throw new DBUnexpectedError(
2757  $this,
2758  "$fname: Got COMMIT while atomic sections $levels are still open."
2759  );
2760  }
2761 
2762  if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
2763  if ( !$this->mTrxLevel ) {
2764  return; // nothing to do
2765  } elseif ( !$this->mTrxAutomatic ) {
2766  throw new DBUnexpectedError(
2767  $this,
2768  "$fname: Flushing an explicit transaction, getting out of sync."
2769  );
2770  }
2771  } else {
2772  if ( !$this->mTrxLevel ) {
2773  $this->queryLogger->error(
2774  "$fname: No transaction to commit, something got out of sync." );
2775  return; // nothing to do
2776  } elseif ( $this->mTrxAutomatic ) {
2777  // @TODO: make this an exception at some point
2778  $msg = "$fname: Explicit commit of implicit transaction.";
2779  $this->queryLogger->error( $msg );
2780  return; // wait for the main transaction set commit round
2781  }
2782  }
2783 
2784  // Avoid fatals if close() was called
2785  $this->assertOpen();
2786 
2788  $writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY );
2789  $this->doCommit( $fname );
2790  if ( $this->mTrxDoneWrites ) {
2791  $this->mLastWriteTime = microtime( true );
2792  $this->trxProfiler->transactionWritingOut(
2793  $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
2794  }
2795 
2796  $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
2797  $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT );
2798  }
2799 
2806  protected function doCommit( $fname ) {
2807  if ( $this->mTrxLevel ) {
2808  $this->query( 'COMMIT', $fname );
2809  $this->mTrxLevel = 0;
2810  }
2811  }
2812 
2813  final public function rollback( $fname = __METHOD__, $flush = '' ) {
2814  if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
2815  if ( !$this->mTrxLevel ) {
2816  return; // nothing to do
2817  }
2818  } else {
2819  if ( !$this->mTrxLevel ) {
2820  $this->queryLogger->error(
2821  "$fname: No transaction to rollback, something got out of sync." );
2822  return; // nothing to do
2823  } elseif ( $this->getFlag( self::DBO_TRX ) ) {
2824  throw new DBUnexpectedError(
2825  $this,
2826  "$fname: Expected mass rollback of all peer databases (DBO_TRX set)."
2827  );
2828  }
2829  }
2830 
2831  // Avoid fatals if close() was called
2832  $this->assertOpen();
2833 
2834  $this->doRollback( $fname );
2835  $this->mTrxAtomicLevels = [];
2836  if ( $this->mTrxDoneWrites ) {
2837  $this->trxProfiler->transactionWritingOut(
2838  $this->mServer, $this->mDBname, $this->mTrxShortId );
2839  }
2840 
2841  $this->mTrxIdleCallbacks = []; // clear
2842  $this->mTrxPreCommitCallbacks = []; // clear
2843  $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
2844  $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
2845  }
2846 
2853  protected function doRollback( $fname ) {
2854  if ( $this->mTrxLevel ) {
2855  # Disconnects cause rollback anyway, so ignore those errors
2856  $ignoreErrors = true;
2857  $this->query( 'ROLLBACK', $fname, $ignoreErrors );
2858  $this->mTrxLevel = 0;
2859  }
2860  }
2861 
2862  public function flushSnapshot( $fname = __METHOD__ ) {
2863  if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
2864  // This only flushes transactions to clear snapshots, not to write data
2865  $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
2866  throw new DBUnexpectedError(
2867  $this,
2868  "$fname: Cannot flush snapshot because writes are pending ($fnames)."
2869  );
2870  }
2871 
2872  $this->commit( $fname, self::FLUSHING_INTERNAL );
2873  }
2874 
2875  public function explicitTrxActive() {
2876  return $this->mTrxLevel && ( $this->mTrxAtomicLevels || !$this->mTrxAutomatic );
2877  }
2878 
2894  public function duplicateTableStructure( $oldName, $newName, $temporary = false,
2895  $fname = __METHOD__
2896  ) {
2897  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2898  }
2899 
2900  public function listTables( $prefix = null, $fname = __METHOD__ ) {
2901  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2902  }
2903 
2904  public function listViews( $prefix = null, $fname = __METHOD__ ) {
2905  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2906  }
2907 
2908  public function timestamp( $ts = 0 ) {
2909  $t = new ConvertibleTimestamp( $ts );
2910  // Let errors bubble up to avoid putting garbage in the DB
2911  return $t->getTimestamp( TS_MW );
2912  }
2913 
2914  public function timestampOrNull( $ts = null ) {
2915  if ( is_null( $ts ) ) {
2916  return null;
2917  } else {
2918  return $this->timestamp( $ts );
2919  }
2920  }
2921 
2935  protected function resultObject( $result ) {
2936  if ( !$result ) {
2937  return false;
2938  } elseif ( $result instanceof ResultWrapper ) {
2939  return $result;
2940  } elseif ( $result === true ) {
2941  // Successful write query
2942  return $result;
2943  } else {
2944  return new ResultWrapper( $this, $result );
2945  }
2946  }
2947 
2948  public function ping( &$rtt = null ) {
2949  // Avoid hitting the server if it was hit recently
2950  if ( $this->isOpen() && ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
2951  if ( !func_num_args() || $this->mRTTEstimate > 0 ) {
2952  $rtt = $this->mRTTEstimate;
2953  return true; // don't care about $rtt
2954  }
2955  }
2956 
2957  // This will reconnect if possible or return false if not
2958  $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
2959  $ok = ( $this->query( self::PING_QUERY, __METHOD__, true ) !== false );
2960  $this->restoreFlags( self::RESTORE_PRIOR );
2961 
2962  if ( $ok ) {
2963  $rtt = $this->mRTTEstimate;
2964  }
2965 
2966  return $ok;
2967  }
2968 
2972  protected function reconnect() {
2973  $this->closeConnection();
2974  $this->mOpened = false;
2975  $this->mConn = false;
2976  try {
2977  $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
2978  $this->lastPing = microtime( true );
2979  $ok = true;
2980  } catch ( DBConnectionError $e ) {
2981  $ok = false;
2982  }
2983 
2984  return $ok;
2985  }
2986 
2987  public function getSessionLagStatus() {
2988  return $this->getTransactionLagStatus() ?: $this->getApproximateLagStatus();
2989  }
2990 
3002  protected function getTransactionLagStatus() {
3003  return $this->mTrxLevel
3004  ? [ 'lag' => $this->mTrxReplicaLag, 'since' => $this->trxTimestamp() ]
3005  : null;
3006  }
3007 
3014  protected function getApproximateLagStatus() {
3015  return [
3016  'lag' => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
3017  'since' => microtime( true )
3018  ];
3019  }
3020 
3039  public static function getCacheSetOptions( IDatabase $db1 ) {
3040  $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
3041  foreach ( func_get_args() as $db ) {
3043  $status = $db->getSessionLagStatus();
3044  if ( $status['lag'] === false ) {
3045  $res['lag'] = false;
3046  } elseif ( $res['lag'] !== false ) {
3047  $res['lag'] = max( $res['lag'], $status['lag'] );
3048  }
3049  $res['since'] = min( $res['since'], $status['since'] );
3050  $res['pending'] = $res['pending'] ?: $db->writesPending();
3051  }
3052 
3053  return $res;
3054  }
3055 
3056  public function getLag() {
3057  return 0;
3058  }
3059 
3060  public function maxListLen() {
3061  return 0;
3062  }
3063 
3064  public function encodeBlob( $b ) {
3065  return $b;
3066  }
3067 
3068  public function decodeBlob( $b ) {
3069  if ( $b instanceof Blob ) {
3070  $b = $b->fetch();
3071  }
3072  return $b;
3073  }
3074 
3075  public function setSessionOptions( array $options ) {
3076  }
3077 
3078  public function sourceFile(
3079  $filename,
3080  callable $lineCallback = null,
3081  callable $resultCallback = null,
3082  $fname = false,
3083  callable $inputCallback = null
3084  ) {
3085  MediaWiki\suppressWarnings();
3086  $fp = fopen( $filename, 'r' );
3087  MediaWiki\restoreWarnings();
3088 
3089  if ( false === $fp ) {
3090  throw new RuntimeException( "Could not open \"{$filename}\".\n" );
3091  }
3092 
3093  if ( !$fname ) {
3094  $fname = __METHOD__ . "( $filename )";
3095  }
3096 
3097  try {
3098  $error = $this->sourceStream(
3099  $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
3100  } catch ( Exception $e ) {
3101  fclose( $fp );
3102  throw $e;
3103  }
3104 
3105  fclose( $fp );
3106 
3107  return $error;
3108  }
3109 
3110  public function setSchemaVars( $vars ) {
3111  $this->mSchemaVars = $vars;
3112  }
3113 
3114  public function sourceStream(
3115  $fp,
3116  callable $lineCallback = null,
3117  callable $resultCallback = null,
3118  $fname = __METHOD__,
3119  callable $inputCallback = null
3120  ) {
3121  $cmd = '';
3122 
3123  while ( !feof( $fp ) ) {
3124  if ( $lineCallback ) {
3125  call_user_func( $lineCallback );
3126  }
3127 
3128  $line = trim( fgets( $fp ) );
3129 
3130  if ( $line == '' ) {
3131  continue;
3132  }
3133 
3134  if ( '-' == $line[0] && '-' == $line[1] ) {
3135  continue;
3136  }
3137 
3138  if ( $cmd != '' ) {
3139  $cmd .= ' ';
3140  }
3141 
3142  $done = $this->streamStatementEnd( $cmd, $line );
3143 
3144  $cmd .= "$line\n";
3145 
3146  if ( $done || feof( $fp ) ) {
3147  $cmd = $this->replaceVars( $cmd );
3148 
3149  if ( !$inputCallback || call_user_func( $inputCallback, $cmd ) ) {
3150  $res = $this->query( $cmd, $fname );
3151 
3152  if ( $resultCallback ) {
3153  call_user_func( $resultCallback, $res, $this );
3154  }
3155 
3156  if ( false === $res ) {
3157  $err = $this->lastError();
3158 
3159  return "Query \"{$cmd}\" failed with error code \"$err\".\n";
3160  }
3161  }
3162  $cmd = '';
3163  }
3164  }
3165 
3166  return true;
3167  }
3168 
3176  public function streamStatementEnd( &$sql, &$newLine ) {
3177  if ( $this->delimiter ) {
3178  $prev = $newLine;
3179  $newLine = preg_replace(
3180  '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
3181  if ( $newLine != $prev ) {
3182  return true;
3183  }
3184  }
3185 
3186  return false;
3187  }
3188 
3209  protected function replaceVars( $ins ) {
3210  $vars = $this->getSchemaVars();
3211  return preg_replace_callback(
3212  '!
3213  /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
3214  \'\{\$ (\w+) }\' | # 3. addQuotes
3215  `\{\$ (\w+) }` | # 4. addIdentifierQuotes
3216  /\*\$ (\w+) \*/ # 5. leave unencoded
3217  !x',
3218  function ( $m ) use ( $vars ) {
3219  // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
3220  // check for both nonexistent keys *and* the empty string.
3221  if ( isset( $m[1] ) && $m[1] !== '' ) {
3222  if ( $m[1] === 'i' ) {
3223  return $this->indexName( $m[2] );
3224  } else {
3225  return $this->tableName( $m[2] );
3226  }
3227  } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
3228  return $this->addQuotes( $vars[$m[3]] );
3229  } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
3230  return $this->addIdentifierQuotes( $vars[$m[4]] );
3231  } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
3232  return $vars[$m[5]];
3233  } else {
3234  return $m[0];
3235  }
3236  },
3237  $ins
3238  );
3239  }
3240 
3247  protected function getSchemaVars() {
3248  if ( $this->mSchemaVars ) {
3249  return $this->mSchemaVars;
3250  } else {
3251  return $this->getDefaultSchemaVars();
3252  }
3253  }
3254 
3263  protected function getDefaultSchemaVars() {
3264  return [];
3265  }
3266 
3267  public function lockIsFree( $lockName, $method ) {
3268  return true;
3269  }
3270 
3271  public function lock( $lockName, $method, $timeout = 5 ) {
3272  $this->mNamedLocksHeld[$lockName] = 1;
3273 
3274  return true;
3275  }
3276 
3277  public function unlock( $lockName, $method ) {
3278  unset( $this->mNamedLocksHeld[$lockName] );
3279 
3280  return true;
3281  }
3282 
3283  public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
3284  if ( $this->writesOrCallbacksPending() ) {
3285  // This only flushes transactions to clear snapshots, not to write data
3286  $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
3287  throw new DBUnexpectedError(
3288  $this,
3289  "$fname: Cannot flush pre-lock snapshot because writes are pending ($fnames)."
3290  );
3291  }
3292 
3293  if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
3294  return null;
3295  }
3296 
3297  $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
3298  if ( $this->trxLevel() ) {
3299  // There is a good chance an exception was thrown, causing any early return
3300  // from the caller. Let any error handler get a chance to issue rollback().
3301  // If there isn't one, let the error bubble up and trigger server-side rollback.
3302  $this->onTransactionResolution(
3303  function () use ( $lockKey, $fname ) {
3304  $this->unlock( $lockKey, $fname );
3305  },
3306  $fname
3307  );
3308  } else {
3309  $this->unlock( $lockKey, $fname );
3310  }
3311  } );
3312 
3313  $this->commit( $fname, self::FLUSHING_INTERNAL );
3314 
3315  return $unlocker;
3316  }
3317 
3318  public function namedLocksEnqueue() {
3319  return false;
3320  }
3321 
3331  public function lockTables( $read, $write, $method, $lowPriority = true ) {
3332  return true;
3333  }
3334 
3341  public function unlockTables( $method ) {
3342  return true;
3343  }
3344 
3352  public function dropTable( $tableName, $fName = __METHOD__ ) {
3353  if ( !$this->tableExists( $tableName, $fName ) ) {
3354  return false;
3355  }
3356  $sql = "DROP TABLE " . $this->tableName( $tableName ) . " CASCADE";
3357 
3358  return $this->query( $sql, $fName );
3359  }
3360 
3361  public function getInfinity() {
3362  return 'infinity';
3363  }
3364 
3365  public function encodeExpiry( $expiry ) {
3366  return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
3367  ? $this->getInfinity()
3368  : $this->timestamp( $expiry );
3369  }
3370 
3371  public function decodeExpiry( $expiry, $format = TS_MW ) {
3372  if ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) {
3373  return 'infinity';
3374  }
3375 
3376  return ConvertibleTimestamp::convert( $format, $expiry );
3377  }
3378 
3379  public function setBigSelects( $value = true ) {
3380  // no-op
3381  }
3382 
3383  public function isReadOnly() {
3384  return ( $this->getReadOnlyReason() !== false );
3385  }
3386 
3390  protected function getReadOnlyReason() {
3391  $reason = $this->getLBInfo( 'readOnlyReason' );
3392 
3393  return is_string( $reason ) ? $reason : false;
3394  }
3395 
3396  public function setTableAliases( array $aliases ) {
3397  $this->tableAliases = $aliases;
3398  }
3399 
3404  protected function requiresDatabaseUser() {
3405  return true;
3406  }
3407 
3419  protected function getBindingHandle() {
3420  if ( !$this->mConn ) {
3421  throw new DBUnexpectedError(
3422  $this,
3423  'DB connection was already closed or the connection dropped.'
3424  );
3425  }
3426 
3427  return $this->mConn;
3428  }
3429 
3434  public function __toString() {
3435  return (string)$this->mConn;
3436  }
3437 
3442  public function __clone() {
3443  $this->connLogger->warning(
3444  "Cloning " . get_class( $this ) . " is not recomended; forking connection:\n" .
3445  ( new RuntimeException() )->getTraceAsString()
3446  );
3447 
3448  if ( $this->isOpen() ) {
3449  // Open a new connection resource without messing with the old one
3450  $this->mOpened = false;
3451  $this->mConn = false;
3452  $this->mTrxEndCallbacks = []; // don't copy
3453  $this->handleSessionLoss(); // no trx or locks anymore
3454  $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
3455  $this->lastPing = microtime( true );
3456  }
3457  }
3458 
3464  public function __sleep() {
3465  throw new RuntimeException( 'Database serialization may cause problems, since ' .
3466  'the connection is not restored on wakeup.' );
3467  }
3468 
3472  public function __destruct() {
3473  if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
3474  trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
3475  }
3476 
3477  $danglingWriters = $this->pendingWriteAndCallbackCallers();
3478  if ( $danglingWriters ) {
3479  $fnames = implode( ', ', $danglingWriters );
3480  trigger_error( "DB transaction writes or callbacks still pending ($fnames)." );
3481  }
3482 
3483  if ( $this->mConn ) {
3484  // Avoid connection leaks for sanity. Normally, resources close at script completion.
3485  // The connection might already be closed in zend/hhvm by now, so suppress warnings.
3486  \MediaWiki\suppressWarnings();
3487  $this->closeConnection();
3488  \MediaWiki\restoreWarnings();
3489  $this->mConn = false;
3490  $this->mOpened = false;
3491  }
3492  }
3493 
3498  public function getSearchEngine() {
3499  wfDeprecated( __METHOD__, '1.28' );
3501  }
3502 }
3503 
3504 class_alias( 'Database', 'DatabaseBase' );
rollback($fname=__METHOD__, $flush= '')
Rollback a transaction previously started using begin().
Definition: Database.php:2813
isTransactableQuery($sql)
Determine whether a SQL statement is sensitive to isolation level.
Definition: Database.php:789
Library for creating, parsing, and converting timestamps.
decodeExpiry($expiry, $format=TS_MW)
Decode an expiry time into a DBMS independent format.
Definition: Database.php:3371
const DBO_IGNORE
Definition: defines.php:8
masterPosWait(DBMasterPos $pos, $timeout)
Wait for the replica DB to catch up to a given master position.
Definition: Database.php:2471
commit($fname=__METHOD__, $flush= '')
Commits a transaction previously started using begin().
Definition: Database.php:2752
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
reportQueryError($error, $errno, $sql, $fname, $tempIgnore=false)
Report a query error.
Definition: Database.php:1038
getInfinity()
Find out when 'infinity' is.
Definition: Database.php:3361
canRecoverFromDisconnect($sql, $priorWritesPending)
Definition: Database.php:1001
string $agent
Agent name for query profiling.
Definition: Database.php:71
__destruct()
Run a few simple sanity checks and close dangling connections.
Definition: Database.php:3472
tableExists($table, $fname=__METHOD__)
Query whether a given table exists.
Definition: Database.php:1418
float $mTrxWriteDuration
Seconds spent in write queries for the current transaction.
Definition: Database.php:189
the array() calling protocol came about after MediaWiki 1.4rc1.
bool $cliMode
Whether this PHP instance is for a CLI script.
Definition: Database.php:69
TransactionProfiler $trxProfiler
Definition: Database.php:224
getSearchEngine()
Definition: Database.php:3498
reportConnectionError($error= 'Unknown error')
Definition: Database.php:741
array[] $mTrxEndCallbacks
List of (callable, method name)
Definition: Database.php:92
select($table, $vars, $conds= '', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
Definition: Database.php:1250
maxListLen()
Return the maximum number of items allowed in a list, or 0 for unlimited.
Definition: Database.php:3060
Utility classThis allows us to distinguish a blob from a normal string and an array of strings...
Definition: Blob.php:8
addIdentifierQuotes($s)
Quotes an identifier using backticks or "double quotes" depending on the database type...
Definition: Database.php:1998
static newUnspecified()
writesOrCallbacksPending()
Returns true if there is a transaction open with possible write queries or transaction pre-commit/idl...
Definition: Database.php:526
__sleep()
Called by serialize.
Definition: Database.php:3464
insert($table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
Definition: Database.php:1452
setLBInfo($name, $value=null)
Set the LB info array, or a member of it.
Definition: Database.php:481
getQueryVerb($sql)
Definition: Database.php:776
string $mDBname
Definition: Database.php:65
$tableAliases
Definition: Database.php:67
doCommit($fname)
Issues the COMMIT command to the database server.
Definition: Database.php:2806
static factory($dbType, $p=[])
Construct a Database subclass instance given a database type and parameters.
Definition: Database.php:325
bufferResults($buffer=null)
Turns buffering of SQL result sets on (true) or off (false).
Definition: Database.php:406
trxLevel()
Gets the current transaction level.
Definition: Database.php:440
doneWrites()
Returns true if the connection may have been used for write queries.
Definition: Database.php:514
setSessionOptions(array $options)
Override database's default behavior.
Definition: Database.php:3075
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
const SLOW_WRITE_SEC
Definition: Database.php:49
strencode($s)
Wrapper for addslashes()
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:1936
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
wasReadOnlyError()
Determines if the last failure was due to the database being read-only.
Definition: Database.php:2421
tableNamesN()
Fetch a number of table names into an zero-indexed numerical array This is handy when you need to con...
Definition: Database.php:1793
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2102
buildConcat($stringList)
Build a concatenation list to feed into a SQL query.
Definition: Database.php:1663
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:3039
const LIST_NAMES
Definition: Defines.php:37
float bool $mLastWriteTime
UNIX timestamp of last write query.
Definition: Database.php:55
DatabaseDomain $currentDomain
Definition: Database.php:119
float $lastPing
UNIX timestamp.
Definition: Database.php:216
trxTimestamp()
Get the UNIX timestamp of the time that the transaction was established.
Definition: Database.php:444
getReadOnlyReason()
Definition: Database.php:3390
aggregateValue($valuedata, $valuename= 'value')
Return aggregated value alias.
Definition: Database.php:1647
const SMALL_WRITE_ROWS
Definition: Database.php:50
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
Definition: Database.php:3396
doRollback($fname)
Issues the ROLLBACK command to the database server.
Definition: Database.php:2853
implicitGroupby()
Returns true if this database does an implicit sort when doing GROUP BY.
Definition: Database.php:502
deadlockLoop()
Perform a deadlock-prone transaction.
Definition: Database.php:2435
runTransactionListenerCallbacks($trigger)
Actually run any "transaction listener" callbacks.
Definition: Database.php:2629
open($server, $user, $password, $dbName)
Open a connection to the database.
timestamp($ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:2908
unlockTables($method)
Unlock specific tables.
Definition: Database.php:3341
BagOStuff $srvCache
APC cache.
Definition: Database.php:74
freeResult($res)
Free a result object returned by query() or select().
Definition: Database.php:1058
$value
getApproximateLagStatus()
Get a replica DB lag estimate for this server.
Definition: Database.php:3014
restoreFlags($state=self::RESTORE_PRIOR)
Restore the flags to their prior state before the last setFlag/clearFlag call.
Definition: Database.php:595
bool $mOpened
Definition: Database.php:85
selectRowCount($tables, $vars= '*', $conds= '', $fname=__METHOD__, $options=[], $join_conds=[])
Get the number of rows in dataset.
Definition: Database.php:1351
assertOpen()
Make sure isOpen() returns true as a sanity check.
Definition: Database.php:728
getServerVersion()
A string describing the current software version, like from mysql_get_server_info().
writesPending()
Definition: Database.php:522
integer $mFlags
Definition: Database.php:103
update($table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
Definition: Database.php:1536
lastError()
Get a description of the last error.
__construct(array $params)
Constructor and database handle and attempt to connect to the DB server.
Definition: Database.php:234
explicitTrxActive()
Definition: Database.php:2875
getServerInfo()
A string describing the current software version, and possibly other details in a user-friendly way...
Definition: Database.php:402
pendingWriteAndCallbackCallers()
Definition: Database.php:558
An object representing a master or replica DB position in a replicated setup.
Definition: DBMasterPos.php:7
tableNameWithAlias($name, $alias=false)
Get an aliased table name e.g.
Definition: Database.php:1812
makeUpdateOptionsArray($options)
Make UPDATE options array for Database::makeUpdateOptions.
Definition: Database.php:1510
array[] $mTrxIdleCallbacks
List of (callable, method name)
Definition: Database.php:88
unionSupportsOrderAndLimit()
Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries within th...
Definition: Database.php:2383
LoggerInterface $queryLogger
Definition: Database.php:78
setFlag($flag, $remember=self::REMEMBER_NOTHING)
Set a flag for this connection.
Definition: Database.php:581
callback $errorLogger
Error logging callback.
Definition: Database.php:80
handleSessionLoss()
Definition: Database.php:1021
const PING_QUERY
Definition: Database.php:46
fieldInfo($table, $field)
mysql_fetch_field() wrapper Returns false if the field doesn't exist
integer $mTrxWriteAdjQueryCount
Number of write queries counted in mTrxWriteAdjDuration.
Definition: Database.php:201
encodeBlob($b)
Some DBMSs have a special format for inserting into blob fields, they don't allow simple quoted strin...
Definition: Database.php:3064
tableName($name, $format= 'quoted')
Format a table name ready for use in constructing an SQL query.
Definition: Database.php:1696
escapeLikeInternal($s)
Definition: Database.php:2019
bool $mTrxAutomatic
Record if the current transaction was started implicitly due to DBO_TRX being set.
Definition: Database.php:167
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:1882
anyChar()
Returns a token for buildLike() that denotes a '_' to be used in a LIKE query.
Definition: Database.php:2043
unionQueries($sqls, $all)
Construct a UNION query This is used for providing overload point for other DB abstractions not compa...
Definition: Database.php:2387
selectDB($db)
Change the current database.
Definition: Database.php:1679
lockIsFree($lockName, $method)
Check to see if a named lock is available (non-blocking)
Definition: Database.php:3267
fieldNameWithAlias($name, $alias=false)
Get an aliased field name e.g.
Definition: Database.php:1846
lastErrno()
Get the last error number.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition: hooks.txt:1007
tableNames()
Fetch a number of table names into an array This is handy when you need to construct SQL for joins...
Definition: Database.php:1782
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
Definition: Database.php:506
selectRow($table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Single row SELECT wrapper.
Definition: Database.php:1317
bool $mTrxEndCallbacksSuppressed
Whether to suppress triggering of transaction end callbacks.
Definition: Database.php:96
getProperty($name)
Definition: Database.php:617
makeOrderBy($options)
Returns an optional ORDER BY.
Definition: Database.php:1238
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message.Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item.Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page.Return false to stop further processing of the tag $reader:XMLReader object &$pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision.Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag.Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload.Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports.&$fullInterwikiPrefix:Interwiki prefix, may contain colons.&$pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable.Can be used to lazy-load the import sources list.&$importSources:The value of $wgImportSources.Modify as necessary.See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page.$context:IContextSource object &$pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect.&$title:Title object for the current page &$request:WebRequest &$ignoreRedirect:boolean to skip redirect check &$target:Title/string of redirect target &$article:Article object 'InternalParseBeforeLinks':during Parser's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InternalParseBeforeSanitize':during Parser's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings.Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not.Return true without providing an interwiki to continue interwiki search.$prefix:interwiki prefix we are looking for.&$iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user's email has been invalidated successfully.$user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification.Callee may modify $url and $query, URL will be constructed as $url.$query &$url:URL to index.php &$query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) &$article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() &$ip:IP being check &$result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from &$allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn't match your organization.$addr:The e-mail address entered by the user &$result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user &$result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we're looking for a messages file for &$file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED!Use $magicWords in a file listed in $wgExtensionMessagesFiles instead.Use this to define synonyms of magic words depending of the language &$magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces.Do not use this hook to add namespaces.Use CanonicalNamespaces for that.&$namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED!Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead.Use to define aliases of special pages names depending of the language &$specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names.&$names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page's language links.This is called in various places to allow extensions to define the effective language links for a page.$title:The page's Title.&$links:Associative array mapping language codes to prefixed links of the form"language:title".&$linkFlags:Associative array mapping prefixed links to arrays of flags.Currently unused, but planned to provide support for marking individual language links in the UI, e.g.for featured articles. 'LanguageSelector':Hook to change the language selector available on a page.$out:The output page.$cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED!Use HtmlPageLinkRendererBegin instead.Used when generating internal and interwiki links in Linker::link(), before processing starts.Return false to skip default processing and return $ret.See documentation for Linker::link() for details on the expected meanings of parameters.$skin:the Skin object $target:the Title that the link is pointing to &$html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1934
if($line===false) $args
Definition: cdb.php:64
string $mServer
Definition: Database.php:59
affectedRows()
Get the number of rows affected by the last write query.
nativeReplace($table, $rows, $fname)
REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE statement.
Definition: Database.php:2141
Class to handle database/prefix specification for IDatabase domains.
nextSequenceValue($seqName)
Returns an appropriately quoted sequence value for inserting a new row.
Definition: Database.php:2051
doProfiledQuery($sql, $commentedSql, $isWrite, $fname)
Definition: Database.php:920
deleteJoin($delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
Definition: Database.php:2219
indexName($index)
Get the name of an index in a given table.
Definition: Database.php:1957
timestampOrNull($ts=null)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:2914
indexInfo($table, $index, $fname=__METHOD__)
Get information about an index into an object.
setLogger(LoggerInterface $logger)
Definition: Database.php:398
setTransactionListener($name, callable $callback=null)
Run a callback each time any transaction commits or rolls back.
Definition: Database.php:2520
endAtomic($fname=__METHOD__)
Ends an atomic section of SQL statements.
Definition: Database.php:2664
updateTrxWriteQueryTime($sql, $runtime)
Update the estimated run-time of a query, not counting large row lock times.
Definition: Database.php:980
startAtomic($fname=__METHOD__)
Begin an atomic section of statements.
Definition: Database.php:2651
begin($fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
Definition: Database.php:2692
isReadOnly()
Definition: Database.php:3383
string $mSchema
Definition: Database.php:101
string $mLastQuery
SQL query.
Definition: Database.php:53
requiresDatabaseUser()
Definition: Database.php:3404
const DBO_DEBUG
Definition: defines.php:6
const LIST_AND
Definition: Defines.php:35
makeGroupByWithHaving($options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1212
float $mRTTEstimate
RTT time estimate.
Definition: Database.php:205
textFieldSize($table, $field)
Returns the size of a text field, or -1 for "unlimited".
Definition: Database.php:2237
onTransactionIdle(callable $callback, $fname=__METHOD__)
Run a callback as soon as there is no transaction pending.
Definition: Database.php:2497
getTransactionLagStatus()
Get the replica DB lag when the current transaction started.
Definition: Database.php:3002
object string $profiler
Class name or object With profileIn/profileOut methods.
Definition: Database.php:222
close()
Closes a database connection.
Definition: Database.php:705
string bool $mPHPError
Definition: Database.php:57
Helper class that detects high-contention DB queries via profiling calls.
setLazyMasterHandle(IDatabase $conn)
Set a lazy-connecting DB handle to the master DB (for replication status purposes) ...
Definition: Database.php:489
getLBInfo($name=null)
Get properties passed down from the server info array of the load balancer.
Definition: Database.php:469
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1046
decodeBlob($b)
Some DBMSs return a special placeholder object representing blob fields in result objects...
Definition: Database.php:3068
lastDoneWrites()
Returns the last time the connection may have been used for write queries.
Definition: Database.php:518
fetchObject($res)
Fetch the next row from the given result object, in object form.
const LIST_COMMA
Definition: Defines.php:34
dbSchema($schema=null)
Get/set the db schema.
Definition: Database.php:460
ignoreIndexClause($index)
IGNORE INDEX clause.
Definition: Database.php:2079
$res
Definition: database.txt:21
doAtomicSection($fname, callable $callback)
Run a callback to do an atomic set of updates for this database.
Definition: Database.php:2679
const DEADLOCK_DELAY_MAX
Maximum time to wait before retry.
Definition: Database.php:42
getFlag($flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition: Database.php:608
bitOr($fieldLeft, $fieldRight)
Definition: Database.php:1659
buildGroupConcatField($delim, $table, $field, $conds= '', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
Definition: Database.php:1667
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:3114
static generalizeSQL($sql)
Removes most variables from an SQL query and replaces them with X or N for numbers.
Definition: Database.php:1377
__clone()
Make sure that copies do not share the same client binding handle.
Definition: Database.php:3442
$params
isOpen()
Is a connection to the database open?
Definition: Database.php:577
makeList($a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Definition: Database.php:1548
string[] $mTrxWriteCallers
Track the write query callers of the current transaction.
Definition: Database.php:185
clearFlag($flag, $remember=self::REMEMBER_NOTHING)
Clear a flag for this connection.
Definition: Database.php:588
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: defines.php:11
getLastPHPError()
Definition: Database.php:667
insertSelect($destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[])
INSERT SELECT wrapper.
Definition: Database.php:2272
isQuotedIdentifier($name)
Returns if the given identifier looks quoted or not according to the database convention for quoting ...
Definition: Database.php:2011
wasConnectionError($errno)
Do not use this method outside of Database/DBError classes.
Definition: Database.php:2431
$buffer
selectSQLText($table, $vars, $conds= '', $fname=__METHOD__, $options=[], $join_conds=[])
The equivalent of IDatabase::select() except that the constructed SQL is returned, instead of being immediately executed.
Definition: Database.php:1257
const DEADLOCK_DELAY_MIN
Minimum time to wait before retry, in microseconds.
Definition: Database.php:40
int $mTrxLevel
Either 1 if a transaction is active or 0 otherwise.
Definition: Database.php:127
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition: Database.php:694
string $mTrxShortId
Either a short hexidecimal string if a transaction is active or "".
Definition: Database.php:134
conditional($cond, $trueVal, $falseVal)
Returns an SQL expression for a simple conditional.
Definition: Database.php:2393
ping(&$rtt=null)
Ping the server and try to reconnect if it there is no connection.
Definition: Database.php:2948
array bool $mSchemaVars
Definition: Database.php:109
serverIsReadOnly()
Definition: Database.php:2486
array $mNamedLocksHeld
Map of (name => 1) for locks obtained via lock()
Definition: Database.php:208
Used by Database::buildLike() to represent characters that have special meaning in SQL LIKE clauses a...
Definition: LikeMatch.php:7
onTransactionPreCommitOrIdle(callable $callback, $fname=__METHOD__)
Run a callback before the current transaction commits or now if there is none.
Definition: Database.php:2504
wasErrorReissuable()
Determines if the last query error was due to a dropped connection and should be dealt with by pingin...
Definition: Database.php:2417
float $mTrxReplicaLag
Lag estimate at the time of BEGIN.
Definition: Database.php:145
ignoreErrors($ignoreErrors=null)
Turns on (false) or off (true) the automatic generation and sending of a "we're sorry, but there has been a database error" page on database errors.
Definition: Database.php:429
bitAnd($fieldLeft, $fieldRight)
Definition: Database.php:1655
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:1123
string $mPassword
Definition: Database.php:63
getScopedLockAndFlush($lockKey, $fname, $timeout)
Acquire a named lock, flush any transaction, and return an RAII style unlocker object.
Definition: Database.php:3283
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
LoggerInterface $connLogger
Definition: Database.php:76
buildStringCast($field)
Definition: Database.php:1675
closeConnection()
Closes underlying database connection.
const PING_TTL
How long before it is worth doing a dummy query to test the connection.
Definition: Database.php:45
callable[] $mTrxRecurringCallbacks
Map of (name => callable)
Definition: Database.php:94
bool $mTrxDoneWrites
Record if possible write queries were done in the last transaction started.
Definition: Database.php:160
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:2165
bool $mTrxAutomaticAtomic
Record if the current transaction was started implicitly by Database::startAtomic.
Definition: Database.php:179
duplicateTableStructure($oldName, $newName, $temporary=false, $fname=__METHOD__)
Creates a new table with structure copied from existing table Note that unlike most database abstract...
Definition: Database.php:2894
const LIST_SET
Definition: Defines.php:36
fetchRow($res)
Fetch the next row from the given result object, in associative array form.
string $mTrxFname
Remembers the function name given for starting the most recent transaction via begin().
Definition: Database.php:153
estimateRowCount($table, $vars= '*', $conds= '', $fname=__METHOD__, $options=[])
Estimate the number of rows in dataset.
Definition: Database.php:1337
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
sourceFile($filename, callable $lineCallback=null, callable $resultCallback=null, $fname=false, callable $inputCallback=null)
Read and execute SQL commands from a file.
Definition: Database.php:3078
integer $mTrxWriteQueryCount
Number of write queries for the current transaction.
Definition: Database.php:193
setBigSelects($value=true)
Allow or deny "big selects" for this session only.
Definition: Database.php:3379
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
Definition: Database.php:3176
const LIST_OR
Definition: Defines.php:38
numRows($res)
Get the number of rows in a result object.
const DBO_TRX
Definition: defines.php:9
buildLike()
LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match conta...
Definition: Database.php:2023
getReplicaPos()
Get the replication position of this replica DB.
Definition: Database.php:2476
indexExists($table, $index, $fname=__METHOD__)
Determines whether an index exists Usually throws a DBQueryError on failure If errors are explicitly ...
Definition: Database.php:1405
unlock($lockName, $method)
Release a lock.
Definition: Database.php:3277
getBindingHandle()
Get the underlying binding handle, mConn.
Definition: Database.php:3419
bitNot($field)
Definition: Database.php:1651
const DBO_NOBUFFER
Definition: defines.php:7
getLazyMasterHandle()
Definition: Database.php:498
wasLockTimeout()
Determines if the last failure was due to a lock timeout.
Definition: Database.php:2413
wasDeadlock()
Determines if the last failure was due to a deadlock.
Definition: Database.php:2409
runOnTransactionIdleCallbacks($trigger)
Actually run and consume any "on transaction idle/resolution" callbacks.
Definition: Database.php:2549
flushSnapshot($fname=__METHOD__)
Commit any transaction but error out if writes or callbacks are pending.
Definition: Database.php:2862
$from
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
const DBO_DEFAULT
Definition: defines.php:10
lockTables($read, $write, $method, $lowPriority=true)
Lock specific tables.
Definition: Database.php:3331
strreplace($orig, $old, $new)
Returns a comand for str_replace function in SQL query.
Definition: Database.php:2401
addQuotes($s)
Adds quotes and backslashes.
Definition: Database.php:1972
doBegin($fname)
Issues the BEGIN command to the database server.
Definition: Database.php:2747
getMasterPos()
Get the position of this master.
Definition: Database.php:2481
anyString()
Returns a token for buildLike() that denotes a '' to be used in a LIKE query.
Definition: Database.php:2047
float $mTrxWriteAdjDuration
Like mTrxWriteQueryCount but excludes lock-bound, easy to replicate, queries.
Definition: Database.php:197
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined...
Definition: Setup.php:36
fieldNamesWithAlias($fields)
Gets an array of aliased field names.
Definition: Database.php:1860
getSessionLagStatus()
Get the replica DB lag when the current transaction started or a general lag estimate if not transact...
Definition: Database.php:2987
onTransactionResolution(callable $callback, $fname=__METHOD__)
Run a callback as soon as the current transaction commits or rolls back.
Definition: Database.php:2490
static getSearchEngineClass(IDatabase $db)
getDefaultSchemaVars()
Get schema variables to use if none have been set via setSchemaVars().
Definition: Database.php:3263
fieldExists($table, $field, $fname=__METHOD__)
Determines whether a field exists in a table.
Definition: Database.php:1399
listViews($prefix=null, $fname=__METHOD__)
Lists all the VIEWs in the database.
Definition: Database.php:2904
getServer()
Get the server hostname or IP address.
Definition: Database.php:1692
array[] $mTrxPreCommitCallbacks
List of (callable, method name)
Definition: Database.php:90
$line
Definition: cdb.php:59
int[] $priorFlags
Prior mFlags values.
Definition: Database.php:219
indexUnique($table, $index)
Determines if a given index is unique.
Definition: Database.php:1432
makeWhereFrom2d($data, $baseKey, $subKey)
Build a partial where clause from a 2-d array such as used for LinkBatch.
Definition: Database.php:1628
getDomainID()
Definition: Database.php:621
array $mTrxAtomicLevels
Array of levels of atomicity within transactions.
Definition: Database.php:173
pendingWriteQueryDuration($type=self::ESTIMATE_TOTAL)
Get the time spend running write queries for this transaction.
Definition: Database.php:532
dropTable($tableName, $fName=__METHOD__)
Delete a table.
Definition: Database.php:3352
getServerUptime()
Determines how long the server has been up.
Definition: Database.php:2405
pendingWriteCallers()
Get the list of method names that did write queries for this transaction.
Definition: Database.php:554
const TINY_WRITE_SEC
Definition: Database.php:48
makeInsertOptions($options)
Helper for Database::insert().
Definition: Database.php:1448
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired 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 inclusive $limit
Definition: hooks.txt:1046
setTrxEndCallbackSuppression($suppress)
Whether to disable running of post-COMMIT/ROLLBACK callbacks.
Definition: Database.php:2536
string $delimiter
Definition: Database.php:117
registerTempTableOperation($sql)
Definition: Database.php:801
resource null $mConn
Database connection.
Definition: Database.php:83
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1046
getWikiID()
Alias for getDomainID()
Definition: Database.php:625
static convert($style=TS_UNIX, $ts)
Convert a timestamp string to a given format.
useIndexClause($index)
USE INDEX clause.
Definition: Database.php:2065
Advanced database interface for IDatabase handles that include maintenance methods.
selectFieldValues($table, $var, $cond= '', $fname=__METHOD__, $options=[], $join_conds=[])
Definition: Database.php:1088
namedLocksEnqueue()
Check to see if a named lock used by lock() use blocking queues.
Definition: Database.php:3318
string bool null $htmlErrors
Stashed value of html_errors INI setting.
Definition: Database.php:115
replace($table, $uniqueIndexes, $rows, $fname=__METHOD__)
REPLACE query wrapper.
Definition: Database.php:2083
selectField($table, $var, $cond= '', $fname=__METHOD__, $options=[])
A SELECT wrapper which returns a single field from a single result row.
Definition: Database.php:1061
float null $mTrxTimestamp
The UNIX time that the transaction started.
Definition: Database.php:143
IDatabase null $lazyMasterHandle
Lazy handle to the master DB this server replicates from.
Definition: Database.php:213
Result wrapper for grabbing data queried from an IDatabase object.
runOnTransactionPreCommitCallbacks()
Actually run and consume any "on transaction pre-commit" callbacks.
Definition: Database.php:2599
array null $preparedArgs
Definition: Database.php:113
encodeExpiry($expiry)
Encode an expiry time into the DBMS dependent format.
Definition: Database.php:3365
string $mUser
Definition: Database.php:61
connectionErrorLogger($errno, $errstr)
This method should not be used outside of Database classes.
Definition: Database.php:684
nativeInsertSelect($destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[])
Definition: Database.php:2311
array $mSessionVars
Definition: Database.php:111
__toString()
Definition: Database.php:3434
string $mTablePrefix
Definition: Database.php:99
setSchemaVars($vars)
Set variables to be used in sourceFile/sourceStream, in preference to the ones in $GLOBALS...
Definition: Database.php:3110
bool null $mDefaultBigSelects
Definition: Database.php:107
replaceVars($ins)
Database independent variable replacement.
Definition: Database.php:3209
tableNamesWithAlias($tables)
Gets an array of aliased table names.
Definition: Database.php:1826
array $mSessionTempTables
Map of (table name => 1) for TEMPORARY tables.
Definition: Database.php:210
makeUpdateOptions($options)
Make UPDATE options for the Database::update function.
Definition: Database.php:1530
getLag()
Get replica DB lag.
Definition: Database.php:3056
limitResult($sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.
Definition: Database.php:2372
getSchemaVars()
Get schema variables.
Definition: Database.php:3247
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account incomplete not yet checked for validity & $retval
Definition: hooks.txt:242
array $mLBInfo
Definition: Database.php:105
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2159
doQuery($sql)
The DBMS-dependent part of query()
resultObject($result)
Take the result from a query, and wrap it in a ResultWrapper if necessary.
Definition: Database.php:2935
getDBname()
Get the current DB name.
Definition: Database.php:1688
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2491
tablePrefix($prefix=null)
Get/set the table prefix.
Definition: Database.php:448
listTables($prefix=null, $fname=__METHOD__)
List all tables on the database.
Definition: Database.php:2900
restoreErrorHandler()
Definition: Database.php:655
prependDatabaseOrSchema($namespace, $relation, $format)
Definition: Database.php:1771
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:34
query($sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition: Database.php:829
lock($lockName, $method, $timeout=5)
Acquire a named lock.
Definition: Database.php:3271
lastQuery()
Return the last query that went through IDatabase::query()
Definition: Database.php:510
const DEADLOCK_TRIES
Number of times to re-try an operation in case of deadlock.
Definition: Database.php:38
$matches
isWriteQuery($sql)
Determine whether a query writes to the DB.
Definition: Database.php:767
installErrorHandler()
Definition: Database.php:646
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:300