MediaWiki  1.28.0
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  $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count", $fname );
1357 
1358  if ( $res ) {
1359  $row = $this->fetchRow( $res );
1360  $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
1361  }
1362 
1363  return $rows;
1364  }
1365 
1374  protected static function generalizeSQL( $sql ) {
1375  # This does the same as the regexp below would do, but in such a way
1376  # as to avoid crashing php on some large strings.
1377  # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
1378 
1379  $sql = str_replace( "\\\\", '', $sql );
1380  $sql = str_replace( "\\'", '', $sql );
1381  $sql = str_replace( "\\\"", '', $sql );
1382  $sql = preg_replace( "/'.*'/s", "'X'", $sql );
1383  $sql = preg_replace( '/".*"/s', "'X'", $sql );
1384 
1385  # All newlines, tabs, etc replaced by single space
1386  $sql = preg_replace( '/\s+/', ' ', $sql );
1387 
1388  # All numbers => N,
1389  # except the ones surrounded by characters, e.g. l10n
1390  $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
1391  $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
1392 
1393  return $sql;
1394  }
1395 
1396  public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1397  $info = $this->fieldInfo( $table, $field );
1398 
1399  return (bool)$info;
1400  }
1401 
1402  public function indexExists( $table, $index, $fname = __METHOD__ ) {
1403  if ( !$this->tableExists( $table ) ) {
1404  return null;
1405  }
1406 
1407  $info = $this->indexInfo( $table, $index, $fname );
1408  if ( is_null( $info ) ) {
1409  return null;
1410  } else {
1411  return $info !== false;
1412  }
1413  }
1414 
1415  public function tableExists( $table, $fname = __METHOD__ ) {
1416  $tableRaw = $this->tableName( $table, 'raw' );
1417  if ( isset( $this->mSessionTempTables[$tableRaw] ) ) {
1418  return true; // already known to exist
1419  }
1420 
1421  $table = $this->tableName( $table );
1422  $old = $this->ignoreErrors( true );
1423  $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
1424  $this->ignoreErrors( $old );
1425 
1426  return (bool)$res;
1427  }
1428 
1429  public function indexUnique( $table, $index ) {
1430  $indexInfo = $this->indexInfo( $table, $index );
1431 
1432  if ( !$indexInfo ) {
1433  return null;
1434  }
1435 
1436  return !$indexInfo[0]->Non_unique;
1437  }
1438 
1445  protected function makeInsertOptions( $options ) {
1446  return implode( ' ', $options );
1447  }
1448 
1449  public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
1450  # No rows to insert, easy just return now
1451  if ( !count( $a ) ) {
1452  return true;
1453  }
1454 
1455  $table = $this->tableName( $table );
1456 
1457  if ( !is_array( $options ) ) {
1458  $options = [ $options ];
1459  }
1460 
1461  $fh = null;
1462  if ( isset( $options['fileHandle'] ) ) {
1463  $fh = $options['fileHandle'];
1464  }
1465  $options = $this->makeInsertOptions( $options );
1466 
1467  if ( isset( $a[0] ) && is_array( $a[0] ) ) {
1468  $multi = true;
1469  $keys = array_keys( $a[0] );
1470  } else {
1471  $multi = false;
1472  $keys = array_keys( $a );
1473  }
1474 
1475  $sql = 'INSERT ' . $options .
1476  " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
1477 
1478  if ( $multi ) {
1479  $first = true;
1480  foreach ( $a as $row ) {
1481  if ( $first ) {
1482  $first = false;
1483  } else {
1484  $sql .= ',';
1485  }
1486  $sql .= '(' . $this->makeList( $row ) . ')';
1487  }
1488  } else {
1489  $sql .= '(' . $this->makeList( $a ) . ')';
1490  }
1491 
1492  if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
1493  return false;
1494  } elseif ( $fh !== null ) {
1495  return true;
1496  }
1497 
1498  return (bool)$this->query( $sql, $fname );
1499  }
1500 
1507  protected function makeUpdateOptionsArray( $options ) {
1508  if ( !is_array( $options ) ) {
1509  $options = [ $options ];
1510  }
1511 
1512  $opts = [];
1513 
1514  if ( in_array( 'IGNORE', $options ) ) {
1515  $opts[] = 'IGNORE';
1516  }
1517 
1518  return $opts;
1519  }
1520 
1527  protected function makeUpdateOptions( $options ) {
1528  $opts = $this->makeUpdateOptionsArray( $options );
1529 
1530  return implode( ' ', $opts );
1531  }
1532 
1533  public function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
1534  $table = $this->tableName( $table );
1535  $opts = $this->makeUpdateOptions( $options );
1536  $sql = "UPDATE $opts $table SET " . $this->makeList( $values, self::LIST_SET );
1537 
1538  if ( $conds !== [] && $conds !== '*' ) {
1539  $sql .= " WHERE " . $this->makeList( $conds, self::LIST_AND );
1540  }
1541 
1542  return $this->query( $sql, $fname );
1543  }
1544 
1545  public function makeList( $a, $mode = self::LIST_COMMA ) {
1546  if ( !is_array( $a ) ) {
1547  throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
1548  }
1549 
1550  $first = true;
1551  $list = '';
1552 
1553  foreach ( $a as $field => $value ) {
1554  if ( !$first ) {
1555  if ( $mode == self::LIST_AND ) {
1556  $list .= ' AND ';
1557  } elseif ( $mode == self::LIST_OR ) {
1558  $list .= ' OR ';
1559  } else {
1560  $list .= ',';
1561  }
1562  } else {
1563  $first = false;
1564  }
1565 
1566  if ( ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_numeric( $field ) ) {
1567  $list .= "($value)";
1568  } elseif ( $mode == self::LIST_SET && is_numeric( $field ) ) {
1569  $list .= "$value";
1570  } elseif (
1571  ( $mode == self::LIST_AND || $mode == self::LIST_OR ) && is_array( $value )
1572  ) {
1573  // Remove null from array to be handled separately if found
1574  $includeNull = false;
1575  foreach ( array_keys( $value, null, true ) as $nullKey ) {
1576  $includeNull = true;
1577  unset( $value[$nullKey] );
1578  }
1579  if ( count( $value ) == 0 && !$includeNull ) {
1580  throw new InvalidArgumentException(
1581  __METHOD__ . ": empty input for field $field" );
1582  } elseif ( count( $value ) == 0 ) {
1583  // only check if $field is null
1584  $list .= "$field IS NULL";
1585  } else {
1586  // IN clause contains at least one valid element
1587  if ( $includeNull ) {
1588  // Group subconditions to ensure correct precedence
1589  $list .= '(';
1590  }
1591  if ( count( $value ) == 1 ) {
1592  // Special-case single values, as IN isn't terribly efficient
1593  // Don't necessarily assume the single key is 0; we don't
1594  // enforce linear numeric ordering on other arrays here.
1595  $value = array_values( $value )[0];
1596  $list .= $field . " = " . $this->addQuotes( $value );
1597  } else {
1598  $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
1599  }
1600  // if null present in array, append IS NULL
1601  if ( $includeNull ) {
1602  $list .= " OR $field IS NULL)";
1603  }
1604  }
1605  } elseif ( $value === null ) {
1606  if ( $mode == self::LIST_AND || $mode == self::LIST_OR ) {
1607  $list .= "$field IS ";
1608  } elseif ( $mode == self::LIST_SET ) {
1609  $list .= "$field = ";
1610  }
1611  $list .= 'NULL';
1612  } else {
1613  if (
1614  $mode == self::LIST_AND || $mode == self::LIST_OR || $mode == self::LIST_SET
1615  ) {
1616  $list .= "$field = ";
1617  }
1618  $list .= $mode == self::LIST_NAMES ? $value : $this->addQuotes( $value );
1619  }
1620  }
1621 
1622  return $list;
1623  }
1624 
1625  public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
1626  $conds = [];
1627 
1628  foreach ( $data as $base => $sub ) {
1629  if ( count( $sub ) ) {
1630  $conds[] = $this->makeList(
1631  [ $baseKey => $base, $subKey => array_keys( $sub ) ],
1632  self::LIST_AND );
1633  }
1634  }
1635 
1636  if ( $conds ) {
1637  return $this->makeList( $conds, self::LIST_OR );
1638  } else {
1639  // Nothing to search for...
1640  return false;
1641  }
1642  }
1643 
1644  public function aggregateValue( $valuedata, $valuename = 'value' ) {
1645  return $valuename;
1646  }
1647 
1648  public function bitNot( $field ) {
1649  return "(~$field)";
1650  }
1651 
1652  public function bitAnd( $fieldLeft, $fieldRight ) {
1653  return "($fieldLeft & $fieldRight)";
1654  }
1655 
1656  public function bitOr( $fieldLeft, $fieldRight ) {
1657  return "($fieldLeft | $fieldRight)";
1658  }
1659 
1660  public function buildConcat( $stringList ) {
1661  return 'CONCAT(' . implode( ',', $stringList ) . ')';
1662  }
1663 
1664  public function buildGroupConcatField(
1665  $delim, $table, $field, $conds = '', $join_conds = []
1666  ) {
1667  $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
1668 
1669  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1670  }
1671 
1672  public function buildStringCast( $field ) {
1673  return $field;
1674  }
1675 
1676  public function selectDB( $db ) {
1677  # Stub. Shouldn't cause serious problems if it's not overridden, but
1678  # if your database engine supports a concept similar to MySQL's
1679  # databases you may as well.
1680  $this->mDBname = $db;
1681 
1682  return true;
1683  }
1684 
1685  public function getDBname() {
1686  return $this->mDBname;
1687  }
1688 
1689  public function getServer() {
1690  return $this->mServer;
1691  }
1692 
1693  public function tableName( $name, $format = 'quoted' ) {
1694  # Skip the entire process when we have a string quoted on both ends.
1695  # Note that we check the end so that we will still quote any use of
1696  # use of `database`.table. But won't break things if someone wants
1697  # to query a database table with a dot in the name.
1698  if ( $this->isQuotedIdentifier( $name ) ) {
1699  return $name;
1700  }
1701 
1702  # Lets test for any bits of text that should never show up in a table
1703  # name. Basically anything like JOIN or ON which are actually part of
1704  # SQL queries, but may end up inside of the table value to combine
1705  # sql. Such as how the API is doing.
1706  # Note that we use a whitespace test rather than a \b test to avoid
1707  # any remote case where a word like on may be inside of a table name
1708  # surrounded by symbols which may be considered word breaks.
1709  if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
1710  return $name;
1711  }
1712 
1713  # Split database and table into proper variables.
1714  # We reverse the explode so that database.table and table both output
1715  # the correct table.
1716  $dbDetails = explode( '.', $name, 3 );
1717  if ( count( $dbDetails ) == 3 ) {
1718  list( $database, $schema, $table ) = $dbDetails;
1719  # We don't want any prefix added in this case
1720  $prefix = '';
1721  } elseif ( count( $dbDetails ) == 2 ) {
1722  list( $database, $table ) = $dbDetails;
1723  # We don't want any prefix added in this case
1724  $prefix = '';
1725  # In dbs that support it, $database may actually be the schema
1726  # but that doesn't affect any of the functionality here
1727  $schema = '';
1728  } else {
1729  list( $table ) = $dbDetails;
1730  if ( isset( $this->tableAliases[$table] ) ) {
1731  $database = $this->tableAliases[$table]['dbname'];
1732  $schema = is_string( $this->tableAliases[$table]['schema'] )
1733  ? $this->tableAliases[$table]['schema']
1734  : $this->mSchema;
1735  $prefix = is_string( $this->tableAliases[$table]['prefix'] )
1736  ? $this->tableAliases[$table]['prefix']
1738  } else {
1739  $database = '';
1740  $schema = $this->mSchema; # Default schema
1741  $prefix = $this->mTablePrefix; # Default prefix
1742  }
1743  }
1744 
1745  # Quote $table and apply the prefix if not quoted.
1746  # $tableName might be empty if this is called from Database::replaceVars()
1747  $tableName = "{$prefix}{$table}";
1748  if ( $format === 'quoted'
1749  && !$this->isQuotedIdentifier( $tableName )
1750  && $tableName !== ''
1751  ) {
1752  $tableName = $this->addIdentifierQuotes( $tableName );
1753  }
1754 
1755  # Quote $schema and $database and merge them with the table name if needed
1756  $tableName = $this->prependDatabaseOrSchema( $schema, $tableName, $format );
1757  $tableName = $this->prependDatabaseOrSchema( $database, $tableName, $format );
1758 
1759  return $tableName;
1760  }
1761 
1768  private function prependDatabaseOrSchema( $namespace, $relation, $format ) {
1769  if ( strlen( $namespace ) ) {
1770  if ( $format === 'quoted' && !$this->isQuotedIdentifier( $namespace ) ) {
1771  $namespace = $this->addIdentifierQuotes( $namespace );
1772  }
1773  $relation = $namespace . '.' . $relation;
1774  }
1775 
1776  return $relation;
1777  }
1778 
1779  public function tableNames() {
1780  $inArray = func_get_args();
1781  $retVal = [];
1782 
1783  foreach ( $inArray as $name ) {
1784  $retVal[$name] = $this->tableName( $name );
1785  }
1786 
1787  return $retVal;
1788  }
1789 
1790  public function tableNamesN() {
1791  $inArray = func_get_args();
1792  $retVal = [];
1793 
1794  foreach ( $inArray as $name ) {
1795  $retVal[] = $this->tableName( $name );
1796  }
1797 
1798  return $retVal;
1799  }
1800 
1809  protected function tableNameWithAlias( $name, $alias = false ) {
1810  if ( !$alias || $alias == $name ) {
1811  return $this->tableName( $name );
1812  } else {
1813  return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
1814  }
1815  }
1816 
1823  protected function tableNamesWithAlias( $tables ) {
1824  $retval = [];
1825  foreach ( $tables as $alias => $table ) {
1826  if ( is_numeric( $alias ) ) {
1827  $alias = $table;
1828  }
1829  $retval[] = $this->tableNameWithAlias( $table, $alias );
1830  }
1831 
1832  return $retval;
1833  }
1834 
1843  protected function fieldNameWithAlias( $name, $alias = false ) {
1844  if ( !$alias || (string)$alias === (string)$name ) {
1845  return $name;
1846  } else {
1847  return $name . ' AS ' . $this->addIdentifierQuotes( $alias ); // PostgreSQL needs AS
1848  }
1849  }
1850 
1857  protected function fieldNamesWithAlias( $fields ) {
1858  $retval = [];
1859  foreach ( $fields as $alias => $field ) {
1860  if ( is_numeric( $alias ) ) {
1861  $alias = $field;
1862  }
1863  $retval[] = $this->fieldNameWithAlias( $field, $alias );
1864  }
1865 
1866  return $retval;
1867  }
1868 
1880  $tables, $use_index = [], $ignore_index = [], $join_conds = []
1881  ) {
1882  $ret = [];
1883  $retJOIN = [];
1884  $use_index = (array)$use_index;
1885  $ignore_index = (array)$ignore_index;
1886  $join_conds = (array)$join_conds;
1887 
1888  foreach ( $tables as $alias => $table ) {
1889  if ( !is_string( $alias ) ) {
1890  // No alias? Set it equal to the table name
1891  $alias = $table;
1892  }
1893  // Is there a JOIN clause for this table?
1894  if ( isset( $join_conds[$alias] ) ) {
1895  list( $joinType, $conds ) = $join_conds[$alias];
1896  $tableClause = $joinType;
1897  $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
1898  if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
1899  $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
1900  if ( $use != '' ) {
1901  $tableClause .= ' ' . $use;
1902  }
1903  }
1904  if ( isset( $ignore_index[$alias] ) ) { // has IGNORE INDEX?
1905  $ignore = $this->ignoreIndexClause(
1906  implode( ',', (array)$ignore_index[$alias] ) );
1907  if ( $ignore != '' ) {
1908  $tableClause .= ' ' . $ignore;
1909  }
1910  }
1911  $on = $this->makeList( (array)$conds, self::LIST_AND );
1912  if ( $on != '' ) {
1913  $tableClause .= ' ON (' . $on . ')';
1914  }
1915 
1916  $retJOIN[] = $tableClause;
1917  } elseif ( isset( $use_index[$alias] ) ) {
1918  // Is there an INDEX clause for this table?
1919  $tableClause = $this->tableNameWithAlias( $table, $alias );
1920  $tableClause .= ' ' . $this->useIndexClause(
1921  implode( ',', (array)$use_index[$alias] )
1922  );
1923 
1924  $ret[] = $tableClause;
1925  } elseif ( isset( $ignore_index[$alias] ) ) {
1926  // Is there an INDEX clause for this table?
1927  $tableClause = $this->tableNameWithAlias( $table, $alias );
1928  $tableClause .= ' ' . $this->ignoreIndexClause(
1929  implode( ',', (array)$ignore_index[$alias] )
1930  );
1931 
1932  $ret[] = $tableClause;
1933  } else {
1934  $tableClause = $this->tableNameWithAlias( $table, $alias );
1935 
1936  $ret[] = $tableClause;
1937  }
1938  }
1939 
1940  // We can't separate explicit JOIN clauses with ',', use ' ' for those
1941  $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
1942  $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
1943 
1944  // Compile our final table clause
1945  return implode( ' ', [ $implicitJoins, $explicitJoins ] );
1946  }
1947 
1954  protected function indexName( $index ) {
1955  return $index;
1956  }
1957 
1958  public function addQuotes( $s ) {
1959  if ( $s instanceof Blob ) {
1960  $s = $s->fetch();
1961  }
1962  if ( $s === null ) {
1963  return 'NULL';
1964  } elseif ( is_bool( $s ) ) {
1965  return (int)$s;
1966  } else {
1967  # This will also quote numeric values. This should be harmless,
1968  # and protects against weird problems that occur when they really
1969  # _are_ strings such as article titles and string->number->string
1970  # conversion is not 1:1.
1971  return "'" . $this->strencode( $s ) . "'";
1972  }
1973  }
1974 
1984  public function addIdentifierQuotes( $s ) {
1985  return '"' . str_replace( '"', '""', $s ) . '"';
1986  }
1987 
1997  public function isQuotedIdentifier( $name ) {
1998  return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
1999  }
2000 
2005  protected function escapeLikeInternal( $s ) {
2006  return addcslashes( $s, '\%_' );
2007  }
2008 
2009  public function buildLike() {
2010  $params = func_get_args();
2011 
2012  if ( count( $params ) > 0 && is_array( $params[0] ) ) {
2013  $params = $params[0];
2014  }
2015 
2016  $s = '';
2017 
2018  foreach ( $params as $value ) {
2019  if ( $value instanceof LikeMatch ) {
2020  $s .= $value->toString();
2021  } else {
2022  $s .= $this->escapeLikeInternal( $value );
2023  }
2024  }
2025 
2026  return " LIKE {$this->addQuotes( $s )} ";
2027  }
2028 
2029  public function anyChar() {
2030  return new LikeMatch( '_' );
2031  }
2032 
2033  public function anyString() {
2034  return new LikeMatch( '%' );
2035  }
2036 
2037  public function nextSequenceValue( $seqName ) {
2038  return null;
2039  }
2040 
2051  public function useIndexClause( $index ) {
2052  return '';
2053  }
2054 
2065  public function ignoreIndexClause( $index ) {
2066  return '';
2067  }
2068 
2069  public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
2070  $quotedTable = $this->tableName( $table );
2071 
2072  if ( count( $rows ) == 0 ) {
2073  return;
2074  }
2075 
2076  # Single row case
2077  if ( !is_array( reset( $rows ) ) ) {
2078  $rows = [ $rows ];
2079  }
2080 
2081  // @FXIME: this is not atomic, but a trx would break affectedRows()
2082  foreach ( $rows as $row ) {
2083  # Delete rows which collide
2084  if ( $uniqueIndexes ) {
2085  $sql = "DELETE FROM $quotedTable WHERE ";
2086  $first = true;
2087  foreach ( $uniqueIndexes as $index ) {
2088  if ( $first ) {
2089  $first = false;
2090  $sql .= '( ';
2091  } else {
2092  $sql .= ' ) OR ( ';
2093  }
2094  if ( is_array( $index ) ) {
2095  $first2 = true;
2096  foreach ( $index as $col ) {
2097  if ( $first2 ) {
2098  $first2 = false;
2099  } else {
2100  $sql .= ' AND ';
2101  }
2102  $sql .= $col . '=' . $this->addQuotes( $row[$col] );
2103  }
2104  } else {
2105  $sql .= $index . '=' . $this->addQuotes( $row[$index] );
2106  }
2107  }
2108  $sql .= ' )';
2109  $this->query( $sql, $fname );
2110  }
2111 
2112  # Now insert the row
2113  $this->insert( $table, $row, $fname );
2114  }
2115  }
2116 
2127  protected function nativeReplace( $table, $rows, $fname ) {
2128  $table = $this->tableName( $table );
2129 
2130  # Single row case
2131  if ( !is_array( reset( $rows ) ) ) {
2132  $rows = [ $rows ];
2133  }
2134 
2135  $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
2136  $first = true;
2137 
2138  foreach ( $rows as $row ) {
2139  if ( $first ) {
2140  $first = false;
2141  } else {
2142  $sql .= ',';
2143  }
2144 
2145  $sql .= '(' . $this->makeList( $row ) . ')';
2146  }
2147 
2148  return $this->query( $sql, $fname );
2149  }
2150 
2151  public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
2152  $fname = __METHOD__
2153  ) {
2154  if ( !count( $rows ) ) {
2155  return true; // nothing to do
2156  }
2157 
2158  if ( !is_array( reset( $rows ) ) ) {
2159  $rows = [ $rows ];
2160  }
2161 
2162  if ( count( $uniqueIndexes ) ) {
2163  $clauses = []; // list WHERE clauses that each identify a single row
2164  foreach ( $rows as $row ) {
2165  foreach ( $uniqueIndexes as $index ) {
2166  $index = is_array( $index ) ? $index : [ $index ]; // columns
2167  $rowKey = []; // unique key to this row
2168  foreach ( $index as $column ) {
2169  $rowKey[$column] = $row[$column];
2170  }
2171  $clauses[] = $this->makeList( $rowKey, self::LIST_AND );
2172  }
2173  }
2174  $where = [ $this->makeList( $clauses, self::LIST_OR ) ];
2175  } else {
2176  $where = false;
2177  }
2178 
2179  $useTrx = !$this->mTrxLevel;
2180  if ( $useTrx ) {
2181  $this->begin( $fname, self::TRANSACTION_INTERNAL );
2182  }
2183  try {
2184  # Update any existing conflicting row(s)
2185  if ( $where !== false ) {
2186  $ok = $this->update( $table, $set, $where, $fname );
2187  } else {
2188  $ok = true;
2189  }
2190  # Now insert any non-conflicting row(s)
2191  $ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok;
2192  } catch ( Exception $e ) {
2193  if ( $useTrx ) {
2194  $this->rollback( $fname, self::FLUSHING_INTERNAL );
2195  }
2196  throw $e;
2197  }
2198  if ( $useTrx ) {
2199  $this->commit( $fname, self::FLUSHING_INTERNAL );
2200  }
2201 
2202  return $ok;
2203  }
2204 
2205  public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
2206  $fname = __METHOD__
2207  ) {
2208  if ( !$conds ) {
2209  throw new DBUnexpectedError( $this, __METHOD__ . ' called with empty $conds' );
2210  }
2211 
2212  $delTable = $this->tableName( $delTable );
2213  $joinTable = $this->tableName( $joinTable );
2214  $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
2215  if ( $conds != '*' ) {
2216  $sql .= 'WHERE ' . $this->makeList( $conds, self::LIST_AND );
2217  }
2218  $sql .= ')';
2219 
2220  $this->query( $sql, $fname );
2221  }
2222 
2223  public function textFieldSize( $table, $field ) {
2224  $table = $this->tableName( $table );
2225  $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
2226  $res = $this->query( $sql, __METHOD__ );
2227  $row = $this->fetchObject( $res );
2228 
2229  $m = [];
2230 
2231  if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
2232  $size = $m[1];
2233  } else {
2234  $size = -1;
2235  }
2236 
2237  return $size;
2238  }
2239 
2240  public function delete( $table, $conds, $fname = __METHOD__ ) {
2241  if ( !$conds ) {
2242  throw new DBUnexpectedError( $this, __METHOD__ . ' called with no conditions' );
2243  }
2244 
2245  $table = $this->tableName( $table );
2246  $sql = "DELETE FROM $table";
2247 
2248  if ( $conds != '*' ) {
2249  if ( is_array( $conds ) ) {
2250  $conds = $this->makeList( $conds, self::LIST_AND );
2251  }
2252  $sql .= ' WHERE ' . $conds;
2253  }
2254 
2255  return $this->query( $sql, $fname );
2256  }
2257 
2258  public function insertSelect(
2259  $destTable, $srcTable, $varMap, $conds,
2260  $fname = __METHOD__, $insertOptions = [], $selectOptions = []
2261  ) {
2262  if ( $this->cliMode ) {
2263  // For massive migrations with downtime, we don't want to select everything
2264  // into memory and OOM, so do all this native on the server side if possible.
2265  return $this->nativeInsertSelect(
2266  $destTable,
2267  $srcTable,
2268  $varMap,
2269  $conds,
2270  $fname,
2271  $insertOptions,
2272  $selectOptions
2273  );
2274  }
2275 
2276  // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
2277  // on only the master (without needing row-based-replication). It also makes it easy to
2278  // know how big the INSERT is going to be.
2279  $fields = [];
2280  foreach ( $varMap as $dstColumn => $sourceColumnOrSql ) {
2281  $fields[] = $this->fieldNameWithAlias( $sourceColumnOrSql, $dstColumn );
2282  }
2283  $selectOptions[] = 'FOR UPDATE';
2284  $res = $this->select( $srcTable, implode( ',', $fields ), $conds, $fname, $selectOptions );
2285  if ( !$res ) {
2286  return false;
2287  }
2288 
2289  $rows = [];
2290  foreach ( $res as $row ) {
2291  $rows[] = (array)$row;
2292  }
2293 
2294  return $this->insert( $destTable, $rows, $fname, $insertOptions );
2295  }
2296 
2297  protected function nativeInsertSelect( $destTable, $srcTable, $varMap, $conds,
2298  $fname = __METHOD__,
2299  $insertOptions = [], $selectOptions = []
2300  ) {
2301  $destTable = $this->tableName( $destTable );
2302 
2303  if ( !is_array( $insertOptions ) ) {
2304  $insertOptions = [ $insertOptions ];
2305  }
2306 
2307  $insertOptions = $this->makeInsertOptions( $insertOptions );
2308 
2309  if ( !is_array( $selectOptions ) ) {
2310  $selectOptions = [ $selectOptions ];
2311  }
2312 
2313  list( $startOpts, $useIndex, $tailOpts, $ignoreIndex ) = $this->makeSelectOptions(
2314  $selectOptions );
2315 
2316  if ( is_array( $srcTable ) ) {
2317  $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
2318  } else {
2319  $srcTable = $this->tableName( $srcTable );
2320  }
2321 
2322  $sql = "INSERT $insertOptions" .
2323  " INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
2324  " SELECT $startOpts " . implode( ',', $varMap ) .
2325  " FROM $srcTable $useIndex $ignoreIndex ";
2326 
2327  if ( $conds != '*' ) {
2328  if ( is_array( $conds ) ) {
2329  $conds = $this->makeList( $conds, self::LIST_AND );
2330  }
2331  $sql .= " WHERE $conds";
2332  }
2333 
2334  $sql .= " $tailOpts";
2335 
2336  return $this->query( $sql, $fname );
2337  }
2338 
2358  public function limitResult( $sql, $limit, $offset = false ) {
2359  if ( !is_numeric( $limit ) ) {
2360  throw new DBUnexpectedError( $this,
2361  "Invalid non-numeric limit passed to limitResult()\n" );
2362  }
2363 
2364  return "$sql LIMIT "
2365  . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
2366  . "{$limit} ";
2367  }
2368 
2369  public function unionSupportsOrderAndLimit() {
2370  return true; // True for almost every DB supported
2371  }
2372 
2373  public function unionQueries( $sqls, $all ) {
2374  $glue = $all ? ') UNION ALL (' : ') UNION (';
2375 
2376  return '(' . implode( $glue, $sqls ) . ')';
2377  }
2378 
2379  public function conditional( $cond, $trueVal, $falseVal ) {
2380  if ( is_array( $cond ) ) {
2381  $cond = $this->makeList( $cond, self::LIST_AND );
2382  }
2383 
2384  return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
2385  }
2386 
2387  public function strreplace( $orig, $old, $new ) {
2388  return "REPLACE({$orig}, {$old}, {$new})";
2389  }
2390 
2391  public function getServerUptime() {
2392  return 0;
2393  }
2394 
2395  public function wasDeadlock() {
2396  return false;
2397  }
2398 
2399  public function wasLockTimeout() {
2400  return false;
2401  }
2402 
2403  public function wasErrorReissuable() {
2404  return false;
2405  }
2406 
2407  public function wasReadOnlyError() {
2408  return false;
2409  }
2410 
2417  public function wasConnectionError( $errno ) {
2418  return false;
2419  }
2420 
2421  public function deadlockLoop() {
2422  $args = func_get_args();
2423  $function = array_shift( $args );
2424  $tries = self::DEADLOCK_TRIES;
2425 
2426  $this->begin( __METHOD__ );
2427 
2428  $retVal = null;
2430  $e = null;
2431  do {
2432  try {
2433  $retVal = call_user_func_array( $function, $args );
2434  break;
2435  } catch ( DBQueryError $e ) {
2436  if ( $this->wasDeadlock() ) {
2437  // Retry after a randomized delay
2438  usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
2439  } else {
2440  // Throw the error back up
2441  throw $e;
2442  }
2443  }
2444  } while ( --$tries > 0 );
2445 
2446  if ( $tries <= 0 ) {
2447  // Too many deadlocks; give up
2448  $this->rollback( __METHOD__ );
2449  throw $e;
2450  } else {
2451  $this->commit( __METHOD__ );
2452 
2453  return $retVal;
2454  }
2455  }
2456 
2457  public function masterPosWait( DBMasterPos $pos, $timeout ) {
2458  # Real waits are implemented in the subclass.
2459  return 0;
2460  }
2461 
2462  public function getReplicaPos() {
2463  # Stub
2464  return false;
2465  }
2466 
2467  public function getMasterPos() {
2468  # Stub
2469  return false;
2470  }
2471 
2472  public function serverIsReadOnly() {
2473  return false;
2474  }
2475 
2476  final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
2477  if ( !$this->mTrxLevel ) {
2478  throw new DBUnexpectedError( $this, "No transaction is active." );
2479  }
2480  $this->mTrxEndCallbacks[] = [ $callback, $fname ];
2481  }
2482 
2483  final public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
2484  $this->mTrxIdleCallbacks[] = [ $callback, $fname ];
2485  if ( !$this->mTrxLevel ) {
2486  $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
2487  }
2488  }
2489 
2490  final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
2491  if ( $this->mTrxLevel ) {
2492  $this->mTrxPreCommitCallbacks[] = [ $callback, $fname ];
2493  } else {
2494  // If no transaction is active, then make one for this callback
2495  $this->startAtomic( __METHOD__ );
2496  try {
2497  call_user_func( $callback );
2498  $this->endAtomic( __METHOD__ );
2499  } catch ( Exception $e ) {
2500  $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
2501  throw $e;
2502  }
2503  }
2504  }
2505 
2506  final public function setTransactionListener( $name, callable $callback = null ) {
2507  if ( $callback ) {
2508  $this->mTrxRecurringCallbacks[$name] = $callback;
2509  } else {
2510  unset( $this->mTrxRecurringCallbacks[$name] );
2511  }
2512  }
2513 
2522  final public function setTrxEndCallbackSuppression( $suppress ) {
2523  $this->mTrxEndCallbacksSuppressed = $suppress;
2524  }
2525 
2535  public function runOnTransactionIdleCallbacks( $trigger ) {
2536  if ( $this->mTrxEndCallbacksSuppressed ) {
2537  return;
2538  }
2539 
2540  $autoTrx = $this->getFlag( self::DBO_TRX ); // automatic begin() enabled?
2542  $e = null; // first exception
2543  do { // callbacks may add callbacks :)
2544  $callbacks = array_merge(
2545  $this->mTrxIdleCallbacks,
2546  $this->mTrxEndCallbacks // include "transaction resolution" callbacks
2547  );
2548  $this->mTrxIdleCallbacks = []; // consumed (and recursion guard)
2549  $this->mTrxEndCallbacks = []; // consumed (recursion guard)
2550  foreach ( $callbacks as $callback ) {
2551  try {
2552  list( $phpCallback ) = $callback;
2553  $this->clearFlag( self::DBO_TRX ); // make each query its own transaction
2554  call_user_func_array( $phpCallback, [ $trigger ] );
2555  if ( $autoTrx ) {
2556  $this->setFlag( self::DBO_TRX ); // restore automatic begin()
2557  } else {
2558  $this->clearFlag( self::DBO_TRX ); // restore auto-commit
2559  }
2560  } catch ( Exception $ex ) {
2561  call_user_func( $this->errorLogger, $ex );
2562  $e = $e ?: $ex;
2563  // Some callbacks may use startAtomic/endAtomic, so make sure
2564  // their transactions are ended so other callbacks don't fail
2565  if ( $this->trxLevel() ) {
2566  $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
2567  }
2568  }
2569  }
2570  } while ( count( $this->mTrxIdleCallbacks ) );
2571 
2572  if ( $e instanceof Exception ) {
2573  throw $e; // re-throw any first exception
2574  }
2575  }
2576 
2586  $e = null; // first exception
2587  do { // callbacks may add callbacks :)
2588  $callbacks = $this->mTrxPreCommitCallbacks;
2589  $this->mTrxPreCommitCallbacks = []; // consumed (and recursion guard)
2590  foreach ( $callbacks as $callback ) {
2591  try {
2592  list( $phpCallback ) = $callback;
2593  call_user_func( $phpCallback );
2594  } catch ( Exception $ex ) {
2595  call_user_func( $this->errorLogger, $ex );
2596  $e = $e ?: $ex;
2597  }
2598  }
2599  } while ( count( $this->mTrxPreCommitCallbacks ) );
2600 
2601  if ( $e instanceof Exception ) {
2602  throw $e; // re-throw any first exception
2603  }
2604  }
2605 
2615  public function runTransactionListenerCallbacks( $trigger ) {
2616  if ( $this->mTrxEndCallbacksSuppressed ) {
2617  return;
2618  }
2619 
2621  $e = null; // first exception
2622 
2623  foreach ( $this->mTrxRecurringCallbacks as $phpCallback ) {
2624  try {
2625  $phpCallback( $trigger, $this );
2626  } catch ( Exception $ex ) {
2627  call_user_func( $this->errorLogger, $ex );
2628  $e = $e ?: $ex;
2629  }
2630  }
2631 
2632  if ( $e instanceof Exception ) {
2633  throw $e; // re-throw any first exception
2634  }
2635  }
2636 
2637  final public function startAtomic( $fname = __METHOD__ ) {
2638  if ( !$this->mTrxLevel ) {
2639  $this->begin( $fname, self::TRANSACTION_INTERNAL );
2640  // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
2641  // in all changes being in one transaction to keep requests transactional.
2642  if ( !$this->getFlag( self::DBO_TRX ) ) {
2643  $this->mTrxAutomaticAtomic = true;
2644  }
2645  }
2646 
2647  $this->mTrxAtomicLevels[] = $fname;
2648  }
2649 
2650  final public function endAtomic( $fname = __METHOD__ ) {
2651  if ( !$this->mTrxLevel ) {
2652  throw new DBUnexpectedError( $this, "No atomic transaction is open (got $fname)." );
2653  }
2654  if ( !$this->mTrxAtomicLevels ||
2655  array_pop( $this->mTrxAtomicLevels ) !== $fname
2656  ) {
2657  throw new DBUnexpectedError( $this, "Invalid atomic section ended (got $fname)." );
2658  }
2659 
2660  if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) {
2661  $this->commit( $fname, self::FLUSHING_INTERNAL );
2662  }
2663  }
2664 
2665  final public function doAtomicSection( $fname, callable $callback ) {
2666  $this->startAtomic( $fname );
2667  try {
2668  $res = call_user_func_array( $callback, [ $this, $fname ] );
2669  } catch ( Exception $e ) {
2670  $this->rollback( $fname, self::FLUSHING_INTERNAL );
2671  throw $e;
2672  }
2673  $this->endAtomic( $fname );
2674 
2675  return $res;
2676  }
2677 
2678  final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
2679  // Protect against mismatched atomic section, transaction nesting, and snapshot loss
2680  if ( $this->mTrxLevel ) {
2681  if ( $this->mTrxAtomicLevels ) {
2682  $levels = implode( ', ', $this->mTrxAtomicLevels );
2683  $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
2684  throw new DBUnexpectedError( $this, $msg );
2685  } elseif ( !$this->mTrxAutomatic ) {
2686  $msg = "$fname: Explicit transaction already active (from {$this->mTrxFname}).";
2687  throw new DBUnexpectedError( $this, $msg );
2688  } else {
2689  // @TODO: make this an exception at some point
2690  $msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
2691  $this->queryLogger->error( $msg );
2692  return; // join the main transaction set
2693  }
2694  } elseif ( $this->getFlag( self::DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
2695  // @TODO: make this an exception at some point
2696  $msg = "$fname: Implicit transaction expected (DBO_TRX set).";
2697  $this->queryLogger->error( $msg );
2698  return; // let any writes be in the main transaction
2699  }
2700 
2701  // Avoid fatals if close() was called
2702  $this->assertOpen();
2703 
2704  $this->doBegin( $fname );
2705  $this->mTrxTimestamp = microtime( true );
2706  $this->mTrxFname = $fname;
2707  $this->mTrxDoneWrites = false;
2708  $this->mTrxAutomaticAtomic = false;
2709  $this->mTrxAtomicLevels = [];
2710  $this->mTrxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
2711  $this->mTrxWriteDuration = 0.0;
2712  $this->mTrxWriteQueryCount = 0;
2713  $this->mTrxWriteAdjDuration = 0.0;
2714  $this->mTrxWriteAdjQueryCount = 0;
2715  $this->mTrxWriteCallers = [];
2716  // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
2717  // Get an estimate of the replica DB lag before then, treating estimate staleness
2718  // as lag itself just to be safe
2719  $status = $this->getApproximateLagStatus();
2720  $this->mTrxReplicaLag = $status['lag'] + ( microtime( true ) - $status['since'] );
2721  // T147697: make explicitTrxActive() return true until begin() finishes. This way, no
2722  // caller will think its OK to muck around with the transaction just because startAtomic()
2723  // has not yet completed (e.g. setting mTrxAtomicLevels).
2724  $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
2725  }
2726 
2733  protected function doBegin( $fname ) {
2734  $this->query( 'BEGIN', $fname );
2735  $this->mTrxLevel = 1;
2736  }
2737 
2738  final public function commit( $fname = __METHOD__, $flush = '' ) {
2739  if ( $this->mTrxLevel && $this->mTrxAtomicLevels ) {
2740  // There are still atomic sections open. This cannot be ignored
2741  $levels = implode( ', ', $this->mTrxAtomicLevels );
2742  throw new DBUnexpectedError(
2743  $this,
2744  "$fname: Got COMMIT while atomic sections $levels are still open."
2745  );
2746  }
2747 
2748  if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
2749  if ( !$this->mTrxLevel ) {
2750  return; // nothing to do
2751  } elseif ( !$this->mTrxAutomatic ) {
2752  throw new DBUnexpectedError(
2753  $this,
2754  "$fname: Flushing an explicit transaction, getting out of sync."
2755  );
2756  }
2757  } else {
2758  if ( !$this->mTrxLevel ) {
2759  $this->queryLogger->error(
2760  "$fname: No transaction to commit, something got out of sync." );
2761  return; // nothing to do
2762  } elseif ( $this->mTrxAutomatic ) {
2763  // @TODO: make this an exception at some point
2764  $msg = "$fname: Explicit commit of implicit transaction.";
2765  $this->queryLogger->error( $msg );
2766  return; // wait for the main transaction set commit round
2767  }
2768  }
2769 
2770  // Avoid fatals if close() was called
2771  $this->assertOpen();
2772 
2774  $writeTime = $this->pendingWriteQueryDuration( self::ESTIMATE_DB_APPLY );
2775  $this->doCommit( $fname );
2776  if ( $this->mTrxDoneWrites ) {
2777  $this->mLastWriteTime = microtime( true );
2778  $this->trxProfiler->transactionWritingOut(
2779  $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
2780  }
2781 
2782  $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
2783  $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT );
2784  }
2785 
2792  protected function doCommit( $fname ) {
2793  if ( $this->mTrxLevel ) {
2794  $this->query( 'COMMIT', $fname );
2795  $this->mTrxLevel = 0;
2796  }
2797  }
2798 
2799  final public function rollback( $fname = __METHOD__, $flush = '' ) {
2800  if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
2801  if ( !$this->mTrxLevel ) {
2802  return; // nothing to do
2803  }
2804  } else {
2805  if ( !$this->mTrxLevel ) {
2806  $this->queryLogger->error(
2807  "$fname: No transaction to rollback, something got out of sync." );
2808  return; // nothing to do
2809  } elseif ( $this->getFlag( self::DBO_TRX ) ) {
2810  throw new DBUnexpectedError(
2811  $this,
2812  "$fname: Expected mass rollback of all peer databases (DBO_TRX set)."
2813  );
2814  }
2815  }
2816 
2817  // Avoid fatals if close() was called
2818  $this->assertOpen();
2819 
2820  $this->doRollback( $fname );
2821  $this->mTrxAtomicLevels = [];
2822  if ( $this->mTrxDoneWrites ) {
2823  $this->trxProfiler->transactionWritingOut(
2824  $this->mServer, $this->mDBname, $this->mTrxShortId );
2825  }
2826 
2827  $this->mTrxIdleCallbacks = []; // clear
2828  $this->mTrxPreCommitCallbacks = []; // clear
2829  $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
2830  $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
2831  }
2832 
2839  protected function doRollback( $fname ) {
2840  if ( $this->mTrxLevel ) {
2841  # Disconnects cause rollback anyway, so ignore those errors
2842  $ignoreErrors = true;
2843  $this->query( 'ROLLBACK', $fname, $ignoreErrors );
2844  $this->mTrxLevel = 0;
2845  }
2846  }
2847 
2848  public function flushSnapshot( $fname = __METHOD__ ) {
2849  if ( $this->writesOrCallbacksPending() || $this->explicitTrxActive() ) {
2850  // This only flushes transactions to clear snapshots, not to write data
2851  $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
2852  throw new DBUnexpectedError(
2853  $this,
2854  "$fname: Cannot flush snapshot because writes are pending ($fnames)."
2855  );
2856  }
2857 
2858  $this->commit( $fname, self::FLUSHING_INTERNAL );
2859  }
2860 
2861  public function explicitTrxActive() {
2862  return $this->mTrxLevel && ( $this->mTrxAtomicLevels || !$this->mTrxAutomatic );
2863  }
2864 
2880  public function duplicateTableStructure( $oldName, $newName, $temporary = false,
2881  $fname = __METHOD__
2882  ) {
2883  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2884  }
2885 
2886  public function listTables( $prefix = null, $fname = __METHOD__ ) {
2887  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2888  }
2889 
2890  public function listViews( $prefix = null, $fname = __METHOD__ ) {
2891  throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2892  }
2893 
2894  public function timestamp( $ts = 0 ) {
2895  $t = new ConvertibleTimestamp( $ts );
2896  // Let errors bubble up to avoid putting garbage in the DB
2897  return $t->getTimestamp( TS_MW );
2898  }
2899 
2900  public function timestampOrNull( $ts = null ) {
2901  if ( is_null( $ts ) ) {
2902  return null;
2903  } else {
2904  return $this->timestamp( $ts );
2905  }
2906  }
2907 
2921  protected function resultObject( $result ) {
2922  if ( !$result ) {
2923  return false;
2924  } elseif ( $result instanceof ResultWrapper ) {
2925  return $result;
2926  } elseif ( $result === true ) {
2927  // Successful write query
2928  return $result;
2929  } else {
2930  return new ResultWrapper( $this, $result );
2931  }
2932  }
2933 
2934  public function ping( &$rtt = null ) {
2935  // Avoid hitting the server if it was hit recently
2936  if ( $this->isOpen() && ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
2937  if ( !func_num_args() || $this->mRTTEstimate > 0 ) {
2938  $rtt = $this->mRTTEstimate;
2939  return true; // don't care about $rtt
2940  }
2941  }
2942 
2943  // This will reconnect if possible or return false if not
2944  $this->clearFlag( self::DBO_TRX, self::REMEMBER_PRIOR );
2945  $ok = ( $this->query( self::PING_QUERY, __METHOD__, true ) !== false );
2946  $this->restoreFlags( self::RESTORE_PRIOR );
2947 
2948  if ( $ok ) {
2949  $rtt = $this->mRTTEstimate;
2950  }
2951 
2952  return $ok;
2953  }
2954 
2958  protected function reconnect() {
2959  $this->closeConnection();
2960  $this->mOpened = false;
2961  $this->mConn = false;
2962  try {
2963  $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
2964  $this->lastPing = microtime( true );
2965  $ok = true;
2966  } catch ( DBConnectionError $e ) {
2967  $ok = false;
2968  }
2969 
2970  return $ok;
2971  }
2972 
2973  public function getSessionLagStatus() {
2974  return $this->getTransactionLagStatus() ?: $this->getApproximateLagStatus();
2975  }
2976 
2988  protected function getTransactionLagStatus() {
2989  return $this->mTrxLevel
2990  ? [ 'lag' => $this->mTrxReplicaLag, 'since' => $this->trxTimestamp() ]
2991  : null;
2992  }
2993 
3000  protected function getApproximateLagStatus() {
3001  return [
3002  'lag' => $this->getLBInfo( 'replica' ) ? $this->getLag() : 0,
3003  'since' => microtime( true )
3004  ];
3005  }
3006 
3025  public static function getCacheSetOptions( IDatabase $db1 ) {
3026  $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
3027  foreach ( func_get_args() as $db ) {
3029  $status = $db->getSessionLagStatus();
3030  if ( $status['lag'] === false ) {
3031  $res['lag'] = false;
3032  } elseif ( $res['lag'] !== false ) {
3033  $res['lag'] = max( $res['lag'], $status['lag'] );
3034  }
3035  $res['since'] = min( $res['since'], $status['since'] );
3036  $res['pending'] = $res['pending'] ?: $db->writesPending();
3037  }
3038 
3039  return $res;
3040  }
3041 
3042  public function getLag() {
3043  return 0;
3044  }
3045 
3046  public function maxListLen() {
3047  return 0;
3048  }
3049 
3050  public function encodeBlob( $b ) {
3051  return $b;
3052  }
3053 
3054  public function decodeBlob( $b ) {
3055  if ( $b instanceof Blob ) {
3056  $b = $b->fetch();
3057  }
3058  return $b;
3059  }
3060 
3061  public function setSessionOptions( array $options ) {
3062  }
3063 
3064  public function sourceFile(
3065  $filename,
3066  callable $lineCallback = null,
3067  callable $resultCallback = null,
3068  $fname = false,
3069  callable $inputCallback = null
3070  ) {
3071  MediaWiki\suppressWarnings();
3072  $fp = fopen( $filename, 'r' );
3073  MediaWiki\restoreWarnings();
3074 
3075  if ( false === $fp ) {
3076  throw new RuntimeException( "Could not open \"{$filename}\".\n" );
3077  }
3078 
3079  if ( !$fname ) {
3080  $fname = __METHOD__ . "( $filename )";
3081  }
3082 
3083  try {
3084  $error = $this->sourceStream(
3085  $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
3086  } catch ( Exception $e ) {
3087  fclose( $fp );
3088  throw $e;
3089  }
3090 
3091  fclose( $fp );
3092 
3093  return $error;
3094  }
3095 
3096  public function setSchemaVars( $vars ) {
3097  $this->mSchemaVars = $vars;
3098  }
3099 
3100  public function sourceStream(
3101  $fp,
3102  callable $lineCallback = null,
3103  callable $resultCallback = null,
3104  $fname = __METHOD__,
3105  callable $inputCallback = null
3106  ) {
3107  $cmd = '';
3108 
3109  while ( !feof( $fp ) ) {
3110  if ( $lineCallback ) {
3111  call_user_func( $lineCallback );
3112  }
3113 
3114  $line = trim( fgets( $fp ) );
3115 
3116  if ( $line == '' ) {
3117  continue;
3118  }
3119 
3120  if ( '-' == $line[0] && '-' == $line[1] ) {
3121  continue;
3122  }
3123 
3124  if ( $cmd != '' ) {
3125  $cmd .= ' ';
3126  }
3127 
3128  $done = $this->streamStatementEnd( $cmd, $line );
3129 
3130  $cmd .= "$line\n";
3131 
3132  if ( $done || feof( $fp ) ) {
3133  $cmd = $this->replaceVars( $cmd );
3134 
3135  if ( !$inputCallback || call_user_func( $inputCallback, $cmd ) ) {
3136  $res = $this->query( $cmd, $fname );
3137 
3138  if ( $resultCallback ) {
3139  call_user_func( $resultCallback, $res, $this );
3140  }
3141 
3142  if ( false === $res ) {
3143  $err = $this->lastError();
3144 
3145  return "Query \"{$cmd}\" failed with error code \"$err\".\n";
3146  }
3147  }
3148  $cmd = '';
3149  }
3150  }
3151 
3152  return true;
3153  }
3154 
3162  public function streamStatementEnd( &$sql, &$newLine ) {
3163  if ( $this->delimiter ) {
3164  $prev = $newLine;
3165  $newLine = preg_replace(
3166  '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
3167  if ( $newLine != $prev ) {
3168  return true;
3169  }
3170  }
3171 
3172  return false;
3173  }
3174 
3195  protected function replaceVars( $ins ) {
3196  $vars = $this->getSchemaVars();
3197  return preg_replace_callback(
3198  '!
3199  /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
3200  \'\{\$ (\w+) }\' | # 3. addQuotes
3201  `\{\$ (\w+) }` | # 4. addIdentifierQuotes
3202  /\*\$ (\w+) \*/ # 5. leave unencoded
3203  !x',
3204  function ( $m ) use ( $vars ) {
3205  // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
3206  // check for both nonexistent keys *and* the empty string.
3207  if ( isset( $m[1] ) && $m[1] !== '' ) {
3208  if ( $m[1] === 'i' ) {
3209  return $this->indexName( $m[2] );
3210  } else {
3211  return $this->tableName( $m[2] );
3212  }
3213  } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
3214  return $this->addQuotes( $vars[$m[3]] );
3215  } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
3216  return $this->addIdentifierQuotes( $vars[$m[4]] );
3217  } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
3218  return $vars[$m[5]];
3219  } else {
3220  return $m[0];
3221  }
3222  },
3223  $ins
3224  );
3225  }
3226 
3233  protected function getSchemaVars() {
3234  if ( $this->mSchemaVars ) {
3235  return $this->mSchemaVars;
3236  } else {
3237  return $this->getDefaultSchemaVars();
3238  }
3239  }
3240 
3249  protected function getDefaultSchemaVars() {
3250  return [];
3251  }
3252 
3253  public function lockIsFree( $lockName, $method ) {
3254  return true;
3255  }
3256 
3257  public function lock( $lockName, $method, $timeout = 5 ) {
3258  $this->mNamedLocksHeld[$lockName] = 1;
3259 
3260  return true;
3261  }
3262 
3263  public function unlock( $lockName, $method ) {
3264  unset( $this->mNamedLocksHeld[$lockName] );
3265 
3266  return true;
3267  }
3268 
3269  public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
3270  if ( $this->writesOrCallbacksPending() ) {
3271  // This only flushes transactions to clear snapshots, not to write data
3272  $fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
3273  throw new DBUnexpectedError(
3274  $this,
3275  "$fname: Cannot flush pre-lock snapshot because writes are pending ($fnames)."
3276  );
3277  }
3278 
3279  if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
3280  return null;
3281  }
3282 
3283  $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
3284  if ( $this->trxLevel() ) {
3285  // There is a good chance an exception was thrown, causing any early return
3286  // from the caller. Let any error handler get a chance to issue rollback().
3287  // If there isn't one, let the error bubble up and trigger server-side rollback.
3288  $this->onTransactionResolution(
3289  function () use ( $lockKey, $fname ) {
3290  $this->unlock( $lockKey, $fname );
3291  },
3292  $fname
3293  );
3294  } else {
3295  $this->unlock( $lockKey, $fname );
3296  }
3297  } );
3298 
3299  $this->commit( $fname, self::FLUSHING_INTERNAL );
3300 
3301  return $unlocker;
3302  }
3303 
3304  public function namedLocksEnqueue() {
3305  return false;
3306  }
3307 
3317  public function lockTables( $read, $write, $method, $lowPriority = true ) {
3318  return true;
3319  }
3320 
3327  public function unlockTables( $method ) {
3328  return true;
3329  }
3330 
3338  public function dropTable( $tableName, $fName = __METHOD__ ) {
3339  if ( !$this->tableExists( $tableName, $fName ) ) {
3340  return false;
3341  }
3342  $sql = "DROP TABLE " . $this->tableName( $tableName ) . " CASCADE";
3343 
3344  return $this->query( $sql, $fName );
3345  }
3346 
3347  public function getInfinity() {
3348  return 'infinity';
3349  }
3350 
3351  public function encodeExpiry( $expiry ) {
3352  return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
3353  ? $this->getInfinity()
3354  : $this->timestamp( $expiry );
3355  }
3356 
3357  public function decodeExpiry( $expiry, $format = TS_MW ) {
3358  if ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() ) {
3359  return 'infinity';
3360  }
3361 
3362  return ConvertibleTimestamp::convert( $format, $expiry );
3363  }
3364 
3365  public function setBigSelects( $value = true ) {
3366  // no-op
3367  }
3368 
3369  public function isReadOnly() {
3370  return ( $this->getReadOnlyReason() !== false );
3371  }
3372 
3376  protected function getReadOnlyReason() {
3377  $reason = $this->getLBInfo( 'readOnlyReason' );
3378 
3379  return is_string( $reason ) ? $reason : false;
3380  }
3381 
3382  public function setTableAliases( array $aliases ) {
3383  $this->tableAliases = $aliases;
3384  }
3385 
3390  protected function requiresDatabaseUser() {
3391  return true;
3392  }
3393 
3405  protected function getBindingHandle() {
3406  if ( !$this->mConn ) {
3407  throw new DBUnexpectedError(
3408  $this,
3409  'DB connection was already closed or the connection dropped.'
3410  );
3411  }
3412 
3413  return $this->mConn;
3414  }
3415 
3420  public function __toString() {
3421  return (string)$this->mConn;
3422  }
3423 
3428  public function __clone() {
3429  $this->connLogger->warning(
3430  "Cloning " . get_class( $this ) . " is not recomended; forking connection:\n" .
3431  ( new RuntimeException() )->getTraceAsString()
3432  );
3433 
3434  if ( $this->isOpen() ) {
3435  // Open a new connection resource without messing with the old one
3436  $this->mOpened = false;
3437  $this->mConn = false;
3438  $this->mTrxEndCallbacks = []; // don't copy
3439  $this->handleSessionLoss(); // no trx or locks anymore
3440  $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
3441  $this->lastPing = microtime( true );
3442  }
3443  }
3444 
3450  public function __sleep() {
3451  throw new RuntimeException( 'Database serialization may cause problems, since ' .
3452  'the connection is not restored on wakeup.' );
3453  }
3454 
3458  public function __destruct() {
3459  if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
3460  trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
3461  }
3462 
3463  $danglingWriters = $this->pendingWriteAndCallbackCallers();
3464  if ( $danglingWriters ) {
3465  $fnames = implode( ', ', $danglingWriters );
3466  trigger_error( "DB transaction writes or callbacks still pending ($fnames)." );
3467  }
3468 
3469  if ( $this->mConn ) {
3470  // Avoid connection leaks for sanity. Normally, resources close at script completion.
3471  // The connection might already be closed in zend/hhvm by now, so suppress warnings.
3472  \MediaWiki\suppressWarnings();
3473  $this->closeConnection();
3474  \MediaWiki\restoreWarnings();
3475  $this->mConn = false;
3476  $this->mOpened = false;
3477  }
3478  }
3479 
3484  public function getSearchEngine() {
3485  wfDeprecated( __METHOD__, '1.28' );
3487  }
3488 }
3489 
3490 class_alias( 'Database', 'DatabaseBase' );
rollback($fname=__METHOD__, $flush= '')
Rollback a transaction previously started using begin().
Definition: Database.php:2799
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:3357
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:2457
commit($fname=__METHOD__, $flush= '')
Commits a transaction previously started using begin().
Definition: Database.php:2738
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:3347
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:3458
tableExists($table, $fname=__METHOD__)
Query whether a given table exists.
Definition: Database.php:1415
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:3484
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:3046
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:1984
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:3450
insert($table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
Definition: Database.php:1449
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:2792
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:3061
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:2407
tableNamesN()
Fetch a number of table names into an zero-indexed numerical array This is handy when you need to con...
Definition: Database.php:1790
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:1660
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:3025
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:3376
aggregateValue($valuedata, $valuename= 'value')
Return aggregated value alias.
Definition: Database.php:1644
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:3382
doRollback($fname)
Issues the ROLLBACK command to the database server.
Definition: Database.php:2839
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:2421
runTransactionListenerCallbacks($trigger)
Actually run any "transaction listener" callbacks.
Definition: Database.php:2615
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:2894
unlockTables($method)
Unlock specific tables.
Definition: Database.php:3327
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:3000
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:1533
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:2861
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:1809
makeUpdateOptionsArray($options)
Make UPDATE options array for Database::makeUpdateOptions.
Definition: Database.php:1507
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:2369
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:3050
tableName($name, $format= 'quoted')
Format a table name ready for use in constructing an SQL query.
Definition: Database.php:1693
escapeLikeInternal($s)
Definition: Database.php:2005
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:1879
anyChar()
Returns a token for buildLike() that denotes a '_' to be used in a LIKE query.
Definition: Database.php:2029
unionQueries($sqls, $all)
Construct a UNION query This is used for providing overload point for other DB abstractions not compa...
Definition: Database.php:2373
selectDB($db)
Change the current database.
Definition: Database.php:1676
lockIsFree($lockName, $method)
Check to see if a named lock is available (non-blocking)
Definition: Database.php:3253
fieldNameWithAlias($name, $alias=false)
Get an aliased field name e.g.
Definition: Database.php:1843
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:1779
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:2127
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:2037
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:2205
indexName($index)
Get the name of an index in a given table.
Definition: Database.php:1954
timestampOrNull($ts=null)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:2900
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:2506
endAtomic($fname=__METHOD__)
Ends an atomic section of SQL statements.
Definition: Database.php:2650
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:2637
begin($fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.
Definition: Database.php:2678
isReadOnly()
Definition: Database.php:3369
string $mSchema
Definition: Database.php:101
string $mLastQuery
SQL query.
Definition: Database.php:53
requiresDatabaseUser()
Definition: Database.php:3390
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:2223
onTransactionIdle(callable $callback, $fname=__METHOD__)
Run a callback as soon as there is no transaction pending.
Definition: Database.php:2483
getTransactionLagStatus()
Get the replica DB lag when the current transaction started.
Definition: Database.php:2988
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:3054
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:2065
$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:2665
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:1656
buildGroupConcatField($delim, $table, $field, $conds= '', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
Definition: Database.php:1664
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:3100
static generalizeSQL($sql)
Removes most variables from an SQL query and replaces them with X or N for numbers.
Definition: Database.php:1374
__clone()
Make sure that copies do not share the same client binding handle.
Definition: Database.php:3428
$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:1545
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:2258
isQuotedIdentifier($name)
Returns if the given identifier looks quoted or not according to the database convention for quoting ...
Definition: Database.php:1997
wasConnectionError($errno)
Do not use this method outside of Database/DBError classes.
Definition: Database.php:2417
$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:2379
ping(&$rtt=null)
Ping the server and try to reconnect if it there is no connection.
Definition: Database.php:2934
array bool $mSchemaVars
Definition: Database.php:109
serverIsReadOnly()
Definition: Database.php:2472
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:2490
wasErrorReissuable()
Determines if the last query error was due to a dropped connection and should be dealt with by pingin...
Definition: Database.php:2403
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:1652
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:3269
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:1672
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:2151
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:2880
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:3064
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:3365
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
Definition: Database.php:3162
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:2009
getReplicaPos()
Get the replication position of this replica DB.
Definition: Database.php:2462
indexExists($table, $index, $fname=__METHOD__)
Determines whether an index exists Usually throws a DBQueryError on failure If errors are explicitly ...
Definition: Database.php:1402
unlock($lockName, $method)
Release a lock.
Definition: Database.php:3263
getBindingHandle()
Get the underlying binding handle, mConn.
Definition: Database.php:3405
bitNot($field)
Definition: Database.php:1648
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:2399
wasDeadlock()
Determines if the last failure was due to a deadlock.
Definition: Database.php:2395
runOnTransactionIdleCallbacks($trigger)
Actually run and consume any "on transaction idle/resolution" callbacks.
Definition: Database.php:2535
flushSnapshot($fname=__METHOD__)
Commit any transaction but error out if writes or callbacks are pending.
Definition: Database.php:2848
$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:3317
strreplace($orig, $old, $new)
Returns a comand for str_replace function in SQL query.
Definition: Database.php:2387
addQuotes($s)
Adds quotes and backslashes.
Definition: Database.php:1958
doBegin($fname)
Issues the BEGIN command to the database server.
Definition: Database.php:2733
getMasterPos()
Get the position of this master.
Definition: Database.php:2467
anyString()
Returns a token for buildLike() that denotes a '' to be used in a LIKE query.
Definition: Database.php:2033
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:1857
getSessionLagStatus()
Get the replica DB lag when the current transaction started or a general lag estimate if not transact...
Definition: Database.php:2973
onTransactionResolution(callable $callback, $fname=__METHOD__)
Run a callback as soon as the current transaction commits or rolls back.
Definition: Database.php:2476
static getSearchEngineClass(IDatabase $db)
getDefaultSchemaVars()
Get schema variables to use if none have been set via setSchemaVars().
Definition: Database.php:3249
fieldExists($table, $field, $fname=__METHOD__)
Determines whether a field exists in a table.
Definition: Database.php:1396
listViews($prefix=null, $fname=__METHOD__)
Lists all the VIEWs in the database.
Definition: Database.php:2890
getServer()
Get the server hostname or IP address.
Definition: Database.php:1689
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:1429
makeWhereFrom2d($data, $baseKey, $subKey)
Build a partial where clause from a 2-d array such as used for LinkBatch.
Definition: Database.php:1625
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:3338
getServerUptime()
Determines how long the server has been up.
Definition: Database.php:2391
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:1445
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:2522
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:2051
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:3304
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:2069
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:2585
array null $preparedArgs
Definition: Database.php:113
encodeExpiry($expiry)
Encode an expiry time into the DBMS dependent format.
Definition: Database.php:3351
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:2297
array $mSessionVars
Definition: Database.php:111
__toString()
Definition: Database.php:3420
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:3096
bool null $mDefaultBigSelects
Definition: Database.php:107
replaceVars($ins)
Database independent variable replacement.
Definition: Database.php:3195
tableNamesWithAlias($tables)
Gets an array of aliased table names.
Definition: Database.php:1823
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:1527
getLag()
Get replica DB lag.
Definition: Database.php:3042
limitResult($sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.
Definition: Database.php:2358
getSchemaVars()
Get schema variables.
Definition: Database.php:3233
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:2921
getDBname()
Get the current DB name.
Definition: Database.php:1685
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:2886
restoreErrorHandler()
Definition: Database.php:655
prependDatabaseOrSchema($namespace, $relation, $format)
Definition: Database.php:1768
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:3257
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