MediaWiki  1.27.1
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  $res = $this->query( "SELECT COUNT(*) AS rowcount FROM ($sql) tmp_count", $fname );
1328 
1329  if ( $res ) {
1330  $row = $this->fetchRow( $res );
1331  $rows = ( isset( $row['rowcount'] ) ) ? (int)$row['rowcount'] : 0;
1332  }
1333 
1334  return $rows;
1335  }
1336 
1345  protected static function generalizeSQL( $sql ) {
1346  # This does the same as the regexp below would do, but in such a way
1347  # as to avoid crashing php on some large strings.
1348  # $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
1349 
1350  $sql = str_replace( "\\\\", '', $sql );
1351  $sql = str_replace( "\\'", '', $sql );
1352  $sql = str_replace( "\\\"", '', $sql );
1353  $sql = preg_replace( "/'.*'/s", "'X'", $sql );
1354  $sql = preg_replace( '/".*"/s', "'X'", $sql );
1355 
1356  # All newlines, tabs, etc replaced by single space
1357  $sql = preg_replace( '/\s+/', ' ', $sql );
1358 
1359  # All numbers => N,
1360  # except the ones surrounded by characters, e.g. l10n
1361  $sql = preg_replace( '/-?\d+(,-?\d+)+/s', 'N,...,N', $sql );
1362  $sql = preg_replace( '/(?<![a-zA-Z])-?\d+(?![a-zA-Z])/s', 'N', $sql );
1363 
1364  return $sql;
1365  }
1366 
1367  public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1368  $info = $this->fieldInfo( $table, $field );
1369 
1370  return (bool)$info;
1371  }
1372 
1373  public function indexExists( $table, $index, $fname = __METHOD__ ) {
1374  if ( !$this->tableExists( $table ) ) {
1375  return null;
1376  }
1377 
1378  $info = $this->indexInfo( $table, $index, $fname );
1379  if ( is_null( $info ) ) {
1380  return null;
1381  } else {
1382  return $info !== false;
1383  }
1384  }
1385 
1386  public function tableExists( $table, $fname = __METHOD__ ) {
1387  $table = $this->tableName( $table );
1388  $old = $this->ignoreErrors( true );
1389  $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
1390  $this->ignoreErrors( $old );
1391 
1392  return (bool)$res;
1393  }
1394 
1395  public function indexUnique( $table, $index ) {
1396  $indexInfo = $this->indexInfo( $table, $index );
1397 
1398  if ( !$indexInfo ) {
1399  return null;
1400  }
1401 
1402  return !$indexInfo[0]->Non_unique;
1403  }
1404 
1411  protected function makeInsertOptions( $options ) {
1412  return implode( ' ', $options );
1413  }
1414 
1415  public function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
1416  # No rows to insert, easy just return now
1417  if ( !count( $a ) ) {
1418  return true;
1419  }
1420 
1421  $table = $this->tableName( $table );
1422 
1423  if ( !is_array( $options ) ) {
1424  $options = [ $options ];
1425  }
1426 
1427  $fh = null;
1428  if ( isset( $options['fileHandle'] ) ) {
1429  $fh = $options['fileHandle'];
1430  }
1431  $options = $this->makeInsertOptions( $options );
1432 
1433  if ( isset( $a[0] ) && is_array( $a[0] ) ) {
1434  $multi = true;
1435  $keys = array_keys( $a[0] );
1436  } else {
1437  $multi = false;
1438  $keys = array_keys( $a );
1439  }
1440 
1441  $sql = 'INSERT ' . $options .
1442  " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
1443 
1444  if ( $multi ) {
1445  $first = true;
1446  foreach ( $a as $row ) {
1447  if ( $first ) {
1448  $first = false;
1449  } else {
1450  $sql .= ',';
1451  }
1452  $sql .= '(' . $this->makeList( $row ) . ')';
1453  }
1454  } else {
1455  $sql .= '(' . $this->makeList( $a ) . ')';
1456  }
1457 
1458  if ( $fh !== null && false === fwrite( $fh, $sql ) ) {
1459  return false;
1460  } elseif ( $fh !== null ) {
1461  return true;
1462  }
1463 
1464  return (bool)$this->query( $sql, $fname );
1465  }
1466 
1473  protected function makeUpdateOptionsArray( $options ) {
1474  if ( !is_array( $options ) ) {
1475  $options = [ $options ];
1476  }
1477 
1478  $opts = [];
1479 
1480  if ( in_array( 'LOW_PRIORITY', $options ) ) {
1481  $opts[] = $this->lowPriorityOption();
1482  }
1483 
1484  if ( in_array( 'IGNORE', $options ) ) {
1485  $opts[] = 'IGNORE';
1486  }
1487 
1488  return $opts;
1489  }
1490 
1497  protected function makeUpdateOptions( $options ) {
1498  $opts = $this->makeUpdateOptionsArray( $options );
1499 
1500  return implode( ' ', $opts );
1501  }
1502 
1503  function update( $table, $values, $conds, $fname = __METHOD__, $options = [] ) {
1504  $table = $this->tableName( $table );
1505  $opts = $this->makeUpdateOptions( $options );
1506  $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
1507 
1508  if ( $conds !== [] && $conds !== '*' ) {
1509  $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
1510  }
1511 
1512  return $this->query( $sql, $fname );
1513  }
1514 
1515  public function makeList( $a, $mode = LIST_COMMA ) {
1516  if ( !is_array( $a ) ) {
1517  throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
1518  }
1519 
1520  $first = true;
1521  $list = '';
1522 
1523  foreach ( $a as $field => $value ) {
1524  if ( !$first ) {
1525  if ( $mode == LIST_AND ) {
1526  $list .= ' AND ';
1527  } elseif ( $mode == LIST_OR ) {
1528  $list .= ' OR ';
1529  } else {
1530  $list .= ',';
1531  }
1532  } else {
1533  $first = false;
1534  }
1535 
1536  if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
1537  $list .= "($value)";
1538  } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
1539  $list .= "$value";
1540  } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
1541  // Remove null from array to be handled separately if found
1542  $includeNull = false;
1543  foreach ( array_keys( $value, null, true ) as $nullKey ) {
1544  $includeNull = true;
1545  unset( $value[$nullKey] );
1546  }
1547  if ( count( $value ) == 0 && !$includeNull ) {
1548  throw new MWException( __METHOD__ . ": empty input for field $field" );
1549  } elseif ( count( $value ) == 0 ) {
1550  // only check if $field is null
1551  $list .= "$field IS NULL";
1552  } else {
1553  // IN clause contains at least one valid element
1554  if ( $includeNull ) {
1555  // Group subconditions to ensure correct precedence
1556  $list .= '(';
1557  }
1558  if ( count( $value ) == 1 ) {
1559  // Special-case single values, as IN isn't terribly efficient
1560  // Don't necessarily assume the single key is 0; we don't
1561  // enforce linear numeric ordering on other arrays here.
1562  $value = array_values( $value )[0];
1563  $list .= $field . " = " . $this->addQuotes( $value );
1564  } else {
1565  $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
1566  }
1567  // if null present in array, append IS NULL
1568  if ( $includeNull ) {
1569  $list .= " OR $field IS NULL)";
1570  }
1571  }
1572  } elseif ( $value === null ) {
1573  if ( $mode == LIST_AND || $mode == LIST_OR ) {
1574  $list .= "$field IS ";
1575  } elseif ( $mode == LIST_SET ) {
1576  $list .= "$field = ";
1577  }
1578  $list .= 'NULL';
1579  } else {
1580  if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
1581  $list .= "$field = ";
1582  }
1583  $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
1584  }
1585  }
1586 
1587  return $list;
1588  }
1589 
1590  public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
1591  $conds = [];
1592 
1593  foreach ( $data as $base => $sub ) {
1594  if ( count( $sub ) ) {
1595  $conds[] = $this->makeList(
1596  [ $baseKey => $base, $subKey => array_keys( $sub ) ],
1597  LIST_AND );
1598  }
1599  }
1600 
1601  if ( $conds ) {
1602  return $this->makeList( $conds, LIST_OR );
1603  } else {
1604  // Nothing to search for...
1605  return false;
1606  }
1607  }
1608 
1617  public function aggregateValue( $valuedata, $valuename = 'value' ) {
1618  return $valuename;
1619  }
1620 
1621  public function bitNot( $field ) {
1622  return "(~$field)";
1623  }
1624 
1625  public function bitAnd( $fieldLeft, $fieldRight ) {
1626  return "($fieldLeft & $fieldRight)";
1627  }
1628 
1629  public function bitOr( $fieldLeft, $fieldRight ) {
1630  return "($fieldLeft | $fieldRight)";
1631  }
1632 
1633  public function buildConcat( $stringList ) {
1634  return 'CONCAT(' . implode( ',', $stringList ) . ')';
1635  }
1636 
1637  public function buildGroupConcatField(
1638  $delim, $table, $field, $conds = '', $join_conds = []
1639  ) {
1640  $fld = "GROUP_CONCAT($field SEPARATOR " . $this->addQuotes( $delim ) . ')';
1641 
1642  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
1643  }
1644 
1645  public function selectDB( $db ) {
1646  # Stub. Shouldn't cause serious problems if it's not overridden, but
1647  # if your database engine supports a concept similar to MySQL's
1648  # databases you may as well.
1649  $this->mDBname = $db;
1650 
1651  return true;
1652  }
1653 
1654  public function getDBname() {
1655  return $this->mDBname;
1656  }
1657 
1658  public function getServer() {
1659  return $this->mServer;
1660  }
1661 
1681  public function tableName( $name, $format = 'quoted' ) {
1683  # Skip the entire process when we have a string quoted on both ends.
1684  # Note that we check the end so that we will still quote any use of
1685  # use of `database`.table. But won't break things if someone wants
1686  # to query a database table with a dot in the name.
1687  if ( $this->isQuotedIdentifier( $name ) ) {
1688  return $name;
1689  }
1690 
1691  # Lets test for any bits of text that should never show up in a table
1692  # name. Basically anything like JOIN or ON which are actually part of
1693  # SQL queries, but may end up inside of the table value to combine
1694  # sql. Such as how the API is doing.
1695  # Note that we use a whitespace test rather than a \b test to avoid
1696  # any remote case where a word like on may be inside of a table name
1697  # surrounded by symbols which may be considered word breaks.
1698  if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
1699  return $name;
1700  }
1701 
1702  # Split database and table into proper variables.
1703  # We reverse the explode so that database.table and table both output
1704  # the correct table.
1705  $dbDetails = explode( '.', $name, 3 );
1706  if ( count( $dbDetails ) == 3 ) {
1707  list( $database, $schema, $table ) = $dbDetails;
1708  # We don't want any prefix added in this case
1709  $prefix = '';
1710  } elseif ( count( $dbDetails ) == 2 ) {
1711  list( $database, $table ) = $dbDetails;
1712  # We don't want any prefix added in this case
1713  # In dbs that support it, $database may actually be the schema
1714  # but that doesn't affect any of the functionality here
1715  $prefix = '';
1716  $schema = null;
1717  } else {
1718  list( $table ) = $dbDetails;
1719  if ( $wgSharedDB !== null # We have a shared database
1720  && $this->mForeign == false # We're not working on a foreign database
1721  && !$this->isQuotedIdentifier( $table ) # Prevent shared tables listing '`table`'
1722  && in_array( $table, $wgSharedTables ) # A shared table is selected
1723  ) {
1724  $database = $wgSharedDB;
1725  $schema = $wgSharedSchema === null ? $this->mSchema : $wgSharedSchema;
1726  $prefix = $wgSharedPrefix === null ? $this->mTablePrefix : $wgSharedPrefix;
1727  } else {
1728  $database = null;
1729  $schema = $this->mSchema; # Default schema
1730  $prefix = $this->mTablePrefix; # Default prefix
1731  }
1732  }
1733 
1734  # Quote $table and apply the prefix if not quoted.
1735  # $tableName might be empty if this is called from Database::replaceVars()
1736  $tableName = "{$prefix}{$table}";
1737  if ( $format == 'quoted' && !$this->isQuotedIdentifier( $tableName ) && $tableName !== '' ) {
1738  $tableName = $this->addIdentifierQuotes( $tableName );
1739  }
1740 
1741  # Quote $schema and merge it with the table name if needed
1742  if ( strlen( $schema ) ) {
1743  if ( $format == 'quoted' && !$this->isQuotedIdentifier( $schema ) ) {
1744  $schema = $this->addIdentifierQuotes( $schema );
1745  }
1746  $tableName = $schema . '.' . $tableName;
1747  }
1748 
1749  # Quote $database and merge it with the table name if needed
1750  if ( $database !== null ) {
1751  if ( $format == 'quoted' && !$this->isQuotedIdentifier( $database ) ) {
1752  $database = $this->addIdentifierQuotes( $database );
1753  }
1754  $tableName = $database . '.' . $tableName;
1755  }
1756 
1757  return $tableName;
1758  }
1759 
1771  public function tableNames() {
1772  $inArray = func_get_args();
1773  $retVal = [];
1774 
1775  foreach ( $inArray as $name ) {
1776  $retVal[$name] = $this->tableName( $name );
1777  }
1778 
1779  return $retVal;
1780  }
1781 
1793  public function tableNamesN() {
1794  $inArray = func_get_args();
1795  $retVal = [];
1796 
1797  foreach ( $inArray as $name ) {
1798  $retVal[] = $this->tableName( $name );
1799  }
1800 
1801  return $retVal;
1802  }
1803 
1812  public function tableNameWithAlias( $name, $alias = false ) {
1813  if ( !$alias || $alias == $name ) {
1814  return $this->tableName( $name );
1815  } else {
1816  return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
1817  }
1818  }
1819 
1826  public function tableNamesWithAlias( $tables ) {
1827  $retval = [];
1828  foreach ( $tables as $alias => $table ) {
1829  if ( is_numeric( $alias ) ) {
1830  $alias = $table;
1831  }
1832  $retval[] = $this->tableNameWithAlias( $table, $alias );
1833  }
1834 
1835  return $retval;
1836  }
1837 
1846  public function fieldNameWithAlias( $name, $alias = false ) {
1847  if ( !$alias || (string)$alias === (string)$name ) {
1848  return $name;
1849  } else {
1850  return $name . ' AS ' . $this->addIdentifierQuotes( $alias ); // PostgreSQL needs AS
1851  }
1852  }
1853 
1860  public function fieldNamesWithAlias( $fields ) {
1861  $retval = [];
1862  foreach ( $fields as $alias => $field ) {
1863  if ( is_numeric( $alias ) ) {
1864  $alias = $field;
1865  }
1866  $retval[] = $this->fieldNameWithAlias( $field, $alias );
1867  }
1868 
1869  return $retval;
1870  }
1871 
1881  protected function tableNamesWithUseIndexOrJOIN(
1882  $tables, $use_index = [], $join_conds = []
1883  ) {
1884  $ret = [];
1885  $retJOIN = [];
1886  $use_index = (array)$use_index;
1887  $join_conds = (array)$join_conds;
1888 
1889  foreach ( $tables as $alias => $table ) {
1890  if ( !is_string( $alias ) ) {
1891  // No alias? Set it equal to the table name
1892  $alias = $table;
1893  }
1894  // Is there a JOIN clause for this table?
1895  if ( isset( $join_conds[$alias] ) ) {
1896  list( $joinType, $conds ) = $join_conds[$alias];
1897  $tableClause = $joinType;
1898  $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
1899  if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
1900  $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
1901  if ( $use != '' ) {
1902  $tableClause .= ' ' . $use;
1903  }
1904  }
1905  $on = $this->makeList( (array)$conds, LIST_AND );
1906  if ( $on != '' ) {
1907  $tableClause .= ' ON (' . $on . ')';
1908  }
1909 
1910  $retJOIN[] = $tableClause;
1911  } elseif ( isset( $use_index[$alias] ) ) {
1912  // Is there an INDEX clause for this table?
1913  $tableClause = $this->tableNameWithAlias( $table, $alias );
1914  $tableClause .= ' ' . $this->useIndexClause(
1915  implode( ',', (array)$use_index[$alias] )
1916  );
1917 
1918  $ret[] = $tableClause;
1919  } else {
1920  $tableClause = $this->tableNameWithAlias( $table, $alias );
1921 
1922  $ret[] = $tableClause;
1923  }
1924  }
1925 
1926  // We can't separate explicit JOIN clauses with ',', use ' ' for those
1927  $implicitJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
1928  $explicitJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
1929 
1930  // Compile our final table clause
1931  return implode( ' ', [ $implicitJoins, $explicitJoins ] );
1932  }
1933 
1940  protected function indexName( $index ) {
1941  // Backwards-compatibility hack
1942  $renamed = [
1943  'ar_usertext_timestamp' => 'usertext_timestamp',
1944  'un_user_id' => 'user_id',
1945  'un_user_ip' => 'user_ip',
1946  ];
1947 
1948  if ( isset( $renamed[$index] ) ) {
1949  return $renamed[$index];
1950  } else {
1951  return $index;
1952  }
1953  }
1954 
1955  public function addQuotes( $s ) {
1956  if ( $s instanceof Blob ) {
1957  $s = $s->fetch();
1958  }
1959  if ( $s === null ) {
1960  return 'NULL';
1961  } else {
1962  # This will also quote numeric values. This should be harmless,
1963  # and protects against weird problems that occur when they really
1964  # _are_ strings such as article titles and string->number->string
1965  # conversion is not 1:1.
1966  return "'" . $this->strencode( $s ) . "'";
1967  }
1968  }
1969 
1979  public function addIdentifierQuotes( $s ) {
1980  return '"' . str_replace( '"', '""', $s ) . '"';
1981  }
1982 
1992  public function isQuotedIdentifier( $name ) {
1993  return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
1994  }
1995 
2000  protected function escapeLikeInternal( $s ) {
2001  return addcslashes( $s, '\%_' );
2002  }
2003 
2004  public function buildLike() {
2005  $params = func_get_args();
2006 
2007  if ( count( $params ) > 0 && is_array( $params[0] ) ) {
2008  $params = $params[0];
2009  }
2010 
2011  $s = '';
2012 
2013  foreach ( $params as $value ) {
2014  if ( $value instanceof LikeMatch ) {
2015  $s .= $value->toString();
2016  } else {
2017  $s .= $this->escapeLikeInternal( $value );
2018  }
2019  }
2020 
2021  return " LIKE {$this->addQuotes( $s )} ";
2022  }
2023 
2024  public function anyChar() {
2025  return new LikeMatch( '_' );
2026  }
2027 
2028  public function anyString() {
2029  return new LikeMatch( '%' );
2030  }
2031 
2032  public function nextSequenceValue( $seqName ) {
2033  return null;
2034  }
2035 
2046  public function useIndexClause( $index ) {
2047  return '';
2048  }
2049 
2050  public function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
2051  $quotedTable = $this->tableName( $table );
2052 
2053  if ( count( $rows ) == 0 ) {
2054  return;
2055  }
2056 
2057  # Single row case
2058  if ( !is_array( reset( $rows ) ) ) {
2059  $rows = [ $rows ];
2060  }
2061 
2062  // @FXIME: this is not atomic, but a trx would break affectedRows()
2063  foreach ( $rows as $row ) {
2064  # Delete rows which collide
2065  if ( $uniqueIndexes ) {
2066  $sql = "DELETE FROM $quotedTable WHERE ";
2067  $first = true;
2068  foreach ( $uniqueIndexes as $index ) {
2069  if ( $first ) {
2070  $first = false;
2071  $sql .= '( ';
2072  } else {
2073  $sql .= ' ) OR ( ';
2074  }
2075  if ( is_array( $index ) ) {
2076  $first2 = true;
2077  foreach ( $index as $col ) {
2078  if ( $first2 ) {
2079  $first2 = false;
2080  } else {
2081  $sql .= ' AND ';
2082  }
2083  $sql .= $col . '=' . $this->addQuotes( $row[$col] );
2084  }
2085  } else {
2086  $sql .= $index . '=' . $this->addQuotes( $row[$index] );
2087  }
2088  }
2089  $sql .= ' )';
2090  $this->query( $sql, $fname );
2091  }
2092 
2093  # Now insert the row
2094  $this->insert( $table, $row, $fname );
2095  }
2096  }
2097 
2108  protected function nativeReplace( $table, $rows, $fname ) {
2109  $table = $this->tableName( $table );
2110 
2111  # Single row case
2112  if ( !is_array( reset( $rows ) ) ) {
2113  $rows = [ $rows ];
2114  }
2115 
2116  $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
2117  $first = true;
2118 
2119  foreach ( $rows as $row ) {
2120  if ( $first ) {
2121  $first = false;
2122  } else {
2123  $sql .= ',';
2124  }
2125 
2126  $sql .= '(' . $this->makeList( $row ) . ')';
2127  }
2128 
2129  return $this->query( $sql, $fname );
2130  }
2131 
2132  public function upsert( $table, array $rows, array $uniqueIndexes, array $set,
2133  $fname = __METHOD__
2134  ) {
2135  if ( !count( $rows ) ) {
2136  return true; // nothing to do
2137  }
2138 
2139  if ( !is_array( reset( $rows ) ) ) {
2140  $rows = [ $rows ];
2141  }
2142 
2143  if ( count( $uniqueIndexes ) ) {
2144  $clauses = []; // list WHERE clauses that each identify a single row
2145  foreach ( $rows as $row ) {
2146  foreach ( $uniqueIndexes as $index ) {
2147  $index = is_array( $index ) ? $index : [ $index ]; // columns
2148  $rowKey = []; // unique key to this row
2149  foreach ( $index as $column ) {
2150  $rowKey[$column] = $row[$column];
2151  }
2152  $clauses[] = $this->makeList( $rowKey, LIST_AND );
2153  }
2154  }
2155  $where = [ $this->makeList( $clauses, LIST_OR ) ];
2156  } else {
2157  $where = false;
2158  }
2159 
2160  $useTrx = !$this->mTrxLevel;
2161  if ( $useTrx ) {
2162  $this->begin( $fname );
2163  }
2164  try {
2165  # Update any existing conflicting row(s)
2166  if ( $where !== false ) {
2167  $ok = $this->update( $table, $set, $where, $fname );
2168  } else {
2169  $ok = true;
2170  }
2171  # Now insert any non-conflicting row(s)
2172  $ok = $this->insert( $table, $rows, $fname, [ 'IGNORE' ] ) && $ok;
2173  } catch ( Exception $e ) {
2174  if ( $useTrx ) {
2175  $this->rollback( $fname );
2176  }
2177  throw $e;
2178  }
2179  if ( $useTrx ) {
2180  $this->commit( $fname );
2181  }
2182 
2183  return $ok;
2184  }
2185 
2186  public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
2187  $fname = __METHOD__
2188  ) {
2189  if ( !$conds ) {
2190  throw new DBUnexpectedError( $this,
2191  'DatabaseBase::deleteJoin() called with empty $conds' );
2192  }
2193 
2194  $delTable = $this->tableName( $delTable );
2195  $joinTable = $this->tableName( $joinTable );
2196  $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
2197  if ( $conds != '*' ) {
2198  $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
2199  }
2200  $sql .= ')';
2201 
2202  $this->query( $sql, $fname );
2203  }
2204 
2212  public function textFieldSize( $table, $field ) {
2213  $table = $this->tableName( $table );
2214  $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
2215  $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
2216  $row = $this->fetchObject( $res );
2217 
2218  $m = [];
2219 
2220  if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
2221  $size = $m[1];
2222  } else {
2223  $size = -1;
2224  }
2225 
2226  return $size;
2227  }
2228 
2237  public function lowPriorityOption() {
2238  return '';
2239  }
2240 
2241  public function delete( $table, $conds, $fname = __METHOD__ ) {
2242  if ( !$conds ) {
2243  throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
2244  }
2245 
2246  $table = $this->tableName( $table );
2247  $sql = "DELETE FROM $table";
2248 
2249  if ( $conds != '*' ) {
2250  if ( is_array( $conds ) ) {
2251  $conds = $this->makeList( $conds, LIST_AND );
2252  }
2253  $sql .= ' WHERE ' . $conds;
2254  }
2255 
2256  return $this->query( $sql, $fname );
2257  }
2258 
2259  public function insertSelect( $destTable, $srcTable, $varMap, $conds,
2260  $fname = __METHOD__,
2261  $insertOptions = [], $selectOptions = []
2262  ) {
2263  $destTable = $this->tableName( $destTable );
2264 
2265  if ( !is_array( $insertOptions ) ) {
2266  $insertOptions = [ $insertOptions ];
2267  }
2268 
2269  $insertOptions = $this->makeInsertOptions( $insertOptions );
2270 
2271  if ( !is_array( $selectOptions ) ) {
2272  $selectOptions = [ $selectOptions ];
2273  }
2274 
2275  list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
2276 
2277  if ( is_array( $srcTable ) ) {
2278  $srcTable = implode( ',', array_map( [ &$this, 'tableName' ], $srcTable ) );
2279  } else {
2280  $srcTable = $this->tableName( $srcTable );
2281  }
2282 
2283  $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
2284  " SELECT $startOpts " . implode( ',', $varMap ) .
2285  " FROM $srcTable $useIndex ";
2286 
2287  if ( $conds != '*' ) {
2288  if ( is_array( $conds ) ) {
2289  $conds = $this->makeList( $conds, LIST_AND );
2290  }
2291  $sql .= " WHERE $conds";
2292  }
2293 
2294  $sql .= " $tailOpts";
2295 
2296  return $this->query( $sql, $fname );
2297  }
2298 
2318  public function limitResult( $sql, $limit, $offset = false ) {
2319  if ( !is_numeric( $limit ) ) {
2320  throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
2321  }
2322 
2323  return "$sql LIMIT "
2324  . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
2325  . "{$limit} ";
2326  }
2327 
2328  public function unionSupportsOrderAndLimit() {
2329  return true; // True for almost every DB supported
2330  }
2331 
2332  public function unionQueries( $sqls, $all ) {
2333  $glue = $all ? ') UNION ALL (' : ') UNION (';
2334 
2335  return '(' . implode( $glue, $sqls ) . ')';
2336  }
2337 
2338  public function conditional( $cond, $trueVal, $falseVal ) {
2339  if ( is_array( $cond ) ) {
2340  $cond = $this->makeList( $cond, LIST_AND );
2341  }
2342 
2343  return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
2344  }
2345 
2346  public function strreplace( $orig, $old, $new ) {
2347  return "REPLACE({$orig}, {$old}, {$new})";
2348  }
2349 
2350  public function getServerUptime() {
2351  return 0;
2352  }
2353 
2354  public function wasDeadlock() {
2355  return false;
2356  }
2357 
2358  public function wasLockTimeout() {
2359  return false;
2360  }
2361 
2362  public function wasErrorReissuable() {
2363  return false;
2364  }
2365 
2366  public function wasReadOnlyError() {
2367  return false;
2368  }
2369 
2377  public function wasConnectionError( $errno ) {
2378  return false;
2379  }
2380 
2400  public function deadlockLoop() {
2401  $args = func_get_args();
2402  $function = array_shift( $args );
2403  $tries = self::DEADLOCK_TRIES;
2404 
2405  $this->begin( __METHOD__ );
2406 
2407  $retVal = null;
2409  $e = null;
2410  do {
2411  try {
2412  $retVal = call_user_func_array( $function, $args );
2413  break;
2414  } catch ( DBQueryError $e ) {
2415  if ( $this->wasDeadlock() ) {
2416  // Retry after a randomized delay
2417  usleep( mt_rand( self::DEADLOCK_DELAY_MIN, self::DEADLOCK_DELAY_MAX ) );
2418  } else {
2419  // Throw the error back up
2420  throw $e;
2421  }
2422  }
2423  } while ( --$tries > 0 );
2424 
2425  if ( $tries <= 0 ) {
2426  // Too many deadlocks; give up
2427  $this->rollback( __METHOD__ );
2428  throw $e;
2429  } else {
2430  $this->commit( __METHOD__ );
2431 
2432  return $retVal;
2433  }
2434  }
2435 
2436  public function masterPosWait( DBMasterPos $pos, $timeout ) {
2437  # Real waits are implemented in the subclass.
2438  return 0;
2439  }
2440 
2441  public function getSlavePos() {
2442  # Stub
2443  return false;
2444  }
2445 
2446  public function getMasterPos() {
2447  # Stub
2448  return false;
2449  }
2450 
2451  final public function onTransactionIdle( $callback ) {
2452  $this->mTrxIdleCallbacks[] = [ $callback, wfGetCaller() ];
2453  if ( !$this->mTrxLevel ) {
2455  }
2456  }
2457 
2458  final public function onTransactionPreCommitOrIdle( $callback ) {
2459  if ( $this->mTrxLevel ) {
2460  $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
2461  } else {
2462  $this->onTransactionIdle( $callback ); // this will trigger immediately
2463  }
2464  }
2465 
2471  protected function runOnTransactionIdleCallbacks() {
2472  $autoTrx = $this->getFlag( DBO_TRX ); // automatic begin() enabled?
2473 
2474  $e = $ePrior = null; // last exception
2475  do { // callbacks may add callbacks :)
2476  $callbacks = $this->mTrxIdleCallbacks;
2477  $this->mTrxIdleCallbacks = []; // recursion guard
2478  foreach ( $callbacks as $callback ) {
2479  try {
2480  list( $phpCallback ) = $callback;
2481  $this->clearFlag( DBO_TRX ); // make each query its own transaction
2482  call_user_func( $phpCallback );
2483  if ( $autoTrx ) {
2484  $this->setFlag( DBO_TRX ); // restore automatic begin()
2485  } else {
2486  $this->clearFlag( DBO_TRX ); // restore auto-commit
2487  }
2488  } catch ( Exception $e ) {
2489  if ( $ePrior ) {
2491  }
2492  $ePrior = $e;
2493  // Some callbacks may use startAtomic/endAtomic, so make sure
2494  // their transactions are ended so other callbacks don't fail
2495  if ( $this->trxLevel() ) {
2496  $this->rollback( __METHOD__ );
2497  }
2498  }
2499  }
2500  } while ( count( $this->mTrxIdleCallbacks ) );
2501 
2502  if ( $e instanceof Exception ) {
2503  throw $e; // re-throw any last exception
2504  }
2505  }
2506 
2512  protected function runOnTransactionPreCommitCallbacks() {
2513  $e = $ePrior = null; // last exception
2514  do { // callbacks may add callbacks :)
2515  $callbacks = $this->mTrxPreCommitCallbacks;
2516  $this->mTrxPreCommitCallbacks = []; // recursion guard
2517  foreach ( $callbacks as $callback ) {
2518  try {
2519  list( $phpCallback ) = $callback;
2520  call_user_func( $phpCallback );
2521  } catch ( Exception $e ) {
2522  if ( $ePrior ) {
2524  }
2525  $ePrior = $e;
2526  }
2527  }
2528  } while ( count( $this->mTrxPreCommitCallbacks ) );
2529 
2530  if ( $e instanceof Exception ) {
2531  throw $e; // re-throw any last exception
2532  }
2533  }
2534 
2535  final public function startAtomic( $fname = __METHOD__ ) {
2536  if ( !$this->mTrxLevel ) {
2537  $this->begin( $fname );
2538  $this->mTrxAutomatic = true;
2539  // If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
2540  // in all changes being in one transaction to keep requests transactional.
2541  if ( !$this->getFlag( DBO_TRX ) ) {
2542  $this->mTrxAutomaticAtomic = true;
2543  }
2544  }
2545 
2546  $this->mTrxAtomicLevels[] = $fname;
2547  }
2548 
2549  final public function endAtomic( $fname = __METHOD__ ) {
2550  if ( !$this->mTrxLevel ) {
2551  throw new DBUnexpectedError( $this, 'No atomic transaction is open.' );
2552  }
2553  if ( !$this->mTrxAtomicLevels ||
2554  array_pop( $this->mTrxAtomicLevels ) !== $fname
2555  ) {
2556  throw new DBUnexpectedError( $this, 'Invalid atomic section ended.' );
2557  }
2558 
2559  if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) {
2560  $this->commit( $fname, 'flush' );
2561  }
2562  }
2563 
2564  final public function doAtomicSection( $fname, $callback ) {
2565  if ( !is_callable( $callback ) ) {
2566  throw new UnexpectedValueException( "Invalid callback." );
2567  };
2568 
2569  $this->startAtomic( $fname );
2570  try {
2571  call_user_func_array( $callback, [ $this, $fname ] );
2572  } catch ( Exception $e ) {
2573  $this->rollback( $fname );
2574  throw $e;
2575  }
2576  $this->endAtomic( $fname );
2577  }
2578 
2579  final public function begin( $fname = __METHOD__ ) {
2580  if ( $this->mTrxLevel ) { // implicit commit
2581  if ( $this->mTrxAtomicLevels ) {
2582  // If the current transaction was an automatic atomic one, then we definitely have
2583  // a problem. Same if there is any unclosed atomic level.
2584  $levels = implode( ', ', $this->mTrxAtomicLevels );
2585  throw new DBUnexpectedError(
2586  $this,
2587  "Got explicit BEGIN from $fname while atomic section(s) $levels are open."
2588  );
2589  } elseif ( !$this->mTrxAutomatic ) {
2590  // We want to warn about inadvertently nested begin/commit pairs, but not about
2591  // auto-committing implicit transactions that were started by query() via DBO_TRX
2592  $msg = "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
2593  " performing implicit commit!";
2594  wfWarn( $msg );
2595  wfLogDBError( $msg,
2596  $this->getLogContext( [
2597  'method' => __METHOD__,
2598  'fname' => $fname,
2599  ] )
2600  );
2601  } else {
2602  // if the transaction was automatic and has done write operations
2603  if ( $this->mTrxDoneWrites ) {
2604  wfDebug( "$fname: Automatic transaction with writes in progress" .
2605  " (from {$this->mTrxFname}), performing implicit commit!\n"
2606  );
2607  }
2608  }
2609 
2611  $writeTime = $this->pendingWriteQueryDuration();
2612  $this->doCommit( $fname );
2613  if ( $this->mTrxDoneWrites ) {
2614  $this->mDoneWrites = microtime( true );
2615  $this->getTransactionProfiler()->transactionWritingOut(
2616  $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
2617  }
2619  }
2620 
2621  # Avoid fatals if close() was called
2622  $this->assertOpen();
2623 
2624  $this->doBegin( $fname );
2625  $this->mTrxTimestamp = microtime( true );
2626  $this->mTrxFname = $fname;
2627  $this->mTrxDoneWrites = false;
2628  $this->mTrxAutomatic = false;
2629  $this->mTrxAutomaticAtomic = false;
2630  $this->mTrxAtomicLevels = [];
2631  $this->mTrxIdleCallbacks = [];
2632  $this->mTrxPreCommitCallbacks = [];
2633  $this->mTrxShortId = wfRandomString( 12 );
2634  $this->mTrxWriteDuration = 0.0;
2635  $this->mTrxWriteCallers = [];
2636  // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
2637  // Get an estimate of the slave lag before then, treating estimate staleness
2638  // as lag itself just to be safe
2639  $status = $this->getApproximateLagStatus();
2640  $this->mTrxSlaveLag = $status['lag'] + ( microtime( true ) - $status['since'] );
2641  }
2642 
2649  protected function doBegin( $fname ) {
2650  $this->query( 'BEGIN', $fname );
2651  $this->mTrxLevel = 1;
2652  }
2653 
2654  final public function commit( $fname = __METHOD__, $flush = '' ) {
2655  if ( $this->mTrxLevel && $this->mTrxAtomicLevels ) {
2656  // There are still atomic sections open. This cannot be ignored
2657  $levels = implode( ', ', $this->mTrxAtomicLevels );
2658  throw new DBUnexpectedError(
2659  $this,
2660  "Got COMMIT while atomic sections $levels are still open"
2661  );
2662  }
2663 
2664  if ( $flush === 'flush' ) {
2665  if ( !$this->mTrxLevel ) {
2666  return; // nothing to do
2667  } elseif ( !$this->mTrxAutomatic ) {
2668  throw new DBUnexpectedError(
2669  $this,
2670  "$fname: Flushing an explicit transaction, getting out of sync!"
2671  );
2672  }
2673  } else {
2674  if ( !$this->mTrxLevel ) {
2675  wfWarn( "$fname: No transaction to commit, something got out of sync!" );
2676  return; // nothing to do
2677  } elseif ( $this->mTrxAutomatic ) {
2678  wfWarn( "$fname: Explicit commit of implicit transaction. Something may be out of sync!" );
2679  }
2680  }
2681 
2682  # Avoid fatals if close() was called
2683  $this->assertOpen();
2684 
2686  $writeTime = $this->pendingWriteQueryDuration();
2687  $this->doCommit( $fname );
2688  if ( $this->mTrxDoneWrites ) {
2689  $this->mDoneWrites = microtime( true );
2690  $this->getTransactionProfiler()->transactionWritingOut(
2691  $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
2692  }
2694  }
2695 
2702  protected function doCommit( $fname ) {
2703  if ( $this->mTrxLevel ) {
2704  $this->query( 'COMMIT', $fname );
2705  $this->mTrxLevel = 0;
2706  }
2707  }
2708 
2709  final public function rollback( $fname = __METHOD__, $flush = '' ) {
2710  if ( $flush !== 'flush' ) {
2711  if ( !$this->mTrxLevel ) {
2712  wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
2713  return; // nothing to do
2714  }
2715  } else {
2716  if ( !$this->mTrxLevel ) {
2717  return; // nothing to do
2718  }
2719  }
2720 
2721  # Avoid fatals if close() was called
2722  $this->assertOpen();
2723 
2724  $this->doRollback( $fname );
2725  $this->mTrxIdleCallbacks = []; // cancel
2726  $this->mTrxPreCommitCallbacks = []; // cancel
2727  $this->mTrxAtomicLevels = [];
2728  if ( $this->mTrxDoneWrites ) {
2729  $this->getTransactionProfiler()->transactionWritingOut(
2730  $this->mServer, $this->mDBname, $this->mTrxShortId );
2731  }
2732  }
2733 
2740  protected function doRollback( $fname ) {
2741  if ( $this->mTrxLevel ) {
2742  $this->query( 'ROLLBACK', $fname, true );
2743  $this->mTrxLevel = 0;
2744  }
2745  }
2746 
2762  public function duplicateTableStructure( $oldName, $newName, $temporary = false,
2763  $fname = __METHOD__
2764  ) {
2765  throw new MWException(
2766  'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
2767  }
2768 
2769  function listTables( $prefix = null, $fname = __METHOD__ ) {
2770  throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
2771  }
2772 
2777  final public function clearViewsCache() {
2778  $this->allViews = null;
2779  }
2780 
2793  public function listViews( $prefix = null, $fname = __METHOD__ ) {
2794  throw new MWException( 'DatabaseBase::listViews is not implemented in descendant class' );
2795  }
2796 
2805  public function isView( $name ) {
2806  throw new MWException( 'DatabaseBase::isView is not implemented in descendant class' );
2807  }
2808 
2809  public function timestamp( $ts = 0 ) {
2810  return wfTimestamp( TS_MW, $ts );
2811  }
2812 
2813  public function timestampOrNull( $ts = null ) {
2814  if ( is_null( $ts ) ) {
2815  return null;
2816  } else {
2817  return $this->timestamp( $ts );
2818  }
2819  }
2820 
2834  protected function resultObject( $result ) {
2835  if ( !$result ) {
2836  return false;
2837  } elseif ( $result instanceof ResultWrapper ) {
2838  return $result;
2839  } elseif ( $result === true ) {
2840  // Successful write query
2841  return $result;
2842  } else {
2843  return new ResultWrapper( $this, $result );
2844  }
2845  }
2846 
2847  public function ping() {
2848  # Stub. Not essential to override.
2849  return true;
2850  }
2851 
2852  public function getSessionLagStatus() {
2853  return $this->getTransactionLagStatus() ?: $this->getApproximateLagStatus();
2854  }
2855 
2867  public function getTransactionLagStatus() {
2868  return $this->mTrxLevel
2869  ? [ 'lag' => $this->mTrxSlaveLag, 'since' => $this->trxTimestamp() ]
2870  : null;
2871  }
2872 
2879  public function getApproximateLagStatus() {
2880  return [
2881  'lag' => $this->getLBInfo( 'slave' ) ? $this->getLag() : 0,
2882  'since' => microtime( true )
2883  ];
2884  }
2885 
2904  public static function getCacheSetOptions( IDatabase $db1 ) {
2905  $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
2906  foreach ( func_get_args() as $db ) {
2908  $status = $db->getSessionLagStatus();
2909  if ( $status['lag'] === false ) {
2910  $res['lag'] = false;
2911  } elseif ( $res['lag'] !== false ) {
2912  $res['lag'] = max( $res['lag'], $status['lag'] );
2913  }
2914  $res['since'] = min( $res['since'], $status['since'] );
2915  $res['pending'] = $res['pending'] ?: $db->writesPending();
2916  }
2917 
2918  return $res;
2919  }
2920 
2921  public function getLag() {
2922  return 0;
2923  }
2924 
2925  function maxListLen() {
2926  return 0;
2927  }
2928 
2929  public function encodeBlob( $b ) {
2930  return $b;
2931  }
2932 
2933  public function decodeBlob( $b ) {
2934  if ( $b instanceof Blob ) {
2935  $b = $b->fetch();
2936  }
2937  return $b;
2938  }
2939 
2940  public function setSessionOptions( array $options ) {
2941  }
2942 
2959  public function sourceFile(
2960  $filename, $lineCallback = false, $resultCallback = false, $fname = false, $inputCallback = false
2961  ) {
2962  MediaWiki\suppressWarnings();
2963  $fp = fopen( $filename, 'r' );
2964  MediaWiki\restoreWarnings();
2965 
2966  if ( false === $fp ) {
2967  throw new MWException( "Could not open \"{$filename}\".\n" );
2968  }
2969 
2970  if ( !$fname ) {
2971  $fname = __METHOD__ . "( $filename )";
2972  }
2973 
2974  try {
2975  $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname, $inputCallback );
2976  } catch ( Exception $e ) {
2977  fclose( $fp );
2978  throw $e;
2979  }
2980 
2981  fclose( $fp );
2982 
2983  return $error;
2984  }
2985 
2994  public function patchPath( $patch ) {
2995  global $IP;
2996 
2997  $dbType = $this->getType();
2998  if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
2999  return "$IP/maintenance/$dbType/archives/$patch";
3000  } else {
3001  return "$IP/maintenance/archives/$patch";
3002  }
3003  }
3004 
3005  public function setSchemaVars( $vars ) {
3006  $this->mSchemaVars = $vars;
3007  }
3008 
3022  public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
3023  $fname = __METHOD__, $inputCallback = false
3024  ) {
3025  $cmd = '';
3026 
3027  while ( !feof( $fp ) ) {
3028  if ( $lineCallback ) {
3029  call_user_func( $lineCallback );
3030  }
3031 
3032  $line = trim( fgets( $fp ) );
3033 
3034  if ( $line == '' ) {
3035  continue;
3036  }
3037 
3038  if ( '-' == $line[0] && '-' == $line[1] ) {
3039  continue;
3040  }
3041 
3042  if ( $cmd != '' ) {
3043  $cmd .= ' ';
3044  }
3045 
3046  $done = $this->streamStatementEnd( $cmd, $line );
3047 
3048  $cmd .= "$line\n";
3049 
3050  if ( $done || feof( $fp ) ) {
3051  $cmd = $this->replaceVars( $cmd );
3052 
3053  if ( ( $inputCallback && call_user_func( $inputCallback, $cmd ) ) || !$inputCallback ) {
3054  $res = $this->query( $cmd, $fname );
3055 
3056  if ( $resultCallback ) {
3057  call_user_func( $resultCallback, $res, $this );
3058  }
3059 
3060  if ( false === $res ) {
3061  $err = $this->lastError();
3062 
3063  return "Query \"{$cmd}\" failed with error code \"$err\".\n";
3064  }
3065  }
3066  $cmd = '';
3067  }
3068  }
3069 
3070  return true;
3071  }
3072 
3080  public function streamStatementEnd( &$sql, &$newLine ) {
3081  if ( $this->delimiter ) {
3082  $prev = $newLine;
3083  $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
3084  if ( $newLine != $prev ) {
3085  return true;
3086  }
3087  }
3088 
3089  return false;
3090  }
3091 
3112  protected function replaceVars( $ins ) {
3113  $vars = $this->getSchemaVars();
3114  return preg_replace_callback(
3115  '!
3116  /\* (\$wgDBprefix|[_i]) \*/ (\w*) | # 1-2. tableName, indexName
3117  \'\{\$ (\w+) }\' | # 3. addQuotes
3118  `\{\$ (\w+) }` | # 4. addIdentifierQuotes
3119  /\*\$ (\w+) \*/ # 5. leave unencoded
3120  !x',
3121  function ( $m ) use ( $vars ) {
3122  // Note: Because of <https://bugs.php.net/bug.php?id=51881>,
3123  // check for both nonexistent keys *and* the empty string.
3124  if ( isset( $m[1] ) && $m[1] !== '' ) {
3125  if ( $m[1] === 'i' ) {
3126  return $this->indexName( $m[2] );
3127  } else {
3128  return $this->tableName( $m[2] );
3129  }
3130  } elseif ( isset( $m[3] ) && $m[3] !== '' && array_key_exists( $m[3], $vars ) ) {
3131  return $this->addQuotes( $vars[$m[3]] );
3132  } elseif ( isset( $m[4] ) && $m[4] !== '' && array_key_exists( $m[4], $vars ) ) {
3133  return $this->addIdentifierQuotes( $vars[$m[4]] );
3134  } elseif ( isset( $m[5] ) && $m[5] !== '' && array_key_exists( $m[5], $vars ) ) {
3135  return $vars[$m[5]];
3136  } else {
3137  return $m[0];
3138  }
3139  },
3140  $ins
3141  );
3142  }
3143 
3150  protected function getSchemaVars() {
3151  if ( $this->mSchemaVars ) {
3152  return $this->mSchemaVars;
3153  } else {
3154  return $this->getDefaultSchemaVars();
3155  }
3156  }
3157 
3166  protected function getDefaultSchemaVars() {
3167  return [];
3168  }
3169 
3170  public function lockIsFree( $lockName, $method ) {
3171  return true;
3172  }
3173 
3174  public function lock( $lockName, $method, $timeout = 5 ) {
3175  $this->mNamedLocksHeld[$lockName] = 1;
3176 
3177  return true;
3178  }
3179 
3180  public function unlock( $lockName, $method ) {
3181  unset( $this->mNamedLocksHeld[$lockName] );
3182 
3183  return true;
3184  }
3185 
3186  public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
3187  if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
3188  return null;
3189  }
3190 
3191  $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
3192  $this->commit( __METHOD__, 'flush' );
3193  $this->unlock( $lockKey, $fname );
3194  } );
3195 
3196  $this->commit( __METHOD__, 'flush' );
3197 
3198  return $unlocker;
3199  }
3200 
3201  public function namedLocksEnqueue() {
3202  return false;
3203  }
3204 
3214  public function lockTables( $read, $write, $method, $lowPriority = true ) {
3215  return true;
3216  }
3217 
3224  public function unlockTables( $method ) {
3225  return true;
3226  }
3227 
3235  public function dropTable( $tableName, $fName = __METHOD__ ) {
3236  if ( !$this->tableExists( $tableName, $fName ) ) {
3237  return false;
3238  }
3239  $sql = "DROP TABLE " . $this->tableName( $tableName );
3240  if ( $this->cascadingDeletes() ) {
3241  $sql .= " CASCADE";
3242  }
3243 
3244  return $this->query( $sql, $fName );
3245  }
3246 
3253  public function getSearchEngine() {
3254  return 'SearchEngineDummy';
3255  }
3256 
3257  public function getInfinity() {
3258  return 'infinity';
3259  }
3260 
3261  public function encodeExpiry( $expiry ) {
3262  return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
3263  ? $this->getInfinity()
3264  : $this->timestamp( $expiry );
3265  }
3266 
3267  public function decodeExpiry( $expiry, $format = TS_MW ) {
3268  return ( $expiry == '' || $expiry == 'infinity' || $expiry == $this->getInfinity() )
3269  ? 'infinity'
3270  : wfTimestamp( $format, $expiry );
3271  }
3272 
3273  public function setBigSelects( $value = true ) {
3274  // no-op
3275  }
3276 
3277  public function isReadOnly() {
3278  return ( $this->getReadOnlyReason() !== false );
3279  }
3280 
3284  protected function getReadOnlyReason() {
3285  $reason = $this->getLBInfo( 'readOnlyReason' );
3286 
3287  return is_string( $reason ) ? $reason : false;
3288  }
3289 
3294  public function __toString() {
3295  return (string)$this->mConn;
3296  }
3297 
3301  public function __destruct() {
3302  if ( $this->mTrxLevel && $this->mTrxDoneWrites ) {
3303  trigger_error( "Uncommitted DB writes (transaction from {$this->mTrxFname})." );
3304  }
3305  if ( count( $this->mTrxIdleCallbacks ) || count( $this->mTrxPreCommitCallbacks ) ) {
3306  $callers = [];
3307  foreach ( $this->mTrxIdleCallbacks as $callbackInfo ) {
3308  $callers[] = $callbackInfo[1];
3309  }
3310  $callers = implode( ', ', $callers );
3311  trigger_error( "DB transaction callbacks still pending (from $callers)." );
3312  }
3313  }
3314 }
3315 
3319 abstract class Database extends DatabaseBase {
3320  // B/C until nothing type hints for DatabaseBase
3321  // @TODO: finish renaming DatabaseBase => Database
3322 }
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:1940
lowPriorityOption()
A string to insert into queries to show that they're low-priority, like MySQL's LOW_PRIORITY.
Definition: Database.php:2237
ping()
Ping the server and try to reconnect if it there is no connection.
Definition: Database.php:2847
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:2925
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:1681
getScopedLockAndFlush($lockKey, $fname, $timeout)
Acquire a named lock, flush any transaction, and return an RAII style unlocker object.
Definition: Database.php:3186
bitOr($fieldLeft, $fieldRight)
Definition: Database.php:1629
$IP
Definition: WebStart.php:58
aggregateValue($valuedata, $valuename= 'value')
Return aggregated value alias.
Definition: Database.php:1617
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:2994
setSessionOptions(array $options)
Override database's default behavior.
Definition: Database.php:2940
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:3261
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:3301
static instance()
Singleton.
Definition: Profiler.php:60
decodeExpiry($expiry, $format=TS_MW)
Decode an expiry time into a DBMS independent format.
Definition: Database.php:3267
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:2793
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:2451
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:1515
lastError()
Get a description of the last error.
doRollback($fname)
Issues the ROLLBACK command to the database server.
Definition: Database.php:2740
getProperty($name)
General read-only accessor.
Definition: Database.php:420
dropTable($tableName, $fName=__METHOD__)
Delete a table.
Definition: Database.php:3235
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:2959
getDefaultSchemaVars()
Get schema variables to use if none have been set via setSchemaVars().
Definition: Database.php:3166
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:3170
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:2366
encodeBlob($b)
Some DBMSs have a special format for inserting into blob fields, they don't allow simple quoted strin...
Definition: Database.php:2929
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:2762
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:1881
listTables($prefix=null, $fname=__METHOD__)
List all tables on the database.
Definition: Database.php:2769
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:1373
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:2512
useIndexClause($index)
USE INDEX clause.
Definition: Database.php:2046
update($table, $values, $conds, $fname=__METHOD__, $options=[])
UPDATE wrapper.
Definition: Database.php:1503
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:2346
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:2809
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:2441
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:1625
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:1992
anyString()
Returns a token for buildLike() that denotes a '' to be used in a LIKE query.
Definition: Database.php:2028
deadlockLoop()
Perform a deadlock-prone transaction.
Definition: Database.php:2400
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:2649
unlock($lockName, $method)
Release a lock.
Definition: Database.php:3180
doCommit($fname)
Issues the COMMIT command to the database server.
Definition: Database.php:2702
buildLike()
LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match conta...
Definition: Database.php:2004
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:1386
unionSupportsOrderAndLimit()
Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries within th...
Definition: Database.php:2328
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:1473
getSchemaVars()
Get schema variables.
Definition: Database.php:3150
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:1654
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:3224
$res
Definition: database.txt:21
endAtomic($fname=__METHOD__)
Ends an atomic section of SQL statements.
Definition: Database.php:2549
namedLocksEnqueue()
Check to see if a named lock used by lock() use blocking queues.
Definition: Database.php:3201
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:1415
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:2338
$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:3253
addQuotes($s)
Adds quotes and backslashes.
Definition: Database.php:1955
addIdentifierQuotes($s)
Quotes an identifier using backticks or "double quotes" depending on the database type...
Definition: Database.php:1979
bitNot($field)
Definition: Database.php:1621
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:2458
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:2904
lockTables($read, $write, $method, $lowPriority=true)
Lock specific tables.
Definition: Database.php:3214
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:2879
replaceVars($ins)
Database independent variable replacement.
Definition: Database.php:3112
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:1645
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:2362
buildGroupConcatField($delim, $table, $field, $conds= '', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
Definition: Database.php:1637
Database abstraction object.
Definition: Database.php:32
indexUnique($table, $index)
Determines if a given index is unique.
Definition: Database.php:1395
wasConnectionError($errno)
Determines if the given query error was a connection drop STUB.
Definition: Database.php:2377
$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:2471
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:2332
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:3273
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:2777
wasDeadlock()
Determines if the last failure was due to a deadlock STUB.
Definition: Database.php:2354
__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:2132
textFieldSize($table, $field)
Returns the size of a text field, or -1 for "unlimited".
Definition: Database.php:2212
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:1658
static getLocalServerInstance($fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
begin($fname=__METHOD__)
Begin a transaction.
Definition: Database.php:2579
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:2024
$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:2813
reportQueryError($error, $errno, $sql, $fname, $tempIgnore=false)
Report a query error.
Definition: Database.php:918
makeInsertOptions($options)
Helper for DatabaseBase::insert().
Definition: Database.php:1411
nextSequenceValue($seqName)
Returns an appropriately quoted sequence value for inserting a new row.
Definition: Database.php:2032
$wgDBprefix
Table name prefix.
getTransactionLagStatus()
Get the slave lag when the current transaction started.
Definition: Database.php:2867
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:1345
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:3174
makeUpdateOptions($options)
Make UPDATE options for the DatabaseBase::update function.
Definition: Database.php:1497
const DBO_DEFAULT
Definition: Defines.php:34
getServerUptime()
Determines how long the server has been up STUB.
Definition: Database.php:2350
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:2654
masterPosWait(DBMasterPos $pos, $timeout)
Wait for the slave to catch up to a given master position.
Definition: Database.php:2436
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:3257
startAtomic($fname=__METHOD__)
Begin an atomic section of statements.
Definition: Database.php:2535
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:2446
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
Definition: Database.php:3080
rollback($fname=__METHOD__, $flush= '')
Rollback a transaction previously started using begin().
Definition: Database.php:2709
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:1590
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:2805
deleteJoin($delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
DELETE where the condition is a join.
Definition: Database.php:2186
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:2000
$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:3005
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:2108
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:2834
replace($table, $uniqueIndexes, $rows, $fname=__METHOD__)
REPLACE query wrapper.
Definition: Database.php:2050
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:2933
getSessionLagStatus()
Get the slave lag when the current transaction started or a general lag estimate if not transaction i...
Definition: Database.php:2852
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:2259
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:1633
doAtomicSection($fname, $callback)
Run a callback to do an atomic set of updates for this database.
Definition: Database.php:2564
sourceStream($fp, $lineCallback=false, $resultCallback=false, $fname=__METHOD__, $inputCallback=false)
Read and execute commands from an open file handle.
Definition: Database.php:3022
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:2318
fieldNamesWithAlias($fields)
Gets an array of aliased field names.
Definition: Database.php:1860
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:1367
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:2921
wasLockTimeout()
Determines if the last failure was due to a lock timeout STUB.
Definition: Database.php:2358
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310