MediaWiki  1.27.2
Database.php
Go to the documentation of this file.
1 <?php
2 
32 abstract class DatabaseBase implements IDatabase {
34  const DEADLOCK_TRIES = 4;
35 
37  const DEADLOCK_DELAY_MIN = 500000;
38 
40  const DEADLOCK_DELAY_MAX = 1500000;
41 
42  protected $mLastQuery = '';
43  protected $mDoneWrites = false;
44  protected $mPHPError = false;
45 
47 
49  protected $srvCache;
50 
52  protected $mConn = null;
53  protected $mOpened = false;
54 
56  protected $mTrxIdleCallbacks = [];
58  protected $mTrxPreCommitCallbacks = [];
59 
60  protected $mTablePrefix;
61  protected $mSchema;
62  protected $mFlags;
63  protected $mForeign;
64  protected $mLBInfo = [];
65  protected $mDefaultBigSelects = null;
66  protected $mSchemaVars = false;
68  protected $mSessionVars = [];
69 
70  protected $preparedArgs;
71 
72  protected $htmlErrors;
73 
74  protected $delimiter = ';';
75 
82  protected $mTrxLevel = 0;
83 
90  protected $mTrxShortId = '';
91 
100  private $mTrxTimestamp = null;
101 
103  private $mTrxSlaveLag = null;
104 
112  private $mTrxFname = null;
113 
120  private $mTrxDoneWrites = false;
121 
128  private $mTrxAutomatic = false;
129 
135  private $mTrxAtomicLevels = [];
136 
142  private $mTrxAutomaticAtomic = false;
143 
149  private $mTrxWriteCallers = [];
150 
156  private $mTrxWriteDuration = 0.0;
157 
159  private $mNamedLocksHeld = [];
160 
163 
168  protected $fileHandle = null;
169 
174  protected $allViews = null;
175 
177  protected $trxProfiler;
178 
179  public function getServerInfo() {
180  return $this->getServerVersion();
181  }
182 
186  public function getDelimiter() {
187  return $this->delimiter;
188  }
189 
199  public function debug( $debug = null ) {
200  return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
201  }
202 
203  public function bufferResults( $buffer = null ) {
204  if ( is_null( $buffer ) ) {
205  return !(bool)( $this->mFlags & DBO_NOBUFFER );
206  } else {
207  return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
208  }
209  }
210 
223  protected function ignoreErrors( $ignoreErrors = null ) {
224  return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
225  }
226 
227  public function trxLevel() {
228  return $this->mTrxLevel;
229  }
230 
231  public function trxTimestamp() {
232  return $this->mTrxLevel ? $this->mTrxTimestamp : null;
233  }
234 
235  public function tablePrefix( $prefix = null ) {
236  return wfSetVar( $this->mTablePrefix, $prefix );
237  }
238 
239  public function dbSchema( $schema = null ) {
240  return wfSetVar( $this->mSchema, $schema );
241  }
242 
248  public function setFileHandle( $fh ) {
249  $this->fileHandle = $fh;
250  }
251 
252  public function getLBInfo( $name = null ) {
253  if ( is_null( $name ) ) {
254  return $this->mLBInfo;
255  } else {
256  if ( array_key_exists( $name, $this->mLBInfo ) ) {
257  return $this->mLBInfo[$name];
258  } else {
259  return null;
260  }
261  }
262  }
263 
264  public function setLBInfo( $name, $value = null ) {
265  if ( is_null( $value ) ) {
266  $this->mLBInfo = $name;
267  } else {
268  $this->mLBInfo[$name] = $value;
269  }
270  }
271 
278  public function setLazyMasterHandle( IDatabase $conn ) {
279  $this->lazyMasterHandle = $conn;
280  }
281 
287  public function getLazyMasterHandle() {
289  }
290 
294  protected function getTransactionProfiler() {
295  if ( !$this->trxProfiler ) {
296  $this->trxProfiler = new TransactionProfiler();
297  }
298 
299  return $this->trxProfiler;
300  }
301 
306  public function setTransactionProfiler( TransactionProfiler $profiler ) {
307  $this->trxProfiler = $profiler;
308  }
309 
315  public function cascadingDeletes() {
316  return false;
317  }
318 
324  public function cleanupTriggers() {
325  return false;
326  }
327 
334  public function strictIPs() {
335  return false;
336  }
337 
343  public function realTimestamps() {
344  return false;
345  }
346 
347  public function implicitGroupby() {
348  return true;
349  }
350 
351  public function implicitOrderby() {
352  return true;
353  }
354 
361  public function searchableIPs() {
362  return false;
363  }
364 
370  public function functionalIndexes() {
371  return false;
372  }
373 
374  public function lastQuery() {
375  return $this->mLastQuery;
376  }
377 
378  public function doneWrites() {
379  return (bool)$this->mDoneWrites;
380  }
381 
382  public function lastDoneWrites() {
383  return $this->mDoneWrites ?: false;
384  }
385 
386  public function writesPending() {
387  return $this->mTrxLevel && $this->mTrxDoneWrites;
388  }
389 
390  public function writesOrCallbacksPending() {
391  return $this->mTrxLevel && (
392  $this->mTrxDoneWrites || $this->mTrxIdleCallbacks || $this->mTrxPreCommitCallbacks
393  );
394  }
395 
396  public function pendingWriteQueryDuration() {
397  return $this->mTrxLevel ? $this->mTrxWriteDuration : false;
398  }
399 
400  public function pendingWriteCallers() {
401  return $this->mTrxLevel ? $this->mTrxWriteCallers : [];
402  }
403 
404  public function isOpen() {
405  return $this->mOpened;
406  }
407 
408  public function setFlag( $flag ) {
409  $this->mFlags |= $flag;
410  }
411 
412  public function clearFlag( $flag ) {
413  $this->mFlags &= ~$flag;
414  }
415 
416  public function getFlag( $flag ) {
417  return !!( $this->mFlags & $flag );
418  }
419 
420  public function getProperty( $name ) {
421  return $this->$name;
422  }
423 
424  public function getWikiID() {
425  if ( $this->mTablePrefix ) {
426  return "{$this->mDBname}-{$this->mTablePrefix}";
427  } else {
428  return $this->mDBname;
429  }
430  }
431 
439  private function getSqlFilePath( $filename ) {
440  global $IP;
441  $dbmsSpecificFilePath = "$IP/maintenance/" . $this->getType() . "/$filename";
442  if ( file_exists( $dbmsSpecificFilePath ) ) {
443  return $dbmsSpecificFilePath;
444  } else {
445  return "$IP/maintenance/$filename";
446  }
447  }
448 
455  public function getSchemaPath() {
456  return $this->getSqlFilePath( 'tables.sql' );
457  }
458 
465  public function getUpdateKeysPath() {
466  return $this->getSqlFilePath( 'update-keys.sql' );
467  }
468 
476  abstract function indexInfo( $table, $index, $fname = __METHOD__ );
477 
484  abstract function strencode( $s );
485 
498  function __construct( array $params ) {
500 
501  $this->srvCache = ObjectCache::getLocalServerInstance( 'hash' );
502 
503  $server = $params['host'];
504  $user = $params['user'];
505  $password = $params['password'];
506  $dbName = $params['dbname'];
507  $flags = $params['flags'];
508  $tablePrefix = $params['tablePrefix'];
509  $schema = $params['schema'];
510  $foreign = $params['foreign'];
511 
512  $this->mFlags = $flags;
513  if ( $this->mFlags & DBO_DEFAULT ) {
514  if ( $wgCommandLineMode ) {
515  $this->mFlags &= ~DBO_TRX;
516  } else {
517  $this->mFlags |= DBO_TRX;
518  }
519  }
520 
521  $this->mSessionVars = $params['variables'];
522 
524  if ( $tablePrefix === 'get from global' ) {
525  $this->mTablePrefix = $wgDBprefix;
526  } else {
527  $this->mTablePrefix = $tablePrefix;
528  }
529 
531  if ( $schema === 'get from global' ) {
532  $this->mSchema = $wgDBmwschema;
533  } else {
534  $this->mSchema = $schema;
535  }
536 
537  $this->mForeign = $foreign;
538 
539  if ( isset( $params['trxProfiler'] ) ) {
540  $this->trxProfiler = $params['trxProfiler']; // override
541  }
542 
543  if ( $user ) {
544  $this->open( $server, $user, $password, $dbName );
545  }
546  }
547 
553  public function __sleep() {
554  throw new MWException( 'Database serialization may cause problems, since ' .
555  'the connection is not restored on wakeup.' );
556  }
557 
580  final public static function factory( $dbType, $p = [] ) {
581  $canonicalDBTypes = [
582  'mysql' => [ 'mysqli', 'mysql' ],
583  'postgres' => [],
584  'sqlite' => [],
585  'oracle' => [],
586  'mssql' => [],
587  ];
588 
589  $driver = false;
590  $dbType = strtolower( $dbType );
591  if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
592  $possibleDrivers = $canonicalDBTypes[$dbType];
593  if ( !empty( $p['driver'] ) ) {
594  if ( in_array( $p['driver'], $possibleDrivers ) ) {
595  $driver = $p['driver'];
596  } else {
597  throw new MWException( __METHOD__ .
598  " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" );
599  }
600  } else {
601  foreach ( $possibleDrivers as $posDriver ) {
602  if ( extension_loaded( $posDriver ) ) {
603  $driver = $posDriver;
604  break;
605  }
606  }
607  }
608  } else {
609  $driver = $dbType;
610  }
611  if ( $driver === false ) {
612  throw new MWException( __METHOD__ .
613  " no viable database extension found for type '$dbType'" );
614  }
615 
616  // Determine schema defaults. Currently Microsoft SQL Server uses $wgDBmwschema,
617  // and everything else doesn't use a schema (e.g. null)
618  // Although postgres and oracle support schemas, we don't use them (yet)
619  // to maintain backwards compatibility
620  $defaultSchemas = [
621  'mssql' => 'get from global',
622  ];
623 
624  $class = 'Database' . ucfirst( $driver );
625  if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
626  // Resolve some defaults for b/c
627  $p['host'] = isset( $p['host'] ) ? $p['host'] : false;
628  $p['user'] = isset( $p['user'] ) ? $p['user'] : false;
629  $p['password'] = isset( $p['password'] ) ? $p['password'] : false;
630  $p['dbname'] = isset( $p['dbname'] ) ? $p['dbname'] : false;
631  $p['flags'] = isset( $p['flags'] ) ? $p['flags'] : 0;
632  $p['variables'] = isset( $p['variables'] ) ? $p['variables'] : [];
633  $p['tablePrefix'] = isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global';
634  if ( !isset( $p['schema'] ) ) {
635  $p['schema'] = isset( $defaultSchemas[$dbType] ) ? $defaultSchemas[$dbType] : null;
636  }
637  $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false;
638 
639  return new $class( $p );
640  } else {
641  return null;
642  }
643  }
644 
645  protected function installErrorHandler() {
646  $this->mPHPError = false;
647  $this->htmlErrors = ini_set( 'html_errors', '0' );
648  set_error_handler( [ $this, 'connectionErrorHandler' ] );
649  }
650 
654  protected function restoreErrorHandler() {
655  restore_error_handler();
656  if ( $this->htmlErrors !== false ) {
657  ini_set( 'html_errors', $this->htmlErrors );
658  }
659  if ( $this->mPHPError ) {
660  $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
661  $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
662 
663  return $error;
664  } else {
665  return false;
666  }
667  }
668 
673  public function connectionErrorHandler( $errno, $errstr ) {
674  $this->mPHPError = $errstr;
675  }
676 
683  protected function getLogContext( array $extras = [] ) {
684  return array_merge(
685  [
686  'db_server' => $this->mServer,
687  'db_name' => $this->mDBname,
688  'db_user' => $this->mUser,
689  ],
690  $extras
691  );
692  }
693 
694  public function close() {
695  if ( count( $this->mTrxIdleCallbacks ) ) { // sanity
696  throw new MWException( "Transaction idle callbacks still pending." );
697  }
698  if ( $this->mConn ) {
699  if ( $this->trxLevel() ) {
700  if ( !$this->mTrxAutomatic ) {
701  wfWarn( "Transaction still in progress (from {$this->mTrxFname}), " .
702  " performing implicit commit before closing connection!" );
703  }
704 
705  $this->commit( __METHOD__, 'flush' );
706  }
707 
708  $closed = $this->closeConnection();
709  $this->mConn = false;
710  } else {
711  $closed = true;
712  }
713  $this->mOpened = false;
714 
715  return $closed;
716  }
717 
723  protected function assertOpen() {
724  if ( !$this->isOpen() ) {
725  throw new DBUnexpectedError( $this, "DB connection was already closed." );
726  }
727  }
728 
734  abstract protected function closeConnection();
735 
736  function reportConnectionError( $error = 'Unknown error' ) {
737  $myError = $this->lastError();
738  if ( $myError ) {
739  $error = $myError;
740  }
741 
742  # New method
743  throw new DBConnectionError( $this, $error );
744  }
745 
753  abstract protected function doQuery( $sql );
754 
762  protected function isWriteQuery( $sql ) {
763  return !preg_match( '/^(?:SELECT|BEGIN|ROLLBACK|COMMIT|SET|SHOW|EXPLAIN|\(SELECT)\b/i', $sql );
764  }
765 
775  protected function isTransactableQuery( $sql ) {
776  $verb = substr( $sql, 0, strcspn( $sql, " \t\r\n" ) );
777  return !in_array( $verb, [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ] );
778  }
779 
780  public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
781  global $wgUser;
782 
783  $this->mLastQuery = $sql;
784 
785  $isWriteQuery = $this->isWriteQuery( $sql );
786  if ( $isWriteQuery ) {
787  $reason = $this->getReadOnlyReason();
788  if ( $reason !== false ) {
789  throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
790  }
791  # Set a flag indicating that writes have been done
792  $this->mDoneWrites = microtime( true );
793  }
794 
795  # Add a comment for easy SHOW PROCESSLIST interpretation
796  if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
797  $userName = $wgUser->getName();
798  if ( mb_strlen( $userName ) > 15 ) {
799  $userName = mb_substr( $userName, 0, 15 ) . '...';
800  }
801  $userName = str_replace( '/', '', $userName );
802  } else {
803  $userName = '';
804  }
805 
806  // Add trace comment to the begin of the sql string, right after the operator.
807  // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
808  $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
809 
810  if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX ) && $this->isTransactableQuery( $sql ) ) {
811  $this->begin( __METHOD__ . " ($fname)" );
812  $this->mTrxAutomatic = true;
813  }
814 
815  # Keep track of whether the transaction has write queries pending
816  if ( $this->mTrxLevel && !$this->mTrxDoneWrites && $isWriteQuery ) {
817  $this->mTrxDoneWrites = true;
818  $this->getTransactionProfiler()->transactionWritingIn(
819  $this->mServer, $this->mDBname, $this->mTrxShortId );
820  }
821 
822  $isMaster = !is_null( $this->getLBInfo( 'master' ) );
823  # generalizeSQL will probably cut down the query to reasonable
824  # logging size most of the time. The substr is really just a sanity check.
825  if ( $isMaster ) {
826  $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
827  $totalProf = 'DatabaseBase::query-master';
828  } else {
829  $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
830  $totalProf = 'DatabaseBase::query';
831  }
832  # Include query transaction state
833  $queryProf .= $this->mTrxShortId ? " [TRX#{$this->mTrxShortId}]" : "";
834 
835  $profiler = Profiler::instance();
836  if ( !$profiler instanceof ProfilerStub ) {
837  $totalProfSection = $profiler->scopedProfileIn( $totalProf );
838  $queryProfSection = $profiler->scopedProfileIn( $queryProf );
839  }
840 
841  if ( $this->debug() ) {
842  wfDebugLog( 'queries', sprintf( "%s: %s", $this->mDBname, $commentedSql ) );
843  }
844 
845  $queryId = MWDebug::query( $sql, $fname, $isMaster );
846 
847  # Avoid fatals if close() was called
848  $this->assertOpen();
849 
850  # Do the query and handle errors
851  $startTime = microtime( true );
852  $ret = $this->doQuery( $commentedSql );
853  $queryRuntime = microtime( true ) - $startTime;
854  # Log the query time and feed it into the DB trx profiler
855  $this->getTransactionProfiler()->recordQueryCompletion(
856  $queryProf, $startTime, $isWriteQuery, $this->affectedRows() );
857 
858  MWDebug::queryTime( $queryId );
859 
860  # Try reconnecting if the connection was lost
861  if ( false === $ret && $this->wasErrorReissuable() ) {
862  # Transaction is gone; this can mean lost writes or REPEATABLE-READ snapshots
863  $hadTrx = $this->mTrxLevel;
864  # T127428: for non-write transactions, a disconnect and a COMMIT are similar:
865  # neither changed data and in both cases any read snapshots are reset anyway.
866  $isNoopCommit = ( !$this->writesOrCallbacksPending() && $sql === 'COMMIT' );
867  # Update state tracking to reflect transaction loss
868  $this->mTrxLevel = 0;
869  $this->mTrxIdleCallbacks = []; // bug 65263
870  $this->mTrxPreCommitCallbacks = []; // bug 65263
871  wfDebug( "Connection lost, reconnecting...\n" );
872  # Stash the last error values since ping() might clear them
873  $lastError = $this->lastError();
874  $lastErrno = $this->lastErrno();
875  if ( $this->ping() ) {
876  wfDebug( "Reconnected\n" );
877  $server = $this->getServer();
878  $msg = __METHOD__ . ": lost connection to $server; reconnected";
879  wfDebugLog( 'DBPerformance', "$msg:\n" . wfBacktrace( true ) );
880 
881  if ( ( $hadTrx && !$isNoopCommit ) || $this->mNamedLocksHeld ) {
882  # Leave $ret as false and let an error be reported.
883  # Callers may catch the exception and continue to use the DB.
884  $this->reportQueryError( $lastError, $lastErrno, $sql, $fname, $tempIgnore );
885  } else {
886  # Should be safe to silently retry (no trx/callbacks/locks)
887  $startTime = microtime( true );
888  $ret = $this->doQuery( $commentedSql );
889  $queryRuntime = microtime( true ) - $startTime;
890  # Log the query time and feed it into the DB trx profiler
891  $this->getTransactionProfiler()->recordQueryCompletion(
892  $queryProf, $startTime, $isWriteQuery, $this->affectedRows() );
893  }
894  } else {
895  wfDebug( "Failed\n" );
896  }
897  }
898 
899  if ( false === $ret ) {
900  $this->reportQueryError(
901  $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
902  }
903 
904  $res = $this->resultObject( $ret );
905 
906  // Destroy profile sections in the opposite order to their creation
907  ScopedCallback::consume( $queryProfSection );
908  ScopedCallback::consume( $totalProfSection );
909 
910  if ( $isWriteQuery && $this->mTrxLevel ) {
911  $this->mTrxWriteDuration += $queryRuntime;
912  $this->mTrxWriteCallers[] = $fname;
913  }
914 
915  return $res;
916  }
917 
918  public function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
919  if ( $this->ignoreErrors() || $tempIgnore ) {
920  wfDebug( "SQL ERROR (ignored): $error\n" );
921  } else {
922  $sql1line = mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 );
923  wfLogDBError(
924  "{fname}\t{db_server}\t{errno}\t{error}\t{sql1line}",
925  $this->getLogContext( [
926  'method' => __METHOD__,
927  'errno' => $errno,
928  'error' => $error,
929  'sql1line' => $sql1line,
930  'fname' => $fname,
931  ] )
932  );
933  wfDebug( "SQL ERROR: " . $error . "\n" );
934  throw new DBQueryError( $this, $error, $errno, $sql, $fname );
935  }
936  }
937 
952  protected function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
953  /* MySQL doesn't support prepared statements (yet), so just
954  * pack up the query for reference. We'll manually replace
955  * the bits later.
956  */
957  return [ 'query' => $sql, 'func' => $func ];
958  }
959 
964  protected function freePrepared( $prepared ) {
965  /* No-op by default */
966  }
967 
975  public function execute( $prepared, $args = null ) {
976  if ( !is_array( $args ) ) {
977  # Pull the var args
978  $args = func_get_args();
979  array_shift( $args );
980  }
981 
982  $sql = $this->fillPrepared( $prepared['query'], $args );
983 
984  return $this->query( $sql, $prepared['func'] );
985  }
986 
994  public function fillPrepared( $preparedQuery, $args ) {
995  reset( $args );
996  $this->preparedArgs =& $args;
997 
998  return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
999  [ &$this, 'fillPreparedArg' ], $preparedQuery );
1000  }
1001 
1011  protected function fillPreparedArg( $matches ) {
1012  switch ( $matches[1] ) {
1013  case '\\?':
1014  return '?';
1015  case '\\!':
1016  return '!';
1017  case '\\&':
1018  return '&';
1019  }
1020 
1021  list( /* $n */, $arg ) = each( $this->preparedArgs );
1022 
1023  switch ( $matches[1] ) {
1024  case '?':
1025  return $this->addQuotes( $arg );
1026  case '!':
1027  return $arg;
1028  case '&':
1029  # return $this->addQuotes( file_get_contents( $arg ) );
1030  throw new DBUnexpectedError(
1031  $this,
1032  '& mode is not implemented. If it\'s really needed, uncomment the line above.'
1033  );
1034  default:
1035  throw new DBUnexpectedError(
1036  $this,
1037  'Received invalid match. This should never happen!'
1038  );
1039  }
1040  }
1041 
1042  public function freeResult( $res ) {
1043  }
1044 
1045  public function selectField(
1046  $table, $var, $cond = '', $fname = __METHOD__, $options = []
1047  ) {
1048  if ( $var === '*' ) { // sanity
1049  throw new DBUnexpectedError( $this, "Cannot use a * field: got '$var'" );
1050  }
1051 
1052  if ( !is_array( $options ) ) {
1053  $options = [ $options ];
1054  }
1055 
1056  $options['LIMIT'] = 1;
1057 
1058  $res = $this->select( $table, $var, $cond, $fname, $options );
1059  if ( $res === false || !$this->numRows( $res ) ) {
1060  return false;
1061  }
1062 
1063  $row = $this->fetchRow( $res );
1064 
1065  if ( $row !== false ) {
1066  return reset( $row );
1067  } else {
1068  return false;
1069  }
1070  }
1071 
1072  public function selectFieldValues(
1073  $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1074  ) {
1075  if ( $var === '*' ) { // sanity
1076  throw new DBUnexpectedError( $this, "Cannot use a * field" );
1077  } elseif ( !is_string( $var ) ) { // sanity
1078  throw new DBUnexpectedError( $this, "Cannot use an array of fields" );
1079  }
1080 
1081  if ( !is_array( $options ) ) {
1082  $options = [ $options ];
1083  }
1084 
1085  $res = $this->select( $table, $var, $cond, $fname, $options, $join_conds );
1086  if ( $res === false ) {
1087  return false;
1088  }
1089 
1090  $values = [];
1091  foreach ( $res as $row ) {
1092  $values[] = $row->$var;
1093  }
1094 
1095  return $values;
1096  }
1097 
1107  public function makeSelectOptions( $options ) {
1108  $preLimitTail = $postLimitTail = '';
1109  $startOpts = '';
1110 
1111  $noKeyOptions = [];
1112 
1113  foreach ( $options as $key => $option ) {
1114  if ( is_numeric( $key ) ) {
1115  $noKeyOptions[$option] = true;
1116  }
1117  }
1118 
1119  $preLimitTail .= $this->makeGroupByWithHaving( $options );
1120 
1121  $preLimitTail .= $this->makeOrderBy( $options );
1122 
1123  // if (isset($options['LIMIT'])) {
1124  // $tailOpts .= $this->limitResult('', $options['LIMIT'],
1125  // isset($options['OFFSET']) ? $options['OFFSET']
1126  // : false);
1127  // }
1128 
1129  if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
1130  $postLimitTail .= ' FOR UPDATE';
1131  }
1132 
1133  if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
1134  $postLimitTail .= ' LOCK IN SHARE MODE';
1135  }
1136 
1137  if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
1138  $startOpts .= 'DISTINCT';
1139  }
1140 
1141  # Various MySQL extensions
1142  if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
1143  $startOpts .= ' /*! STRAIGHT_JOIN */';
1144  }
1145 
1146  if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
1147  $startOpts .= ' HIGH_PRIORITY';
1148  }
1149 
1150  if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
1151  $startOpts .= ' SQL_BIG_RESULT';
1152  }
1153 
1154  if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
1155  $startOpts .= ' SQL_BUFFER_RESULT';
1156  }
1157 
1158  if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
1159  $startOpts .= ' SQL_SMALL_RESULT';
1160  }
1161 
1162  if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
1163  $startOpts .= ' SQL_CALC_FOUND_ROWS';
1164  }
1165 
1166  if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
1167  $startOpts .= ' SQL_CACHE';
1168  }
1169 
1170  if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
1171  $startOpts .= ' SQL_NO_CACHE';
1172  }
1173 
1174  if ( isset( $options['USE INDEX'] ) && is_string( $options['USE INDEX'] ) ) {
1175  $useIndex = $this->useIndexClause( $options['USE INDEX'] );
1176  } else {
1177  $useIndex = '';
1178  }
1179 
1180  return [ $startOpts, $useIndex, $preLimitTail, $postLimitTail ];
1181  }
1182 
1191  public function makeGroupByWithHaving( $options ) {
1192  $sql = '';
1193  if ( isset( $options['GROUP BY'] ) ) {
1194  $gb = is_array( $options['GROUP BY'] )
1195  ? implode( ',', $options['GROUP BY'] )
1196  : $options['GROUP BY'];
1197  $sql .= ' GROUP BY ' . $gb;
1198  }
1199  if ( isset( $options['HAVING'] ) ) {
1200  $having = is_array( $options['HAVING'] )
1201  ? $this->makeList( $options['HAVING'], LIST_AND )
1202  : $options['HAVING'];
1203  $sql .= ' HAVING ' . $having;
1204  }
1205 
1206  return $sql;
1207  }
1208 
1217  public function makeOrderBy( $options ) {
1218  if ( isset( $options['ORDER BY'] ) ) {
1219  $ob = is_array( $options['ORDER BY'] )
1220  ? implode( ',', $options['ORDER BY'] )
1221  : $options['ORDER BY'];
1222 
1223  return ' ORDER BY ' . $ob;
1224  }
1225 
1226  return '';
1227  }
1228 
1229  // See IDatabase::select for the docs for this function
1230  public function select( $table, $vars, $conds = '', $fname = __METHOD__,
1231  $options = [], $join_conds = [] ) {
1232  $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
1233 
1234  return $this->query( $sql, $fname );
1235  }
1236 
1237  public function selectSQLText( $table, $vars, $conds = '', $fname = __METHOD__,
1238  $options = [], $join_conds = []
1239  ) {
1240  if ( is_array( $vars ) ) {
1241  $vars = implode( ',', $this->fieldNamesWithAlias( $vars ) );
1242  }
1243 
1244  $options = (array)$options;
1245  $useIndexes = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
1246  ? $options['USE INDEX']
1247  : [];
1248 
1249  if ( is_array( $table ) ) {
1250  $from = ' FROM ' .
1251  $this->tableNamesWithUseIndexOrJOIN( $table, $useIndexes, $join_conds );
1252  } elseif ( $table != '' ) {
1253  if ( $table[0] == ' ' ) {
1254  $from = ' FROM ' . $table;
1255  } else {
1256  $from = ' FROM ' .
1257  $this->tableNamesWithUseIndexOrJOIN( [ $table ], $useIndexes, [] );
1258  }
1259  } else {
1260  $from = '';
1261  }
1262 
1263  list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) =
1264  $this->makeSelectOptions( $options );
1265 
1266  if ( !empty( $conds ) ) {
1267  if ( is_array( $conds ) ) {
1268  $conds = $this->makeList( $conds, LIST_AND );
1269  }
1270  $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
1271  } else {
1272  $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
1273  }
1274 
1275  if ( isset( $options['LIMIT'] ) ) {
1276  $sql = $this->limitResult( $sql, $options['LIMIT'],
1277  isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
1278  }
1279  $sql = "$sql $postLimitTail";
1280 
1281  if ( isset( $options['EXPLAIN'] ) ) {
1282  $sql = 'EXPLAIN ' . $sql;
1283  }
1284 
1285  return $sql;
1286  }
1287 
1288  public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
1289  $options = [], $join_conds = []
1290  ) {
1291  $options = (array)$options;
1292  $options['LIMIT'] = 1;
1293  $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
1294 
1295  if ( $res === false ) {
1296  return false;
1297  }
1298 
1299  if ( !$this->numRows( $res ) ) {
1300  return false;
1301  }
1302 
1303  $obj = $this->fetchObject( $res );
1304 
1305  return $obj;
1306  }
1307 
1308  public function estimateRowCount(
1309  $table, $vars = '*', $conds = '', $fname = __METHOD__, $options = []
1310  ) {
1311  $rows = 0;
1312  $res = $this->select( $table, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options );
1313 
1314  if ( $res ) {
1315  $row = $this->fetchRow( $res );
1316  $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
1317  }
1318 
1319  return $rows;
1320  }
1321 
1322  public function selectRowCount(
1323  $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1324  ) {
1325  $rows = 0;
1326  $sql = $this->selectSQLText( $tables, '1', $conds, $fname, $options, $join_conds );
1327  // The identifier quotes is primarily for MSSQL.
1328  $rowCountCol = $this->addIdentifierQuotes( "rowcount" );
1329  $tableName = $this->addIdentifierQuotes( "tmp_count" );
1330  $res = $this->query( "SELECT COUNT(*) AS $rowCountCol FROM ($sql) $tableName", $fname );
1331 
1332  if ( $res ) {
1333  $row = $this->fetchRow( $res );
1334  $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
1335  }
1336 
1337  return $rows;
1338  }
1339 
1348  protected static function generalizeSQL( $sql ) {
1349  # This does the same as the regexp below would do, but in such a way
1350  # as to avoid crashing php on some large strings.
1351  # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
1352 
1353  $sql = str_replace( "\\\\", '', $sql );
1354  $sql = str_replace( "\\'", '', $sql );
1355  $sql = str_replace( "\\\"", '', $sql );
1356  $sql = preg_replace( "/'.*'/s", "'X'", $sql );
1357  $sql = preg_replace( '/".*"/s', "'X'", $sql );
1358 
1359  # All newlines, tabs, etc replaced by single space
1360  $sql = preg_replace( '/\s+/', ' ', $sql );
1361 
1362  # All numbers => N,
1363  # except the ones surrounded by characters, e.g. l10n
1364  $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
1365  $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
1366 
1367  return $sql;
1368  }
1369 
1370  public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1371  $info = $this->fieldInfo( $table, $field );
1372 
1373  return (bool)$info;
1374  }
1375 
1376  public function indexExists( $table, $index, $fname = __METHOD__ ) {
1377  if ( !$this->tableExists( $table ) ) {
1378  return null;
1379  }
1380 
1381  $info = $this->indexInfo( $table, $index, $fname );
1382  if ( is_null( $info ) ) {
1383  return null;
1384  } else {
1385  return $info !== false;
1386  }
1387  }
1388 
1389  public function tableExists( $table, $fname = __METHOD__ ) {
1390  $table = $this->tableName( $table );
1391  $old = $this->ignoreErrors( true );
1392  $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
1393  $this->ignoreErrors( $old );
1394 
1395  return (bool)$res;
1396  }
1397 
1398  public function indexUnique( $table, $index ) {
1399  $indexInfo = $this->indexInfo( $table, $index );
1400 
1401  if ( !$indexInfo ) {
1402  return null;
1403  }
1404 
1405  return !$indexInfo[0]->Non_unique;
1406  }
1407 
1414  protected function makeInsertOptions( $options ) {
1415  return implode( ' ', $options );
1416  }
1417 
1418  public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
1419  # No rows to insert, easy just return now
1420  if ( !count( $a ) ) {
1421  return true;
1422  }
1423 
1424  $table = $this->tableName( $table );
1425 
1426  if ( !is_array( $options ) ) {
1427  $options = [ $options ];
1428  }
1429 
1430  $fh = null;
1431  if ( isset( $options['fileHandle'] ) ) {
1432  $fh = $options['fileHandle'];
1433  }
1434  $options = $this->makeInsertOptions( $options );
1435 
1436  if ( isset( $a[0] ) && is_array( $a[0] ) ) {
1437  $multi = true;
1438  $keys = array_keys( $a[0] );
1439  } else {
1440  $multi = false;
1441  $keys = array_keys( $a );
1442  }
1443 
1444  $sql = 'INSERT ' . $options .
1445  " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
1446 
1447  if ( $multi ) {
1448  $first = true;
1449  foreach ( $a as $row ) {
1450  if ( $first ) {
1451  $first = false;
1452  } else {
1453  $sql .= ',';
1454  }
1455  $sql .= '(' . $this->makeList( $row ) . ')';
1456  }
1457  } else {
1458  $sql .= '(' . $this->makeList( $a ) . ')';
1459  }
1460 
1461  if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
1462  return false;
1463  } elseif ( $fh !== null ) {
1464  return true;
1465  }
1466 
1467  return (bool)$this->query( $sql, $fname );
1468  }
1469 
1476  protected function makeUpdateOptionsArray( $options ) {
1477  if ( !is_array( $options ) ) {
1478  $options = [ $options ];
1479  }
1480 
1481  $opts = [];
1482 
1483  if ( in_array( 'LOW_PRIORITY', $options ) ) {
1484  $opts[] = $this->lowPriorityOption();
1485  }
1486 
1487  if ( in_array( 'IGNORE', $options ) ) {
1488  $opts[] = 'IGNORE';
1489  }
1490 
1491  return $opts;
1492  }
1493 
1500  protected function makeUpdateOptions( $options ) {
1501  $opts = $this->makeUpdateOptionsArray( $options );
1502 
1503  return implode( ' ', $opts );
1504  }
1505 
1506  function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
1507  $table = $this->tableName( $table );
1508  $opts = $this->makeUpdateOptions( $options );
1509  $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
1510 
1511  if ( $conds !== [] && $conds !== '*' ) {
1512  $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
1513  }
1514 
1515  return $this->query( $sql, $fname );
1516  }
1517 
1518  public function makeList( $a, $mode = LIST_COMMA ) {
1519  if ( !is_array( $a ) ) {
1520  throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
1521  }
1522 
1523  $first = true;
1524  $list = '';
1525 
1526  foreach ( $a as $field => $value ) {
1527  if ( !$first ) {
1528  if ( $mode == LIST_AND ) {
1529  $list .= ' AND ';
1530  } elseif ( $mode == LIST_OR ) {
1531  $list .= ' OR ';
1532  } else {
1533  $list .= ',';
1534  }
1535  } else {
1536  $first = false;
1537  }
1538 
1539  if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
1540  $list .= "($value)";
1541  } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
1542  $list .= "$value";
1543  } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
1544  // Remove null from array to be handled separately if found
1545  $includeNull = false;
1546  foreach ( array_keys( $value, null, true ) as $nullKey ) {
1547  $includeNull = true;
1548  unset( $value[$nullKey] );
1549  }
1550  if ( count( $value ) == 0 && !$includeNull ) {
1551  throw new MWException( __METHOD__ . ": empty input for field $field" );
1552  } elseif ( count( $value ) == 0 ) {
1553  // only check if $field is null
1554  $list .= "$field IS NULL";
1555  } else {
1556  // IN clause contains at least one valid element
1557  if ( $includeNull ) {
1558  // Group subconditions to ensure correct precedence
1559  $list .= '(';
1560  }
1561  if ( count( $value ) == 1 ) {
1562  // Special-case single values, as IN isn't terribly efficient
1563  // Don't necessarily assume the single key is 0; we don't
1564  // enforce linear numeric ordering on other arrays here.
1565  $value = array_values( $value )[0];
1566  $list .= $field . " = " . $this->addQuotes( $value );
1567  } else {
1568  $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
1569  }
1570  // if null present in array, append IS NULL
1571  if ( $includeNull ) {
1572  $list .= " OR $field IS NULL)";
1573  }
1574  }
1575  } elseif ( $value === null ) {
1576  if ( $mode == LIST_AND || $mode == LIST_OR ) {
1577  $list .= "$field IS ";
1578  } elseif ( $mode == LIST_SET ) {
1579  $list .= "$field = ";
1580  }
1581  $list .= 'NULL';
1582  } else {
1583  if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
1584  $list .= "$field = ";
1585  }
1586  $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
1587  }
1588  }
1589 
1590  return $list;
1591  }
1592 
1593  public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
1594  $conds = [];
1595 
1596  foreach ( $data as $base => $sub ) {
1597  if ( count( $sub ) ) {
1598  $conds[] = $this->makeList(
1599  [ $baseKey => $base, $subKey => array_keys( $sub ) ],
1600  LIST_AND );
1601  }
1602  }
1603 
1604  if ( $conds ) {
1605  return $this->makeList( $conds, LIST_OR );
1606  } else {
1607  // Nothing to search for...
1608  return false;
1609  }
1610  }
1611 
1620  public function aggregateValue( $valuedata, $valuename = 'value' ) {
1621  return $valuename;
1622  }
1623 
1624  public function bitNot( $field ) {
1625  return "(~$field)";
1626  }
1627 
1628  public function bitAnd( $fieldLeft, $fieldRight ) {
1629  return "($fieldLeft & $fieldRight)";
1630  }
1631 
1632  public function bitOr( $fieldLeft, $fieldRight ) {
1633  return "($fieldLeft | $fieldRight)";
1634  }
1635 
1636  public function buildConcat( $stringList ) {
1637  return 'CONCAT(' . implode( ',', $stringList ) . ')';
1638  }
1639 
1640  public function buildGroupConcatField(
1641  $delim, $table, $field, $conds = '', $join_conds = []
1642  ) {
1643  $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
1644 
1645  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1646  }
1647 
1648  public function selectDB( $db ) {
1649  # Stub. Shouldn't cause serious problems if it's not overridden, but
1650  # if your database engine supports a concept similar to MySQL's
1651  # databases you may as well.
1652  $this->mDBname = $db;
1653 
1654  return true;
1655  }
1656 
1657  public function getDBname() {
1658  return $this->mDBname;
1659  }
1660 
1661  public function getServer() {
1662  return $this->mServer;
1663  }
1664 
1684  public function tableName( $name, $format = 'quoted' ) {
1686  # Skip the entire process when we have a string quoted on both ends.
1687  # Note that we check the end so that we will still quote any use of
1688  # use of `database`.table. But won't break things if someone wants
1689  # to query a database table with a dot in the name.
1690  if ( $this->isQuotedIdentifier( $name ) ) {
1691  return $name;
1692  }
1693 
1694  # Lets test for any bits of text that should never show up in a table
1695  # name. Basically anything like JOIN or ON which are actually part of
1696  # SQL queries, but may end up inside of the table value to combine
1697  # sql. Such as how the API is doing.
1698  # Note that we use a whitespace test rather than a \b test to avoid
1699  # any remote case where a word like on may be inside of a table name
1700  # surrounded by symbols which may be considered word breaks.
1701  if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
1702  return $name;
1703  }
1704 
1705  # Split database and table into proper variables.
1706  # We reverse the explode so that database.table and table both output
1707  # the correct table.
1708  $dbDetails = explode( '.', $name, 3 );
1709  if ( count( $dbDetails ) == 3 ) {
1710  list( $database, $schema, $table ) = $dbDetails;
1711  # We don't want any prefix added in this case
1712  $prefix = '';
1713  } elseif ( count( $dbDetails ) == 2 ) {
1714  list( $database, $table ) = $dbDetails;
1715  # We don't want any prefix added in this case
1716  # In dbs that support it, $database may actually be the schema
1717  # but that doesn't affect any of the functionality here
1718  $prefix = '';
1719  $schema = null;
1720  } else {
1721  list( $table ) = $dbDetails;
1722  if ( $wgSharedDB !== null # We have a shared database
1723  && $this->mForeign == false # We're not working on a foreign database
1724  && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`'
1725  && in_array( $table, $wgSharedTables ) # A shared table is selected
1726  ) {
1727  $database = $wgSharedDB;
1728  $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema;
1729  $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
1730  } else {
1731  $database = null;
1732  $schema = $this->mSchema; # Default schema
1733  $prefix = $this->mTablePrefix; # Default prefix
1734  }
1735  }
1736 
1737  # Quote $table and apply the prefix if not quoted.
1738  # $tableName might be empty if this is called from Database::replaceVars()
1739  $tableName = "{$prefix}{$table}";
1740  if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) {
1741  $tableName = $this->addIdentifierQuotes( $tableName );
1742  }
1743 
1744  # Quote $schema and merge it with the table name if needed
1745  if ( strlen( $schema ) ) {
1746  if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) {
1747  $schema = $this->addIdentifierQuotes( $schema );
1748  }
1749  $tableName = $schema . '.' . $tableName;
1750  }
1751 
1752  # Quote $database and merge it with the table name if needed
1753  if ( $database !== null ) {
1754  if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
1755  $database = $this->addIdentifierQuotes( $database );
1756  }
1757  $tableName = $database . '.' . $tableName;
1758  }
1759 
1760  return $tableName;
1761  }
1762 
1774  public function tableNames() {
1775  $inArray = func_get_args();
1776  $retVal = [];
1777 
1778  foreach ( $inArray as $name ) {
1779  $retVal[$name] = $this->tableName( $name );
1780  }
1781 
1782  return $retVal;
1783  }
1784 
1796  public function tableNamesN() {
1797  $inArray = func_get_args();
1798  $retVal = [];
1799 
1800  foreach ( $inArray as $name ) {
1801  $retVal[] = $this->tableName( $name );
1802  }
1803 
1804  return $retVal;
1805  }
1806 
1815  public function tableNameWithAlias( $name, $alias = false ) {
1816  if ( !$alias || $alias == $name ) {
1817  return $this->tableName( $name );
1818  } else {
1819  return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
1820  }
1821  }
1822 
1829  public function tableNamesWithAlias( $tables ) {
1830  $retval = [];
1831  foreach ( $tables as $alias => $table ) {
1832  if ( is_numeric( $alias ) ) {
1833  $alias = $table;
1834  }
1835  $retval[] = $this->tableNameWithAlias( $table, $alias );
1836  }
1837 
1838  return $retval;
1839  }
1840 
1849  public function fieldNameWithAlias( $name, $alias = false ) {
1850  if ( !$alias || (string)$alias === (string)$name ) {
1851  return $name;
1852  } else {
1853  return $name . ' AS ' . $this->addIdentifierQuotes( $alias ); // PostgreSQL needs AS
1854  }
1855  }
1856 
1863  public function fieldNamesWithAlias( $fields ) {
1864  $retval = [];
1865  foreach ( $fields as $alias => $field ) {
1866  if ( is_numeric( $alias ) ) {
1867  $alias = $field;
1868  }
1869  $retval[] = $this->fieldNameWithAlias( $field, $alias );
1870  }
1871 
1872  return $retval;
1873  }
1874 
1884  protected function tableNamesWithUseIndexOrJOIN(
1885  $tables, $use_index = [], $join_conds = []
1886  ) {
1887  $ret = [];
1888  $retJOIN = [];
1889  $use_index = (array)$use_index;
1890  $join_conds = (array)$join_conds;
1891 
1892  foreach ( $tables as $alias => $table ) {
1893  if ( !is_string( $alias ) ) {
1894  // No alias? Set it equal to the table name
1895  $alias = $table;
1896  }
1897  // Is there a JOIN clause for this table?
1898  if ( isset( $join_conds[$alias] ) ) {
1899  list( $joinType, $conds ) = $join_conds[$alias];
1900  $tableClause = $joinType;
1901  $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
1902  if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
1903  $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
1904  if ( $use != '' ) {
1905  $tableClause .= ' ' . $use;
1906  }
1907  }
1908  $on = $this->makeList( (array)$conds, LIST_AND );
1909  if ( $on != '' ) {
1910  $tableClause .= ' ON (' . $on . ')';
1911  }
1912 
1913  $retJOIN[] = $tableClause;
1914  } elseif ( isset( $use_index[$alias] ) ) {
1915  // Is there an INDEX clause for this table?
1916  $tableClause = $this->tableNameWithAlias( $table, $alias );
1917  $tableClause .= ' ' . $this->useIndexClause(
1918  implode( ',', (array)$use_index[$alias] )
1919  );
1920 
1921  $ret[] = $tableClause;
1922  } else {
1923  $tableClause = $this->tableNameWithAlias( $table, $alias );
1924 
1925  $ret[] = $tableClause;
1926  }
1927  }
1928 
1929  // We can't separate explicit JOIN clauses with ',', use ' ' for those
1930  $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
1931  $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
1932 
1933  // Compile our final table clause
1934  return implode( ' ', [ $implicitJoins, $explicitJoins ] );
1935  }
1936 
1943  protected function indexName( $index ) {
1944  // Backwards-compatibility hack
1945  $renamed = [
1946  'ar_usertext_timestamp' => 'usertext_timestamp',
1947  'un_user_id' => 'user_id',
1948  'un_user_ip' => 'user_ip',
1949  ];
1950 
1951  if ( isset( $renamed[$index] ) ) {
1952  return $renamed[$index];
1953  } else {
1954  return $index;
1955  }
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  } else {
1965  # This will also quote numeric values. This should be harmless,
1966  # and protects against weird problems that occur when they really
1967  # _are_ strings such as article titles and string->number->string
1968  # conversion is not 1:1.
1969  return "'" . $this->strencode( $s ) . "'";
1970  }
1971  }
1972 
1982  public function addIdentifierQuotes( $s ) {
1983  return '"' . str_replace( '"', '""', $s ) . '"';
1984  }
1985 
1995  public function isQuotedIdentifier( $name ) {
1996  return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
1997  }
1998 
2003  protected function escapeLikeInternal( $s ) {
2004  return addcslashes( $s, '\%_' );
2005  }
2006 
2007  public function buildLike() {
2008  $params = func_get_args();
2009 
2010  if ( count( $params ) > 0 && is_array( $params[0] ) ) {
2011  $params = $params[0];
2012  }
2013 
2014  $s = '';
2015 
2016  foreach ( $params as $value ) {
2017  if ( $value instanceof LikeMatch ) {
2018  $s .= $value->toString();
2019  } else {
2020  $s .= $this->escapeLikeInternal( $value );
2021  }
2022  }
2023 
2024  return " LIKE {$this->addQuotes( $s )} ";
2025  }
2026 
2027  public function anyChar() {
2028  return new LikeMatch( '_' );
2029  }
2030 
2031  public function anyString() {
2032  return new LikeMatch( '%' );
2033  }
2034 
2035  public function nextSequenceValue( $seqName ) {
2036  return null;
2037  }
2038 
2049  public function useIndexClause( $index ) {
2050  return '';
2051  }
2052 
2053  public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
2054  $quotedTable = $this->tableName( $table );
2055 
2056  if ( count( $rows ) == 0 ) {
2057  return;
2058  }
2059 
2060  # Single row case
2061  if ( !is_array( reset( $rows ) ) ) {
2062  $rows = [ $rows ];
2063  }
2064 
2065  // @FXIME: this is not atomic, but a trx would break affectedRows()
2066  foreach ( $rows as $row ) {
2067  # Delete rows which collide
2068  if ( $uniqueIndexes ) {
2069  $sql = "DELETE FROM $quotedTable WHERE ";
2070  $first = true;
2071  foreach ( $uniqueIndexes as $index ) {
2072  if ( $first ) {
2073  $first = false;
2074  $sql .= '( ';
2075  } else {
2076  $sql .= ' ) OR ( ';
2077  }
2078  if ( is_array( $index ) ) {
2079  $first2 = true;
2080  foreach ( $index as $col ) {
2081  if ( $first2 ) {
2082  $first2 = false;
2083  } else {
2084  $sql .= ' AND ';
2085  }
2086  $sql .= $col . '=' . $this->addQuotes( $row[$col] );
2087  }
2088  } else {
2089  $sql .= $index . '=' . $this->addQuotes( $row[$index] );
2090  }
2091  }
2092  $sql .= ' )';
2093  $this->query( $sql, $fname );
2094  }
2095 
2096  # Now insert the row
2097  $this->insert( $table, $row, $fname );
2098  }
2099  }
2100 
2111  protected function nativeReplace( $table, $rows, $fname ) {
2112  $table = $this->tableName( $table );
2113 
2114  # Single row case
2115  if ( !is_array( reset( $rows ) ) ) {
2116  $rows = [ $rows ];
2117  }
2118 
2119  $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
2120  $first = true;
2121 
2122  foreach ( $rows as $row ) {
2123  if ( $first ) {
2124  $first = false;
2125  } else {
2126  $sql .= ',';
2127  }
2128 
2129  $sql .= '(' . $this->makeList( $row ) . ')';
2130  }
2131 
2132  return $this->query( $sql, $fname );
2133  }
2134 
2135  public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
2136  $fname = __METHOD__
2137  ) {
2138  if ( !count( $rows ) ) {
2139  return true; // nothing to do
2140  }
2141 
2142  if ( !is_array( reset( $rows ) ) ) {
2143  $rows = [ $rows ];
2144  }
2145 
2146  if ( count( $uniqueIndexes ) ) {
2147  $clauses = []; // list WHERE clauses that each identify a single row
2148  foreach ( $rows as $row ) {
2149  foreach ( $uniqueIndexes as $index ) {
2150  $index = is_array( $index ) ? $index : [ $index ]; // columns
2151  $rowKey = []; // unique key to this row
2152  foreach ( $index as $column ) {
2153  $rowKey[$column] = $row[$column];
2154  }
2155  $clauses[] = $this->makeList( $rowKey, LIST_AND );
2156  }
2157  }
2158  $where = [ $this->makeList( $clauses, LIST_OR ) ];
2159  } else {
2160  $where = false;
2161  }
2162 
2163  $useTrx = !$this->mTrxLevel;
2164  if ( $useTrx ) {
2165  $this->begin( $fname );
2166  }
2167  try {
2168  # Update any existing conflicting row(s)
2169  if ( $where !== false ) {
2170  $ok = $this->update( $table, $set, $where, $fname );
2171  } else {
2172  $ok = true;
2173  }
2174  # Now insert any non-conflicting row(s)
2175  $ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok;
2176  } catch ( Exception $e ) {
2177  if ( $useTrx ) {
2178  $this->rollback( $fname );
2179  }
2180  throw $e;
2181  }
2182  if ( $useTrx ) {
2183  $this->commit( $fname );
2184  }
2185 
2186  return $ok;
2187  }
2188 
2189  public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
2190  $fname = __METHOD__
2191  ) {
2192  if ( !$conds ) {
2193  throw new DBUnexpectedError( $this,
2194  'DatabaseBase::deleteJoin() called with empty $conds' );
2195  }
2196 
2197  $delTable = $this->tableName( $delTable );
2198  $joinTable = $this->tableName( $joinTable );
2199  $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
2200  if ( $conds != '*' ) {
2201  $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
2202  }
2203  $sql .= ')';
2204 
2205  $this->query( $sql, $fname );
2206  }
2207 
2215  public function textFieldSize( $table, $field ) {
2216  $table = $this->tableName( $table );
2217  $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
2218  $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
2219  $row = $this->fetchObject( $res );
2220 
2221  $m = [];
2222 
2223  if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
2224  $size = $m[1];
2225  } else {
2226  $size = -1;
2227  }
2228 
2229  return $size;
2230  }
2231 
2240  public function lowPriorityOption() {
2241  return '';
2242  }
2243 
2244  public function delete( $table, $conds, $fname = __METHOD__ ) {
2245  if ( !$conds ) {
2246  throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
2247  }
2248 
2249  $table = $this->tableName( $table );
2250  $sql = "DELETE FROM $table";
2251 
2252  if ( $conds != '*' ) {
2253  if ( is_array( $conds ) ) {
2254  $conds = $this->makeList( $conds, LIST_AND );
2255  }
2256  $sql .= ' WHERE ' . $conds;
2257  }
2258 
2259  return $this->query( $sql, $fname );
2260  }
2261 
2262  public function insertSelect( $destTable, $srcTable, $varMap, $conds,
2263  $fname = __METHOD__,
2264  $insertOptions = [], $selectOptions = []
2265  ) {
2266  $destTable = $this->tableName( $destTable );
2267 
2268  if ( !is_array( $insertOptions ) ) {
2269  $insertOptions = [ $insertOptions ];
2270  }
2271 
2272  $insertOptions = $this->makeInsertOptions( $insertOptions );
2273 
2274  if ( !is_array( $selectOptions ) ) {
2275  $selectOptions = [ $selectOptions ];
2276  }
2277 
2278  list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
2279 
2280  if ( is_array( $srcTable ) ) {
2281  $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
2282  } else {
2283  $srcTable = $this->tableName( $srcTable );
2284  }
2285 
2286  $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
2287  " SELECT $startOpts " . implode( ',', $varMap ) .
2288  " FROM $srcTable $useIndex ";
2289 
2290  if ( $conds != '*' ) {
2291  if ( is_array( $conds ) ) {
2292  $conds = $this->makeList( $conds, LIST_AND );
2293  }
2294  $sql .= " WHERE $conds";
2295  }
2296 
2297  $sql .= " $tailOpts";
2298 
2299  return $this->query( $sql, $fname );
2300  }
2301 
2321  public function limitResult( $sql, $limit, $offset = false ) {
2322  if ( !is_numeric( $limit ) ) {
2323  throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
2324  }
2325 
2326  return "$sql LIMIT "
2327  . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
2328  . "{$limit} ";
2329  }
2330 
2331  public function unionSupportsOrderAndLimit() {
2332  return true; // True for almost every DB supported
2333  }
2334 
2335  public function unionQueries( $sqls, $all ) {
2336  $glue = $all ? ') UNION ALL (' : ') UNION (';
2337 
2338  return '(' . implode( $glue, $sqls ) . ')';
2339  }
2340 
2341  public function conditional( $cond, $trueVal, $falseVal ) {
2342  if ( is_array( $cond ) ) {
2343  $cond = $this->makeList( $cond, LIST_AND );
2344  }
2345 
2346  return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
2347  }
2348 
2349  public function strreplace( $orig, $old, $new ) {
2350  return "REPLACE({$orig}, {$old}, {$new})";
2351  }
2352 
2353  public function getServerUptime() {
2354  return 0;
2355  }
2356 
2357  public function wasDeadlock() {
2358  return false;
2359  }
2360 
2361  public function wasLockTimeout() {
2362  return false;
2363  }
2364 
2365  public function wasErrorReissuable() {
2366  return false;
2367  }
2368 
2369  public function wasReadOnlyError() {
2370  return false;
2371  }
2372 
2380  public function wasConnectionError( $errno ) {
2381  return false;
2382  }
2383 
2403  public function deadlockLoop() {
2404  $args = func_get_args();
2405  $function = array_shift( $args );
2406  $tries = self::DEADLOCK_TRIES;
2407 
2408  $this->begin( __METHOD__ );
2409 
2410  $retVal = null;
2412  $e = null;
2413  do {
2414  try {
2415  $retVal = call_user_func_array( $function, $args );
2416  break;
2417  } catch ( DBQueryError $e ) {
2418  if ( $this->wasDeadlock() ) {
2419  // Retry after a randomized delay
2420  usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
2421  } else {
2422  // Throw the error back up
2423  throw $e;
2424  }
2425  }
2426  } while ( --$tries > 0 );
2427 
2428  if ( $tries <= 0 ) {
2429  // Too many deadlocks; give up
2430  $this->rollback( __METHOD__ );
2431  throw $e;
2432  } else {
2433  $this->commit( __METHOD__ );
2434 
2435  return $retVal;
2436  }
2437  }
2438 
2439  public function masterPosWait( DBMasterPos $pos, $timeout ) {
2440  # Real waits are implemented in the subclass.
2441  return 0;
2442  }
2443 
2444  public function getSlavePos() {
2445  # Stub
2446  return false;
2447  }
2448 
2449  public function getMasterPos() {
2450  # Stub
2451  return false;
2452  }
2453 
2454  final public function onTransactionIdle( $callback ) {
2455  $this->mTrxIdleCallbacks[] = [ $callback, wfGetCaller() ];
2456  if ( !$this->mTrxLevel ) {
2458  }
2459  }
2460 
2461  final public function onTransactionPreCommitOrIdle( $callback ) {
2462  if ( $this->mTrxLevel ) {
2463  $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
2464  } else {
2465  $this->onTransactionIdle( $callback ); // this will trigger immediately
2466  }
2467  }
2468 
2474  protected function runOnTransactionIdleCallbacks() {
2475  $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
2476 
2477  $e = $ePrior = null; // last exception
2478  do { // callbacks may add callbacks :)
2479  $callbacks = $this->mTrxIdleCallbacks;
2480  $this->mTrxIdleCallbacks = []; // recursion guard
2481  foreach ( $callbacks as $callback ) {
2482  try {
2483  list( $phpCallback ) = $callback;
2484  $this->clearFlag( DBO_TRX ); // make each query its own transaction
2485  call_user_func( $phpCallback );
2486  if ( $autoTrx ) {
2487  $this->setFlag( DBO_TRX ); // restore automatic begin()
2488  } else {
2489  $this->clearFlag( DBO_TRX ); // restore auto-commit
2490  }
2491  } catch ( Exception $e ) {
2492  if ( $ePrior ) {
2494  }
2495  $ePrior = $e;
2496  // Some callbacks may use startAtomic/endAtomic, so make sure
2497  // their transactions are ended so other callbacks don't fail
2498  if ( $this->trxLevel() ) {
2499  $this->rollback( __METHOD__ );
2500  }
2501  }
2502  }
2503  } while ( count( $this->mTrxIdleCallbacks ) );
2504 
2505  if ( $e instanceof Exception ) {
2506  throw $e; // re-throw any last exception
2507  }
2508  }
2509 
2515  protected function runOnTransactionPreCommitCallbacks() {
2516  $e = $ePrior = null; // last exception
2517  do { // callbacks may add callbacks :)
2518  $callbacks = $this->mTrxPreCommitCallbacks;
2519  $this->mTrxPreCommitCallbacks = []; // recursion guard
2520  foreach ( $callbacks as $callback ) {
2521  try {
2522  list( $phpCallback ) = $callback;
2523  call_user_func( $phpCallback );
2524  } catch ( Exception $e ) {
2525  if ( $ePrior ) {
2527  }
2528  $ePrior = $e;
2529  }
2530  }
2531  } while ( count( $this->mTrxPreCommitCallbacks ) );
2532 
2533  if ( $e instanceof Exception ) {
2534  throw $e; // re-throw any last exception
2535  }
2536  }
2537 
2538  final public function startAtomic( $fname = __METHOD__ ) {
2539  if ( !$this->mTrxLevel ) {
2540  $this->begin( $fname );
2541  $this->mTrxAutomatic = true;
2542  // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
2543  // in all changes being in one transaction to keep requests transactional.
2544  if ( !$this->getFlag( DBO_TRX ) ) {
2545  $this->mTrxAutomaticAtomic = true;
2546  }
2547  }
2548 
2549  $this->mTrxAtomicLevels[] = $fname;
2550  }
2551 
2552  final public function endAtomic( $fname = __METHOD__ ) {
2553  if ( !$this->mTrxLevel ) {
2554  throw new DBUnexpectedError( $this, 'No atomic transaction is open.' );
2555  }
2556  if ( !$this->mTrxAtomicLevels ||
2557  array_pop( $this->mTrxAtomicLevels ) !== $fname
2558  ) {
2559  throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' );
2560  }
2561 
2562  if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) {
2563  $this->commit( $fname, 'flush' );
2564  }
2565  }
2566 
2567  final public function doAtomicSection( $fname, $callback ) {
2568  if ( !is_callable( $callback ) ) {
2569  throw new UnexpectedValueException( "Invalid callback." );
2570  };
2571 
2572  $this->startAtomic( $fname );
2573  try {
2574  call_user_func_array( $callback, [ $this, $fname ] );
2575  } catch ( Exception $e ) {
2576  $this->rollback( $fname );
2577  throw $e;
2578  }
2579  $this->endAtomic( $fname );
2580  }
2581 
2582  final public function begin( $fname = __METHOD__ ) {
2583  if ( $this->mTrxLevel ) { // implicit commit
2584  if ( $this->mTrxAtomicLevels ) {
2585  // If the current transaction was an automatic atomic one, then we definitely have
2586  // a problem. Same if there is any unclosed atomic level.
2587  $levels = implode( ', ', $this->mTrxAtomicLevels );
2588  throw new DBUnexpectedError(
2589  $this,
2590  "Got explicit BEGIN from $fname while atomic section(s) $levels are open."
2591  );
2592  } elseif ( !$this->mTrxAutomatic ) {
2593  // We want to warn about inadvertently nested begin/commit pairs, but not about
2594  // auto-committing implicit transactions that were started by query() via DBO_TRX
2595  $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
2596  " performing implicit commit!";
2597  wfWarn( $msg );
2598  wfLogDBError( $msg,
2599  $this->getLogContext( [
2600  'method' => __METHOD__,
2601  'fname' => $fname,
2602  ] )
2603  );
2604  } else {
2605  // if the transaction was automatic and has done write operations
2606  if ( $this->mTrxDoneWrites ) {
2607  wfDebug( "$fname: Automatic transaction with writes in progress" .
2608  " (from {$this->mTrxFname}), performing implicit commit!\n"
2609  );
2610  }
2611  }
2612 
2614  $writeTime = $this->pendingWriteQueryDuration();
2615  $this->doCommit( $fname );
2616  if ( $this->mTrxDoneWrites ) {
2617  $this->mDoneWrites = microtime( true );
2618  $this->getTransactionProfiler()->transactionWritingOut(
2619  $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
2620  }
2622  }
2623 
2624  # Avoid fatals if close() was called
2625  $this->assertOpen();
2626 
2627  $this->doBegin( $fname );
2628  $this->mTrxTimestamp = microtime( true );
2629  $this->mTrxFname = $fname;
2630  $this->mTrxDoneWrites = false;
2631  $this->mTrxAutomatic = false;
2632  $this->mTrxAutomaticAtomic = false;
2633  $this->mTrxAtomicLevels = [];
2634  $this->mTrxIdleCallbacks = [];
2635  $this->mTrxPreCommitCallbacks = [];
2636  $this->mTrxShortId = wfRandomString( 12 );
2637  $this->mTrxWriteDuration = 0.0;
2638  $this->mTrxWriteCallers = [];
2639  // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
2640  // Get an estimate of the slave lag before then, treating estimate staleness
2641  // as lag itself just to be safe
2642  $status = $this->getApproximateLagStatus();
2643  $this->mTrxSlaveLag = $status['lag'] + ( microtime( true ) - $status['since'] );
2644  }
2645 
2652  protected function doBegin( $fname ) {
2653  $this->query( 'BEGIN', $fname );
2654  $this->mTrxLevel = 1;
2655  }
2656 
2657  final public function commit( $fname = __METHOD__, $flush = '' ) {
2658  if ( $this->mTrxLevel && $this->mTrxAtomicLevels ) {
2659  // There are still atomic sections open. This cannot be ignored
2660  $levels = implode( ', ', $this->mTrxAtomicLevels );
2661  throw new DBUnexpectedError(
2662  $this,
2663  "Got COMMIT while atomic sections $levels are still open"
2664  );
2665  }
2666 
2667  if ( $flush === 'flush' ) {
2668  if ( !$this->mTrxLevel ) {
2669  return; // nothing to do
2670  } elseif ( !$this->mTrxAutomatic ) {
2671  throw new DBUnexpectedError(
2672  $this,
2673  "$fname: Flushing an explicit transaction, getting out of sync!"
2674  );
2675  }
2676  } else {
2677  if ( !$this->mTrxLevel ) {
2678  wfWarn( "$fname: No transaction to commit, something got out of sync!" );
2679  return; // nothing to do
2680  } elseif ( $this->mTrxAutomatic ) {
2681  wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
2682  }
2683  }
2684 
2685  # Avoid fatals if close() was called
2686  $this->assertOpen();
2687 
2689  $writeTime = $this->pendingWriteQueryDuration();
2690  $this->doCommit( $fname );
2691  if ( $this->mTrxDoneWrites ) {
2692  $this->mDoneWrites = microtime( true );
2693  $this->getTransactionProfiler()->transactionWritingOut(
2694  $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
2695  }
2697  }
2698 
2705  protected function doCommit( $fname ) {
2706  if ( $this->mTrxLevel ) {
2707  $this->query( 'COMMIT', $fname );
2708  $this->mTrxLevel = 0;
2709  }
2710  }
2711 
2712  final public function rollback( $fname = __METHOD__, $flush = '' ) {
2713  if ( $flush !== 'flush' ) {
2714  if ( !$this->mTrxLevel ) {
2715  wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
2716  return; // nothing to do
2717  }
2718  } else {
2719  if ( !$this->mTrxLevel ) {
2720  return; // nothing to do
2721  }
2722  }
2723 
2724  # Avoid fatals if close() was called
2725  $this->assertOpen();
2726 
2727  $this->doRollback( $fname );
2728  $this->mTrxIdleCallbacks = []; // cancel
2729  $this->mTrxPreCommitCallbacks = []; // cancel
2730  $this->mTrxAtomicLevels = [];
2731  if ( $this->mTrxDoneWrites ) {
2732  $this->getTransactionProfiler()->transactionWritingOut(
2733  $this->mServer, $this->mDBname, $this->mTrxShortId );
2734  }
2735  }
2736 
2743  protected function doRollback( $fname ) {
2744  if ( $this->mTrxLevel ) {
2745  $this->query( 'ROLLBACK', $fname, true );
2746  $this->mTrxLevel = 0;
2747  }
2748  }
2749 
2765  public function duplicateTableStructure( $oldName, $newName, $temporary = false,
2766  $fname = __METHOD__
2767  ) {
2768  throw new MWException(
2769  'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
2770  }
2771 
2772  function listTables( $prefix = null, $fname = __METHOD__ ) {
2773  throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
2774  }
2775 
2780  final public function clearViewsCache() {
2781  $this->allViews = null;
2782  }
2783 
2796  public function listViews( $prefix = null, $fname = __METHOD__ ) {
2797  throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' );
2798  }
2799 
2808  public function isView( $name ) {
2809  throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
2810  }
2811 
2812  public function timestamp( $ts = 0 ) {
2813  return wfTimestamp( TS_MW, $ts );
2814  }
2815 
2816  public function timestampOrNull( $ts = null ) {
2817  if ( is_null( $ts ) ) {
2818  return null;
2819  } else {
2820  return $this->timestamp( $ts );
2821  }
2822  }
2823 
2837  protected function resultObject( $result ) {
2838  if ( !$result ) {
2839  return false;
2840  } elseif ( $result instanceof ResultWrapper ) {
2841  return $result;
2842  } elseif ( $result === true ) {
2843  // Successful write query
2844  return $result;
2845  } else {
2846  return new ResultWrapper( $this, $result );
2847  }
2848  }
2849 
2850  public function ping() {
2851  # Stub. Not essential to override.
2852  return true;
2853  }
2854 
2855  public function getSessionLagStatus() {
2856  return $this->getTransactionLagStatus() ?: $this->getApproximateLagStatus();
2857  }
2858 
2870  public function getTransactionLagStatus() {
2871  return $this->mTrxLevel
2872  ? [ 'lag' => $this->mTrxSlaveLag, 'since' => $this->trxTimestamp() ]
2873  : null;
2874  }
2875 
2882  public function getApproximateLagStatus() {
2883  return [
2884  'lag' => $this->getLBInfo( 'slave' ) ? $this->getLag() : 0,
2885  'since' => microtime( true )
2886  ];
2887  }
2888 
2907  public static function getCacheSetOptions( IDatabase $db1 ) {
2908  $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
2909  foreach ( func_get_args() as $db ) {
2911  $status = $db->getSessionLagStatus();
2912  if ( $status['lag'] === false ) {
2913  $res['lag'] = false;
2914  } elseif ( $res['lag'] !== false ) {
2915  $res['lag'] = max( $res['lag'], $status['lag'] );
2916  }
2917  $res['since'] = min( $res['since'], $status['since'] );
2918  $res['pending'] = $res['pending'] ?: $db->writesPending();
2919  }
2920 
2921  return $res;
2922  }
2923 
2924  public function getLag() {
2925  return 0;
2926  }
2927 
2928  function maxListLen() {
2929  return 0;
2930  }
2931 
2932  public function encodeBlob( $b ) {
2933  return $b;
2934  }
2935 
2936  public function decodeBlob( $b ) {
2937  if ( $b instanceof Blob ) {
2938  $b = $b->fetch();
2939  }
2940  return $b;
2941  }
2942 
2943  public function setSessionOptions( array $options ) {
2944  }
2945 
2962  public function sourceFile(
2963  $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
2964  ) {
2965  MediaWiki\suppressWarnings();
2966  $fp = fopen( $filename, 'r' );
2967  MediaWiki\restoreWarnings();
2968 
2969  if ( false === $fp ) {
2970  throw new MWException( "Could not open \"{$filename}\".\n" );
2971  }
2972 
2973  if ( !$fname ) {
2974  $fname = __METHOD__ . "( $filename )";
2975  }
2976 
2977  try {
2978  $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
2979  } catch ( Exception $e ) {
2980  fclose( $fp );
2981  throw $e;
2982  }
2983 
2984  fclose( $fp );
2985 
2986  return $error;
2987  }
2988 
2997  public function patchPath( $patch ) {
2998  global $IP;
2999 
3000  $dbType = $this->getType();
3001  if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
3002  return "$IP/maintenance/$dbType/archives/$patch";
3003  } else {
3004  return "$IP/maintenance/archives/$patch";
3005  }
3006  }
3007 
3008  public function setSchemaVars( $vars ) {
3009  $this->mSchemaVars = $vars;
3010  }
3011 
3025  public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
3026  $fname = __METHOD__, $inputCallback = false
3027  ) {
3028  $cmd = '';
3029 
3030  while ( !feof( $fp ) ) {
3031  if ( $lineCallback ) {
3032  call_user_func( $lineCallback );
3033  }
3034 
3035  $line = trim( fgets( $fp ) );
3036 
3037  if ( $line == '' ) {
3038  continue;
3039  }
3040 
3041  if ( '-' == $line[0] && '-' == $line[1] ) {
3042  continue;
3043  }
3044 
3045  if ( $cmd != '' ) {
3046  $cmd .= ' ';
3047  }
3048 
3049  $done = $this->streamStatementEnd( $cmd, $line );
3050 
3051  $cmd .= "$line\n";
3052 
3053  if ( $done || feof( $fp ) ) {
3054  $cmd = $this->replaceVars( $cmd );
3055 
3056  if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
3057  $res = $this->query( $cmd, $fname );
3058 
3059  if ( $resultCallback ) {
3060  call_user_func( $resultCallback, $res, $this );
3061  }
3062 
3063  if ( false === $res ) {
3064  $err = $this->lastError();
3065 
3066  return "Query \"{$cmd}\" failed with error code \"$err\".\n";
3067  }
3068  }
3069  $cmd = '';
3070  }
3071  }
3072 
3073  return true;
3074  }
3075 
3083  public function streamStatementEnd( &$sql, &$newLine ) {
3084  if ( $this->delimiter ) {
3085  $prev = $newLine;
3086  $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
3087  if ( $newLine != $prev ) {
3088  return true;
3089  }
3090  }
3091 
3092  return false;
3093  }
3094 
3115  protected function replaceVars( $ins ) {
3116  $vars = $this->getSchemaVars();
3117  return preg_replace_callback(
3118  '!
3119  /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
3120  \'\{\$ (\w+) }\' | # 3. addQuotes
3121  `\{\$ (\w+) }` | # 4. addIdentifierQuotes
3122  /\*\$ (\w+) \*/ # 5. leave unencoded
3123  !x',
3124  function ( $m ) use ( $vars ) {
3125  // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
3126  // check for both nonexistent keys *and* the empty string.
3127  if ( isset( $m[1] ) && $m[1] !== '' ) {
3128  if ( $m[1] === 'i' ) {
3129  return $this->indexName( $m[2] );
3130  } else {
3131  return $this->tableName( $m[2] );
3132  }
3133  } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
3134  return $this->addQuotes( $vars[$m[3]] );
3135  } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
3136  return $this->addIdentifierQuotes( $vars[$m[4]] );
3137  } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
3138  return $vars[$m[5]];
3139  } else {
3140  return $m[0];
3141  }
3142  },
3143  $ins
3144  );
3145  }
3146 
3153  protected function getSchemaVars() {
3154  if ( $this->mSchemaVars ) {
3155  return $this->mSchemaVars;
3156  } else {
3157  return $this->getDefaultSchemaVars();
3158  }
3159  }
3160 
3169  protected function getDefaultSchemaVars() {
3170  return [];
3171  }
3172 
3173  public function lockIsFree( $lockName, $method ) {
3174  return true;
3175  }
3176 
3177  public function lock( $lockName, $method, $timeout = 5 ) {
3178  $this->mNamedLocksHeld[$lockName] = 1;
3179 
3180  return true;
3181  }
3182 
3183  public function unlock( $lockName, $method ) {
3184  unset( $this->mNamedLocksHeld[$lockName] );
3185 
3186  return true;
3187  }
3188 
3189  public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
3190  if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
3191  return null;
3192  }
3193 
3194  $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
3195  $this->commit( __METHOD__, 'flush' );
3196  $this->unlock( $lockKey, $fname );
3197  } );
3198 
3199  $this->commit( __METHOD__, 'flush' );
3200 
3201  return $unlocker;
3202  }
3203 
3204  public function namedLocksEnqueue() {
3205  return false;
3206  }
3207 
3217  public function lockTables( $read, $write, $method, $lowPriority = true ) {
3218  return true;
3219  }
3220 
3227  public function unlockTables( $method ) {
3228  return true;
3229  }
3230 
3238  public function dropTable( $tableName, $fName = __METHOD__ ) {
3239  if ( !$this->tableExists( $tableName, $fName ) ) {
3240  return false;
3241  }
3242  $sql = "DROP TABLE " . $this->tableName( $tableName );
3243  if ( $this->cascadingDeletes() ) {
3244  $sql .= " CASCADE";
3245  }
3246 
3247  return $this->query( $sql, $fName );
3248  }
3249 
3256  public function getSearchEngine() {
3257  return 'SearchEngineDummy';
3258  }
3259 
3260  public function getInfinity() {
3261  return 'infinity';
3262  }
3263 
3264  public function encodeExpiry( $expiry ) {
3265  return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
3266  ? $this->getInfinity()
3267  : $this->timestamp( $expiry );
3268  }
3269 
3270  public function decodeExpiry( $expiry, $format = TS_MW ) {
3271  return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
3272  ? 'infinity'
3273  : wfTimestamp( $format, $expiry );
3274  }
3275 
3276  public function setBigSelects( $value = true ) {
3277  // no-op
3278  }
3279 
3280  public function isReadOnly() {
3281  return ( $this->getReadOnlyReason() !== false );
3282  }
3283 
3287  protected function getReadOnlyReason() {
3288  $reason = $this->getLBInfo( 'readOnlyReason' );
3289 
3290  return is_string( $reason ) ? $reason : false;
3291  }
3292 
3297  public function __toString() {
3298  return (string)$this->mConn;
3299  }
3300 
3304  public function __destruct() {
3305  if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
3306  trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
3307  }
3308  if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) {
3309  $callers = [];
3310  foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) {
3311  $callers[] = $callbackInfo[1];
3312  }
3313  $callers = implode( ', ', $callers );
3314  trigger_error( "DB transaction callbacks still pending (from $callers)." );
3315  }
3316  }
3317 }
3318 
3322 abstract class Database extends DatabaseBase {
3323  // B/C until nothing type hints for DatabaseBase
3324  // @TODO: finish renaming DatabaseBase => Database
3325 }
doneWrites()
Returns true if the connection may have been used for write queries.
Definition: Database.php:378
select($table, $vars, $conds= '', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
Definition: Database.php:1230
indexName($index)
Get the name of an index in a given table.
Definition: Database.php:1943
lowPriorityOption()
A string to insert into queries to show that they're low-priority, like MySQL's LOW_PRIORITY.
Definition: Database.php:2240
ping()
Ping the server and try to reconnect if it there is no connection.
Definition: Database.php:2850
setLBInfo($name, $value=null)
Set the LB info array, or a member of it.
Definition: Database.php:264
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
TransactionProfiler $trxProfiler
Definition: Database.php:177
const DEADLOCK_DELAY_MAX
Maximum time to wait before retry.
Definition: Database.php:40
maxListLen()
Return the maximum number of items allowed in a list, or 0 for unlimited.
Definition: Database.php:2928
the array() calling protocol came about after MediaWiki 1.4rc1.
Utility classThis allows us to distinguish a blob from a normal string and an array of strings...
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the database
Definition: design.txt:12
float $mTrxSlaveLag
Lag estimate at the time of BEGIN.
Definition: Database.php:103
tableName($name, $format= 'quoted')
Format a table name ready for use in constructing an SQL query.
Definition: Database.php:1684
getScopedLockAndFlush($lockKey, $fname, $timeout)
Acquire a named lock, flush any transaction, and return an RAII style unlocker object.
Definition: Database.php:3189
bitOr($fieldLeft, $fieldRight)
Definition: Database.php:1632
$IP
Definition: WebStart.php:58
aggregateValue($valuedata, $valuename= 'value')
Return aggregated value alias.
Definition: Database.php:1620
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
patchPath($patch)
Get the full path of a patch file.
Definition: Database.php:2997
setSessionOptions(array $options)
Override database's default behavior.
Definition: Database.php:2943
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:1932
encodeExpiry($expiry)
Encode an expiry time into the DBMS dependent format.
Definition: Database.php:3264
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:1798
const LIST_NAMES
Definition: Defines.php:195
__destruct()
Run a few simple sanity checks.
Definition: Database.php:3304
static instance()
Singleton.
Definition: Profiler.php:60
decodeExpiry($expiry, $format=TS_MW)
Decode an expiry time into a DBMS independent format.
Definition: Database.php:3270
estimateRowCount($table, $vars= '*', $conds= '', $fname=__METHOD__, $options=[])
Estimate the number of rows in dataset.
Definition: Database.php:1308
clearFlag($flag)
Clear a flag for this connection.
Definition: Database.php:412
makeOrderBy($options)
Returns an optional ORDER BY.
Definition: Database.php:1217
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
setTransactionProfiler(TransactionProfiler $profiler)
Definition: Database.php:306
open($server, $user, $password, $dbName)
Open a connection to the database.
resource $mConn
Database connection.
Definition: Database.php:52
listViews($prefix=null, $fname=__METHOD__)
Lists all the VIEWs in the database.
Definition: Database.php:2796
wfBacktrace($raw=null)
Get a debug backtrace as a string.
$wgDBmwschema
Mediawiki schema.
wfLogDBError($text, array $context=[])
Log for database errors.
$value
const DBO_TRX
Definition: Defines.php:33
$wgSharedTables
getServerVersion()
A string describing the current software version, like from mysql_get_server_info().
Stub profiler that does nothing.
onTransactionIdle($callback)
Run an anonymous function as soon as there is no transaction pending.
Definition: Database.php:2454
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2548
makeList($a, $mode=LIST_COMMA)
Makes an encoded list of strings from an array.
Definition: Database.php:1518
lastError()
Get a description of the last error.
doRollback($fname)
Issues the ROLLBACK command to the database server.
Definition: Database.php:2743
getProperty($name)
General read-only accessor.
Definition: Database.php:420
dropTable($tableName, $fName=__METHOD__)
Delete a table.
Definition: Database.php:3238
An object representing a master or slave position in a replicated setup.
functionalIndexes()
Returns true if this database can use functional indexes.
Definition: Database.php:370
const DBO_DEBUG
Definition: Defines.php:30
sourceFile($filename, $lineCallback=false, $resultCallback=false, $fname=false, $inputCallback=false)
Read and execute SQL commands from a file.
Definition: Database.php:2962
getDefaultSchemaVars()
Get schema variables to use if none have been set via setSchemaVars().
Definition: Database.php:3169
getTransactionProfiler()
Definition: Database.php:294
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
lockIsFree($lockName, $method)
Check to see if a named lock is available (non-blocking)
Definition: Database.php:3173
doQuery($sql)
The DBMS-dependent part of query()
fieldInfo($table, $field)
mysql_fetch_field() wrapper Returns false if the field doesn't exist
cleanupTriggers()
Returns true if this database supports (and uses) triggers (e.g.
Definition: Database.php:324
getSchemaPath()
Return a path to the DBMS-specific schema file, otherwise default to tables.sql.
Definition: Database.php:455
wasReadOnlyError()
Determines if the last failure was due to the database being read-only.
Definition: Database.php:2369
encodeBlob($b)
Some DBMSs have a special format for inserting into blob fields, they don't allow simple quoted strin...
Definition: Database.php:2932
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:2765
pendingWriteCallers()
Get the list of method names that did write queries for this transaction.
Definition: Database.php:400
restoreErrorHandler()
Definition: Database.php:654
tableNamesWithUseIndexOrJOIN($tables, $use_index=[], $join_conds=[])
Get the aliased table name clause for a FROM clause which might have a JOIN and/or USE INDEX clause...
Definition: Database.php:1884
listTables($prefix=null, $fname=__METHOD__)
List all tables on the database.
Definition: Database.php:2772
selectFieldValues($table, $var, $cond= '', $fname=__METHOD__, $options=[], $join_conds=[])
Definition: Database.php:1072
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
lastErrno()
Get the last error number.
wfRandomString($length=32)
Get a random string containing a number of pseudo-random hex characters.
const DEADLOCK_TRIES
Number of times to re-try an operation in case of deadlock.
Definition: Database.php:34
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition: hooks.txt:965
close()
Closes a database connection.
Definition: Database.php:694
getSqlFilePath($filename)
Return a path to the DBMS-specific SQL file if it exists, otherwise default SQL file.
Definition: Database.php:439
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:1237
indexExists($table, $index, $fname=__METHOD__)
Determines whether an index exists Usually throws a DBQueryError on failure If errors are explicitly ...
Definition: Database.php:1376
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
tablePrefix($prefix=null)
Get/set the table prefix.
Definition: Database.php:235
runOnTransactionPreCommitCallbacks()
Actually any "on transaction pre-commit" callbacks.
Definition: Database.php:2515
useIndexClause($index)
USE INDEX clause.
Definition: Database.php:2049
update($table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
Definition: Database.php:1506
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':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:1796
if($line===false) $args
Definition: cdb.php:64
affectedRows()
Get the number of rows affected by the last write query.
strreplace($orig, $old, $new)
Returns a comand for str_replace function in SQL query.
Definition: Database.php:2349
trxTimestamp()
Get the UNIX timestamp of the time that the transaction was established.
Definition: Database.php:231
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Class for asserting that a callback happens when an dummy object leaves scope.
timestamp($ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:2812
wfDebugLog($logGroup, $text, $dest= 'all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
getSlavePos()
Get the replication position of this slave.
Definition: Database.php:2444
global $wgCommandLineMode
Definition: Setup.php:513
implicitGroupby()
Returns true if this database does an implicit sort when doing GROUP BY.
Definition: Database.php:347
bitAnd($fieldLeft, $fieldRight)
Definition: Database.php:1628
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:223
isQuotedIdentifier($name)
Returns if the given identifier looks quoted or not according to the database convention for quoting ...
Definition: Database.php:1995
anyString()
Returns a token for buildLike() that denotes a '' to be used in a LIKE query.
Definition: Database.php:2031
deadlockLoop()
Perform a deadlock-prone transaction.
Definition: Database.php:2403
fillPreparedArg($matches)
preg_callback func for fillPrepared() The arguments should be in $this->preparedArgs and must not be ...
Definition: Database.php:1011
wfSetBit(&$dest, $bit, $state=true)
As for wfSetVar except setting a bit.
doBegin($fname)
Issues the BEGIN command to the database server.
Definition: Database.php:2652
unlock($lockName, $method)
Release a lock.
Definition: Database.php:3183
doCommit($fname)
Issues the COMMIT command to the database server.
Definition: Database.php:2705
buildLike()
LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match conta...
Definition: Database.php:2007
strictIPs()
Returns true if this database is strict about what can be put into an IP field.
Definition: Database.php:334
tableExists($table, $fname=__METHOD__)
Query whether a given table exists.
Definition: Database.php:1389
unionSupportsOrderAndLimit()
Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries within th...
Definition: Database.php:2331
bufferResults($buffer=null)
Turns buffering of SQL result sets on (true) or off (false).
Definition: Database.php:203
const LIST_AND
Definition: Defines.php:193
makeUpdateOptionsArray($options)
Make UPDATE options array for DatabaseBase::makeUpdateOptions.
Definition: Database.php:1476
getSchemaVars()
Get schema variables.
Definition: Database.php:3153
trxLevel()
Gets the current transaction level.
Definition: Database.php:227
wfWarn($msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
strencode($s)
Wrapper for addslashes()
Helper class that detects high-contention DB queries via profiling calls.
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:1004
getUpdateKeysPath()
Return a path to the DBMS-specific update key file, otherwise default to update-keys.sql.
Definition: Database.php:465
getDBname()
Get the current DB name.
Definition: Database.php:1657
fetchObject($res)
Fetch the next row from the given result object, in object form.
const LIST_COMMA
Definition: Defines.php:192
freePrepared($prepared)
Free a prepared query, generated by prepare().
Definition: Database.php:964
unlockTables($method)
Unlock specific tables.
Definition: Database.php:3227
$res
Definition: database.txt:21
endAtomic($fname=__METHOD__)
Ends an atomic section of SQL statements.
Definition: Database.php:2552
namedLocksEnqueue()
Check to see if a named lock used by lock() use blocking queues.
Definition: Database.php:3204
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
Definition: Database.php:351
assertOpen()
Make sure isOpen() returns true as a sanity check.
Definition: Database.php:723
const DBO_NOBUFFER
Definition: Defines.php:31
setLazyMasterHandle(IDatabase $conn)
Set a lazy-connecting DB handle to the master DB (for replication status purposes) ...
Definition: Database.php:278
getFlag($flag)
Returns a boolean whether the flag $flag is set for this connection.
Definition: Database.php:416
$params
isTransactableQuery($sql)
Determine whether a SQL statement is sensitive to isolation level.
Definition: Database.php:775
insert($table, $a, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
Definition: Database.php:1418
static queryTime($id)
Calculates how long a query took.
Definition: MWDebug.php:391
$wgSharedDB
Shared database for multiple wikis.
static factory($dbType, $p=[])
Given a DB type, construct the name of the appropriate child class of DatabaseBase.
Definition: Database.php:580
conditional($cond, $trueVal, $falseVal)
Returns an SQL expression for a simple conditional.
Definition: Database.php:2341
$buffer
debug($debug=null)
Boolean, controls output of large amounts of debug information.
Definition: Database.php:199
callable[] $mTrxPreCommitCallbacks
Definition: Database.php:58
IDatabase null $lazyMasterHandle
Lazy handle to the master DB this server replicates from.
Definition: Database.php:162
getSearchEngine()
Get search engine class.
Definition: Database.php:3256
addQuotes($s)
Adds quotes and backslashes.
Definition: Database.php:1958
addIdentifierQuotes($s)
Quotes an identifier using backticks or "double quotes" depending on the database type...
Definition: Database.php:1982
bitNot($field)
Definition: Database.php:1624
Used by DatabaseBase::buildLike() to represent characters that have special meaning in SQL LIKE claus...
searchableIPs()
Returns true if this database can do a native search on IP columns e.g.
Definition: Database.php:361
onTransactionPreCommitOrIdle($callback)
Run an anonymous function before the current transaction commits or now if there is none...
Definition: Database.php:2461
freeResult($res)
Free a result object returned by query() or select().
Definition: Database.php:1042
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:2907
lockTables($read, $write, $method, $lowPriority=true)
Lock specific tables.
Definition: Database.php:3217
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
getApproximateLagStatus()
Get a slave lag estimate for this server.
Definition: Database.php:2882
replaceVars($ins)
Database independent variable replacement.
Definition: Database.php:3115
const LIST_SET
Definition: Defines.php:194
fetchRow($res)
Fetch the next row from the given result object, in associative array form.
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
selectDB($db)
Change the current database.
Definition: Database.php:1648
selectRow($table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Single row SELECT wrapper.
Definition: Database.php:1288
int $mTrxLevel
Either 1 if a transaction is active or 0 otherwise.
Definition: Database.php:82
const DBO_IGNORE
Definition: Defines.php:32
pendingWriteQueryDuration()
Get the time spend running write queries for this transaction.
Definition: Database.php:396
const LIST_OR
Definition: Defines.php:196
numRows($res)
Get the number of rows in a result object.
setFlag($flag)
Set a flag for this connection.
Definition: Database.php:408
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
wasErrorReissuable()
Determines if the last query error was something that should be dealt with by pinging the connection ...
Definition: Database.php:2365
buildGroupConcatField($delim, $table, $field, $conds= '', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
Definition: Database.php:1640
Database abstraction object.
Definition: Database.php:32
indexUnique($table, $index)
Determines if a given index is unique.
Definition: Database.php:1398
wasConnectionError($errno)
Determines if the given query error was a connection drop STUB.
Definition: Database.php:2380
$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
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
closeConnection()
Closes underlying database connection.
selectRowCount($tables, $vars= '*', $conds= '', $fname=__METHOD__, $options=[], $join_conds=[])
Get the number of rows in dataset.
Definition: Database.php:1322
cascadingDeletes()
Returns true if this database supports (and uses) cascading deletes.
Definition: Database.php:315
runOnTransactionIdleCallbacks()
Actually any "on transaction idle" callbacks.
Definition: Database.php:2474
selectField($table, $var, $cond= '', $fname=__METHOD__, $options=[])
A SELECT wrapper which returns a single field from a single result row.
Definition: Database.php:1045
unionQueries($sqls, $all)
Construct a UNION query This is used for providing overload point for other DB abstractions not compa...
Definition: Database.php:2335
indexInfo($table, $index, $fname=__METHOD__)
Get information about an index into an object.
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined...
Definition: Setup.php:35
setBigSelects($value=true)
Allow or deny "big selects" for this session only.
Definition: Database.php:3276
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 then executing the whole list after the page is displayed We don t do anything smart like collating updates to the same table or such because the list is almost always going to have just one item on if so it s not worth the trouble Since there is a job queue in the jobs table
Definition: deferred.txt:11
clearViewsCache()
Reset the views process cache set by listViews()
Definition: Database.php:2780
wasDeadlock()
Determines if the last failure was due to a deadlock STUB.
Definition: Database.php:2357
__sleep()
Called by serialize.
Definition: Database.php:553
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:2135
textFieldSize($table, $field)
Returns the size of a text field, or -1 for "unlimited".
Definition: Database.php:2215
fillPrepared($preparedQuery, $args)
For faking prepared SQL statements on DBs that don't support it directly.
Definition: Database.php:994
getServer()
Get the server hostname or IP address.
Definition: Database.php:1661
static getLocalServerInstance($fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
begin($fname=__METHOD__)
Begin a transaction.
Definition: Database.php:2582
getServerInfo()
A string describing the current software version, and possibly other details in a user-friendly way...
Definition: Database.php:179
anyChar()
Returns a token for buildLike() that denotes a '_' to be used in a LIKE query.
Definition: Database.php:2027
$line
Definition: cdb.php:59
timestampOrNull($ts=null)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
Definition: Database.php:2816
reportQueryError($error, $errno, $sql, $fname, $tempIgnore=false)
Report a query error.
Definition: Database.php:918
makeInsertOptions($options)
Helper for DatabaseBase::insert().
Definition: Database.php:1414
nextSequenceValue($seqName)
Returns an appropriately quoted sequence value for inserting a new row.
Definition: Database.php:2035
$wgDBprefix
Table name prefix.
getTransactionLagStatus()
Get the slave lag when the current transaction started.
Definition: Database.php:2870
query($sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition: Database.php:780
static generalizeSQL($sql)
Removes most variables from an SQL query and replaces them with X or N for numbers.
Definition: Database.php:1348
static query($sql, $function, $isMaster)
Begins profiling on a database query.
Definition: MWDebug.php:344
lastDoneWrites()
Returns the last time the connection may have been used for write queries.
Definition: Database.php:382
isWriteQuery($sql)
Determine whether a query writes to the DB.
Definition: Database.php:762
lock($lockName, $method, $timeout=5)
Acquire a named lock.
Definition: Database.php:3177
makeUpdateOptions($options)
Make UPDATE options for the DatabaseBase::update function.
Definition: Database.php:1500
const DBO_DEFAULT
Definition: Defines.php:34
getServerUptime()
Determines how long the server has been up STUB.
Definition: Database.php:2353
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:1004
commit($fname=__METHOD__, $flush= '')
Commits a transaction previously started using begin().
Definition: Database.php:2657
masterPosWait(DBMasterPos $pos, $timeout)
Wait for the slave to catch up to a given master position.
Definition: Database.php:2439
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:1004
getInfinity()
Find out when 'infinity' is.
Definition: Database.php:3260
startAtomic($fname=__METHOD__)
Begin an atomic section of statements.
Definition: Database.php:2538
static consume(ScopedCallback &$sc=null)
Trigger a scoped callback and destroy it.
bool $mTrxDoneWrites
Record if possible write queries were done in the last transaction started.
Definition: Database.php:120
connectionErrorHandler($errno, $errstr)
Definition: Database.php:673
getMasterPos()
Get the position of this master.
Definition: Database.php:2449
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
Definition: Database.php:3083
rollback($fname=__METHOD__, $flush= '')
Rollback a transaction previously started using begin().
Definition: Database.php:2712
execute($prepared, $args=null)
Execute a prepared query with the various arguments.
Definition: Database.php:975
makeWhereFrom2d($data, $baseKey, $subKey)
Build a partial where clause from a 2-d array such as used for LinkBatch.
Definition: Database.php:1593
makeGroupByWithHaving($options)
Returns an optional GROUP BY with an optional HAVING.
Definition: Database.php:1191
getLBInfo($name=null)
Get properties passed down from the server info array of the load balancer.
Definition: Database.php:252
Result wrapper for grabbing data queried by someone else.
isView($name)
Differentiates between a TABLE and a VIEW.
Definition: Database.php:2808
deleteJoin($delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
Definition: Database.php:2189
getLogContext(array $extras=[])
Create a log context to pass to wfLogDBError or other logging functions.
Definition: Database.php:683
BagOStuff $srvCache
APC cache.
Definition: Database.php:49
escapeLikeInternal($s)
Definition: Database.php:2003
$wgSharedPrefix
writesOrCallbacksPending()
Returns true if there is a transaction open with possible write queries or transaction pre-commit/idl...
Definition: Database.php:390
prepare($sql, $func= 'DatabaseBase::prepare')
Intended to be compatible with the PEAR::DB wrapper functions.
Definition: Database.php:952
setSchemaVars($vars)
Set variables to be used in sourceFile/sourceStream, in preference to the ones in $GLOBALS...
Definition: Database.php:3008
static logException($e)
Log an exception to the exception log (if enabled).
lastQuery()
Return the last query that went through IDatabase::query()
Definition: Database.php:374
nativeReplace($table, $rows, $fname)
REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE statement.
Definition: Database.php:2111
callable[] $mTrxIdleCallbacks
Definition: Database.php:56
__construct(array $params)
Constructor.
Definition: Database.php:498
$debug
Definition: mcc.php:31
resultObject($result)
Take the result from a query, and wrap it in a ResultWrapper if necessary.
Definition: Database.php:2837
replace($table, $uniqueIndexes, $rows, $fname=__METHOD__)
REPLACE query wrapper.
Definition: Database.php:2053
getLazyMasterHandle()
Definition: Database.php:287
$wgSharedSchema
realTimestamps()
Returns true if this database uses timestamps rather than integers.
Definition: Database.php:343
isOpen()
Is a connection to the database open?
Definition: Database.php:404
decodeBlob($b)
Some DBMSs return a special placeholder object representing blob fields in result objects...
Definition: Database.php:2936
getSessionLagStatus()
Get the slave lag when the current transaction started or a general lag estimate if not transaction i...
Definition: Database.php:2855
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:1107
insertSelect($destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[])
INSERT SELECT wrapper.
Definition: Database.php:2262
installErrorHandler()
Definition: Database.php:645
reportConnectionError($error= 'Unknown error')
Definition: Database.php:736
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:1996
buildConcat($stringList)
Build a concatenation list to feed into a SQL query.
Definition: Database.php:1636
doAtomicSection($fname, $callback)
Run a callback to do an atomic set of updates for this database.
Definition: Database.php:2567
sourceStream($fp, $lineCallback=false, $resultCallback=false, $fname=__METHOD__, $inputCallback=false)
Read and execute commands from an open file handle.
Definition: Database.php:3025
wfGetCaller($level=2)
Get the name of the function which called this function wfGetCaller( 1 ) is the function with the wfG...
limitResult($sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.
Definition: Database.php:2321
fieldNamesWithAlias($fields)
Gets an array of aliased field names.
Definition: Database.php:1863
Basic database interface for live and lazy-loaded DB handles.
Definition: IDatabase.php:35
setFileHandle($fh)
Set the filehandle to copy write statements to.
Definition: Database.php:248
const DEADLOCK_DELAY_MIN
Minimum time to wait before retry, in microseconds.
Definition: Database.php:37
fieldExists($table, $field, $fname=__METHOD__)
Determines whether a field exists in a table.
Definition: Database.php:1370
dbSchema($schema=null)
Get/set the db schema.
Definition: Database.php:239
$wgUser
Definition: Setup.php:794
$matches
getLag()
Get slave lag.
Definition: Database.php:2924
wasLockTimeout()
Determines if the last failure was due to a lock timeout STUB.
Definition: Database.php:2361
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310