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