MediaWiki master
Database.php
Go to the documentation of this file.
1<?php
6namespace Wikimedia\Rdbms;
7
8use Exception;
9use InvalidArgumentException;
10use LogicException;
11use Psr\Log\LoggerAwareInterface;
12use Psr\Log\LoggerInterface;
13use Psr\Log\NullLogger;
14use RuntimeException;
15use Stringable;
16use Throwable;
20use Wikimedia\RequestTimeout\CriticalSectionProvider;
21use Wikimedia\RequestTimeout\CriticalSectionScope;
22use Wikimedia\ScopedCallback;
26use Wikimedia\Timestamp\TimestampFormat as TS;
27
38abstract class Database implements Stringable, IDatabaseForOwner, IMaintainableDatabase, LoggerAwareInterface {
40 protected $csProvider;
42 protected $logger;
44 protected $errorLogger;
48 protected $profiler;
50 private $tracer;
52 private $transactionManager;
53
55 protected $currentDomain;
57 protected $flagsHolder;
58
59 // phpcs:ignore MediaWiki.Commenting.PropertyDocumentation.ObjectTypeHintVar
61 protected $conn;
62
64 protected $serverName;
66 protected $cliMode;
68 protected $connectTimeout;
70 protected $receiveTimeout;
72 protected $agent;
79
81 protected $ssl;
83 protected $strictWarnings;
85 protected $lbInfo = [];
87 protected $delimiter = ';';
88
90 private $htmlErrors;
91
93 protected $sessionNamedLocks = [];
95 protected $sessionTempTables = [];
96
98 protected $lastQueryAffectedRows = 0;
100 protected $lastQueryInsertId;
101
103 protected $lastEmulatedAffectedRows;
105 protected $lastEmulatedInsertId;
106
108 protected $lastConnectError = '';
109
111 private $lastPing = 0.0;
113 private $lastWriteTime;
115 private $lastPhpError = false;
116
118 private $csmId;
120 private $csmFname;
122 private $csmError;
123
125 public const ATTR_DB_IS_FILE = 'db-is-file';
127 public const ATTR_DB_LEVEL_LOCKING = 'db-level-locking';
129 public const ATTR_SCHEMAS_AS_TABLE_GROUPS = 'supports-schemas';
130
132 public const NEW_UNCONNECTED = 0;
134 public const NEW_CONNECTED = 1;
135
137 protected const ERR_NONE = 0;
139 protected const ERR_RETRY_QUERY = 1;
141 protected const ERR_ABORT_QUERY = 2;
143 protected const ERR_ABORT_TRX = 4;
145 protected const ERR_ABORT_SESSION = 8;
146
148 protected const DROPPED_CONN_BLAME_THRESHOLD_SEC = 3.0;
149
151 private const NOT_APPLICABLE = 'n/a';
152
154 private const PING_TTL = 1.0;
156 private const PING_QUERY = 'SELECT 1 AS ping';
157
159 protected const CONN_HOST = 'host';
161 protected const CONN_USER = 'user';
163 protected const CONN_PASSWORD = 'password';
165 protected const CONN_INITIAL_DB = 'dbname';
167 protected const CONN_INITIAL_SCHEMA = 'schema';
169 protected const CONN_INITIAL_TABLE_PREFIX = 'tablePrefix';
170
172 protected $platform;
173
175 protected $replicationReporter;
176
181 public function __construct( array $params ) {
182 $this->logger = $params['logger'] ?? new NullLogger();
184 $this->logger,
185 $params['trxProfiler']
186 );
188 self::CONN_HOST => ( isset( $params['host'] ) && $params['host'] !== '' )
189 ? $params['host']
190 : null,
191 self::CONN_USER => ( isset( $params['user'] ) && $params['user'] !== '' )
192 ? $params['user']
193 : null,
194 self::CONN_INITIAL_DB => ( isset( $params['dbname'] ) && $params['dbname'] !== '' )
195 ? $params['dbname']
196 : null,
197 self::CONN_INITIAL_SCHEMA => ( isset( $params['schema'] ) && $params['schema'] !== '' )
198 ? $params['schema']
199 : null,
200 self::CONN_PASSWORD => is_string( $params['password'] ) ? $params['password'] : null,
201 self::CONN_INITIAL_TABLE_PREFIX => (string)$params['tablePrefix']
202 ];
203
204 $this->lbInfo = $params['lbInfo'] ?? [];
205 $this->connectionVariables = $params['variables'] ?? [];
206 // Set SQL mode, default is turning them all off, can be overridden or skipped with null
207 if ( is_string( $params['sqlMode'] ?? null ) ) {
208 $this->connectionVariables['sql_mode'] = $params['sqlMode'];
209 }
210 $flags = (int)$params['flags'];
211 $this->flagsHolder = new DatabaseFlags( $flags );
212 $this->ssl = $params['ssl'] ?? (bool)( $flags & self::DBO_SSL );
213 $this->connectTimeout = $params['connectTimeout'] ?? null;
214 $this->receiveTimeout = $params['receiveTimeout'] ?? null;
215 $this->cliMode = (bool)$params['cliMode'];
216 $this->agent = (string)$params['agent'];
217 $this->serverName = $params['serverName'];
218 $this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'] ?? 10000;
219 $this->strictWarnings = !empty( $params['strictWarnings'] );
220
221 $this->profiler = is_callable( $params['profiler'] ) ? $params['profiler'] : null;
222 $this->errorLogger = $params['errorLogger'];
223 $this->deprecationLogger = $params['deprecationLogger'];
224
225 $this->csProvider = $params['criticalSectionProvider'] ?? null;
226
227 // Set initial dummy domain until open() sets the final DB/prefix
229 $params['dbname'] != '' ? $params['dbname'] : null,
230 $params['schema'] != '' ? $params['schema'] : null,
231 $params['tablePrefix']
232 );
233 $this->platform = new SQLPlatform(
234 $this,
235 $this->logger,
236 $this->currentDomain,
237 $this->errorLogger
238 );
239 $this->tracer = $params['tracer'] ?? new NoopTracer();
240 // Children classes must set $this->replicationReporter.
241 }
242
251 final public function initConnection() {
252 if ( $this->isOpen() ) {
253 throw new LogicException( __METHOD__ . ': already connected' );
254 }
255 // Establish the connection
256 $this->open(
257 $this->connectionParams[self::CONN_HOST],
258 $this->connectionParams[self::CONN_USER],
259 $this->connectionParams[self::CONN_PASSWORD],
260 $this->connectionParams[self::CONN_INITIAL_DB],
261 $this->connectionParams[self::CONN_INITIAL_SCHEMA],
262 $this->connectionParams[self::CONN_INITIAL_TABLE_PREFIX]
263 );
264 $this->lastPing = microtime( true );
265 }
266
278 abstract protected function open( $server, $user, $password, $db, $schema, $tablePrefix );
279
284 public static function getAttributes() {
285 return [];
286 }
287
291 public function setLogger( LoggerInterface $logger ): void {
292 $this->logger = $logger;
293 }
294
296 public function getServerInfo() {
297 return $this->getServerVersion();
298 }
299
301 public function tablePrefix( $prefix = null ) {
302 $old = $this->currentDomain->getTablePrefix();
303
304 if ( $prefix !== null ) {
305 $this->currentDomain = new DatabaseDomain(
306 $this->currentDomain->getDatabase(),
307 $this->currentDomain->getSchema(),
308 $prefix
309 );
310 $this->platform->setCurrentDomain( $this->currentDomain );
311 }
312
313 return $old;
314 }
315
317 public function dbSchema( $schema = null ) {
318 $old = $this->currentDomain->getSchema();
319
320 if ( $schema !== null ) {
321 if ( $schema !== '' && $this->getDBname() === null ) {
322 throw new DBUnexpectedError(
323 $this,
324 "Cannot set schema to '$schema'; no database set"
325 );
326 }
327
328 $this->currentDomain = new DatabaseDomain(
329 $this->currentDomain->getDatabase(),
330 // DatabaseDomain uses null for unspecified schemas
331 ( $schema !== '' ) ? $schema : null,
332 $this->currentDomain->getTablePrefix()
333 );
334 $this->platform->setCurrentDomain( $this->currentDomain );
335 }
336
337 return (string)$old;
338 }
339
341 public function getLBInfo( $name = null ) {
342 if ( $name === null ) {
343 return $this->lbInfo;
344 }
345
346 if ( array_key_exists( $name, $this->lbInfo ) ) {
347 return $this->lbInfo[$name];
348 }
349
350 return null;
351 }
352
354 public function setLBInfo( $nameOrArray, $value = null ) {
355 if ( is_array( $nameOrArray ) ) {
356 $this->lbInfo = $nameOrArray;
357 } elseif ( is_string( $nameOrArray ) ) {
358 if ( $value !== null ) {
359 $this->lbInfo[$nameOrArray] = $value;
360 } else {
361 unset( $this->lbInfo[$nameOrArray] );
362 }
363 } else {
364 throw new InvalidArgumentException( "Got non-string key" );
365 }
366 }
367
369 public function lastDoneWrites() {
370 return $this->lastWriteTime;
371 }
372
378 public function sessionLocksPending() {
379 return (bool)$this->sessionNamedLocks;
380 }
381
385 final protected function getTransactionRoundFname() {
386 if ( $this->flagsHolder->hasImplicitTrxFlag() ) {
387 // LoadBalancer transaction round participation is enabled for this DB handle;
388 // get the owner of the active explicit transaction round (if any)
389 return $this->getLBInfo( self::LB_TRX_ROUND_FNAME );
390 }
391
392 return null;
393 }
394
396 public function isOpen() {
397 return (bool)$this->conn;
398 }
399
401 public function getDomainID() {
402 return $this->currentDomain->getId();
403 }
404
411 abstract public function strencode( $s );
412
416 protected function installErrorHandler() {
417 $this->lastPhpError = false;
418 $this->htmlErrors = ini_set( 'html_errors', '0' );
419 set_error_handler( $this->connectionErrorLogger( ... ) );
420 }
421
427 protected function restoreErrorHandler() {
428 restore_error_handler();
429 if ( $this->htmlErrors !== false ) {
430 ini_set( 'html_errors', $this->htmlErrors );
431 }
432
433 return $this->getLastPHPError();
434 }
435
439 protected function getLastPHPError() {
440 if ( $this->lastPhpError ) {
441 $error = preg_replace( '!\[<a.*</a>\]!', '', $this->lastPhpError );
442 $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
443
444 return $error;
445 }
446
447 return false;
448 }
449
458 public function connectionErrorLogger( $errno, $errstr ) {
459 $this->lastPhpError = $errstr;
460 }
461
468 protected function getLogContext( array $extras = [] ) {
469 return $extras + [
470 'db_server' => $this->getServerName(),
471 'db_name' => $this->getDBname(),
472 'db_user' => $this->connectionParams[self::CONN_USER] ?? null,
473 ];
474 }
475
477 final public function close( $fname = __METHOD__ ) {
478 $error = null; // error to throw after disconnecting
479
480 $wasOpen = (bool)$this->conn;
481 // This should mostly do nothing if the connection is already closed
482 if ( $this->conn ) {
483 // Roll back any dangling transaction first
484 if ( $this->trxLevel() ) {
485 $error = $this->transactionManager->trxCheckBeforeClose( $this, $fname );
486 // Rollback the changes and run any callbacks as needed
487 $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
488 $this->runTransactionPostRollbackCallbacks();
489 }
490
491 // Close the actual connection in the binding handle
492 $closed = $this->closeConnection();
493 } else {
494 $closed = true; // already closed; nothing to do
495 }
496
497 $this->conn = null;
498
499 // Log any unexpected errors after having disconnected
500 if ( $error !== null ) {
501 // T217819, T231443: this is probably just LoadBalancer trying to recover from
502 // errors and shutdown. Log any problems and move on since the request has to
503 // end one way or another. Throwing errors is not very useful at some point.
504 $this->logger->error( $error, [ 'db_log_category' => 'query' ] );
505 }
506
507 // Note that various subclasses call close() at the start of open(), which itself is
508 // called by replaceLostConnection(). In that case, just because onTransactionResolution()
509 // callbacks are pending does not mean that an exception should be thrown. Rather, they
510 // will be executed after the reconnection step.
511 if ( $wasOpen ) {
512 // Double check that no callbacks are dangling
513 $fnames = $this->pendingWriteAndCallbackCallers();
514 if ( $fnames ) {
515 throw new RuntimeException(
516 "Transaction callbacks are still pending: " . implode( ', ', $fnames )
517 );
518 }
519 }
520
521 return $closed;
522 }
523
532 final protected function assertHasConnectionHandle() {
533 if ( !$this->isOpen() ) {
534 throw new DBUnexpectedError( $this, "DB connection was already closed" );
535 }
536 }
537
543 abstract protected function closeConnection();
544
572 abstract protected function doSingleStatementQuery( string $sql ): QueryStatus;
573
581 private function hasPermanentTable( Query $query ) {
582 if ( $query->getVerb() === 'CREATE TEMPORARY' ) {
583 // Temporary table creation is allowed
584 return false;
585 }
586 $table = $query->getWriteTable();
587 if ( $table === null ) {
588 // Parse error? Assume permanent.
589 return true;
590 }
591 [ $db, $pt ] = $this->platform->getDatabaseAndTableIdentifier( $table );
592 $tempInfo = $this->sessionTempTables[$db][$pt] ?? null;
593 return !$tempInfo || $tempInfo->pseudoPermanent;
594 }
595
599 protected function registerTempTables( Query $query ) {
600 $table = $query->getWriteTable();
601 if ( $table === null ) {
602 return;
603 }
604 switch ( $query->getVerb() ) {
605 case 'CREATE TEMPORARY':
606 [ $db, $pt ] = $this->platform->getDatabaseAndTableIdentifier( $table );
607 $this->sessionTempTables[$db][$pt] = new TempTableInfo(
608 $this->transactionManager->getTrxId(),
609 (bool)( $query->getFlags() & self::QUERY_PSEUDO_PERMANENT )
610 );
611 break;
612
613 case 'DROP':
614 [ $db, $pt ] = $this->platform->getDatabaseAndTableIdentifier( $table );
615 unset( $this->sessionTempTables[$db][$pt] );
616 }
617 }
618
620 public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
621 if ( !( $sql instanceof Query ) ) {
622 $flags = (int)$flags; // b/c; this field used to be a bool
623 $sql = QueryBuilderFromRawSql::buildQuery( $sql, $flags, $this->currentDomain->getTablePrefix() );
624 } else {
625 $flags = $sql->getFlags();
626 }
627
628 // Make sure that this caller is allowed to issue this query statement
629 $this->assertQueryIsCurrentlyAllowed( $sql->getVerb(), $fname );
630
631 // Send the query to the server and fetch any corresponding errors
632 $status = $this->executeQuery( $sql, $fname, $flags );
633 if ( $status->res === false ) {
634 // An error occurred; log, and, if needed, report an exception.
635 // Errors that corrupt the transaction/session state cannot be silenced.
636 $ignore = (
637 $this->flagsHolder::contains( $flags, self::QUERY_SILENCE_ERRORS ) &&
638 !$this->flagsHolder::contains( $status->flags, self::ERR_ABORT_SESSION ) &&
639 !$this->flagsHolder::contains( $status->flags, self::ERR_ABORT_TRX )
640 );
641 $this->reportQueryError( $status->message, $status->code, $sql->getSQL(), $fname, $ignore );
642 }
643
644 return $status->res;
645 }
646
663 final protected function executeQuery( $sql, $fname, $flags ) {
664 $this->assertHasConnectionHandle();
665
666 $isPermWrite = false;
667 $isWrite = $sql->isWriteQuery();
668 if ( $isWrite ) {
669 ChangedTablesTracker::recordQuery( $this->currentDomain, $sql );
670 // Permit temporary table writes on replica connections, but require a writable
671 // master connection for writes to persistent tables.
672 if ( $this->hasPermanentTable( $sql ) ) {
673 $isPermWrite = true;
674 $info = $this->getReadOnlyReason();
675 if ( $info ) {
676 [ $reason, $source ] = $info;
677 if ( $source === 'role' ) {
678 throw new DBReadOnlyRoleError( $this, "Database is read-only: $reason" );
679 } else {
680 throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
681 }
682 }
683 // DBConnRef uses QUERY_REPLICA_ROLE to enforce replica roles during query()
684 if ( $this->flagsHolder::contains( $sql->getFlags(), self::QUERY_REPLICA_ROLE ) ) {
685 throw new DBReadOnlyRoleError(
686 $this,
687 "Cannot write; target role is DB_REPLICA"
688 );
689 }
690 }
691 }
692
693 // Whether a silent retry attempt is left for recoverable connection loss errors
694 $retryLeft = !$this->flagsHolder::contains( $flags, self::QUERY_NO_RETRY );
695
696 $cs = $this->commenceCriticalSection( __METHOD__ );
697
698 do {
699 // Start a DBO_TRX wrapper transaction as needed (throw an error on failure)
700 if ( $this->beginIfImplied( $sql, $fname, $flags ) ) {
701 // Since begin() was called, any connection loss was already handled
702 $retryLeft = false;
703 }
704 // Send the query statement to the server and fetch any results.
705 $status = $this->attemptQuery( $sql, $fname, $isPermWrite );
706 } while (
707 // An error occurred that can be recovered from via query retry
708 $this->flagsHolder::contains( $status->flags, self::ERR_RETRY_QUERY ) &&
709 // The retry has not been exhausted (consume it now)
710 // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
711 $retryLeft && !( $retryLeft = false )
712 );
713
714 // Register creation and dropping of temporary tables
715 if ( $status->res ) {
716 $this->registerTempTables( $sql );
717 }
718 $this->completeCriticalSection( __METHOD__, $cs );
719
720 return $status;
721 }
722
744 private function attemptQuery(
745 $sql,
746 string $fname,
747 bool $isPermWrite
748 ) {
749 // Transaction attributes before issuing this query
750 $priorSessInfo = new CriticalSessionInfo(
751 $this->transactionManager->getTrxId(),
752 $this->transactionManager->explicitTrxActive(),
753 $this->transactionManager->pendingWriteCallers(),
754 $this->transactionManager->pendingPreCommitCallbackCallers(),
755 $this->sessionNamedLocks,
756 $this->sessionTempTables
757 );
758 // Get the transaction-aware SQL string used for profiling
759 $generalizedSql = GeneralizedSql::newFromQuery(
760 $sql,
761 ( $this->replicationReporter->getTopologyRole() === self::ROLE_STREAMING_MASTER )
762 ? 'role-primary: '
763 : ''
764 );
765 // Add agent and calling method comments to the SQL
766 $cStatement = $this->makeCommentedSql( $sql->getSQL(), $fname );
767 // Start profile section
768 $ps = $this->profiler ? ( $this->profiler )( $generalizedSql->stringify() ) : null;
769 $startTime = microtime( true );
770
771 // Clear any overrides from a prior "query method". Note that this does not affect
772 // any such methods that are currently invoking query() itself since those query
773 // methods set these fields before returning.
774 $this->lastEmulatedAffectedRows = null;
775 $this->lastEmulatedInsertId = null;
776
777 // Record an OTEL span for this query.
778 $writeTableName = $sql->getWriteTable();
779 $spanName = $writeTableName ?
780 "Database {$sql->getVerb()} {$this->getDBname()}.{$writeTableName}" :
781 "Database {$sql->getVerb()} {$this->getDBname()}";
782 $span = $this->tracer->createSpan( $spanName )
783 ->setSpanKind( SpanInterface::SPAN_KIND_CLIENT )
784 ->start();
785 if ( $span->getContext()->isSampled() ) {
786 $span->setAttributes( [
787 'code.function' => $fname,
788 'db.namespace' => $this->getDBname(),
789 'db.operation.name' => $sql->getVerb(),
790 'db.query.text' => $generalizedSql->stringify(),
791 'db.system' => $this->getType(),
792 'server.address' => $this->getServerName(),
793 'db.collection.name' => $writeTableName, # nulls filtered out
794 ] );
795 }
796
797 $status = $this->doSingleStatementQuery( $cStatement );
798
799 // End profile section
800 $endTime = microtime( true );
801 $queryRuntime = max( $endTime - $startTime, 0.0 );
802 unset( $ps );
803 $span->end();
804
805 if ( $status->res !== false ) {
806 $this->lastPing = $endTime;
807 $span->setSpanStatus( SpanInterface::SPAN_STATUS_OK );
808 } else {
809 $span->setSpanStatus( SpanInterface::SPAN_STATUS_ERROR )
810 ->setAttributes( [
811 'db.response.status_code' => $status->code,
812 'exception.message' => $status->message,
813 ] );
814 }
815
816 $affectedRowCount = $status->rowsAffected;
817 $returnedRowCount = $status->rowsReturned;
818 $this->lastQueryAffectedRows = $affectedRowCount;
819
820 if ( $span->getContext()->isSampled() ) {
821 $span->setAttributes( [
822 'db.response.affected_rows' => $affectedRowCount,
823 'db.response.returned_rows' => $returnedRowCount,
824 ] );
825 }
826
827 if ( $status->res !== false ) {
828 if ( $isPermWrite ) {
829 if ( $this->trxLevel() ) {
830 $this->transactionManager->transactionWritingIn(
831 $this->getServerName(),
832 $this->getDomainID(),
833 $startTime
834 );
835 $this->transactionManager->updateTrxWriteQueryReport(
836 $sql->getSQL(),
837 $queryRuntime,
838 $affectedRowCount,
839 $fname
840 );
841 } else {
842 $this->lastWriteTime = $endTime;
843 }
844 }
845 }
846
847 $this->transactionManager->recordQueryCompletion(
848 $generalizedSql,
849 $startTime,
850 $isPermWrite,
851 $isPermWrite ? $affectedRowCount : $returnedRowCount,
852 $this->getServerName()
853 );
854
855 // Check if the query failed...
856 $status->flags = $this->handleErroredQuery( $status, $sql, $fname, $queryRuntime, $priorSessInfo );
857 // Avoid the overhead of logging calls unless debug mode is enabled
858 if ( $this->flagsHolder->getFlag( self::DBO_DEBUG ) ) {
859 $this->logger->debug(
860 "{method} [{runtime_ms}ms] {db_server}: {sql}",
861 $this->getLogContext( [
862 'method' => $fname,
863 'sql' => $sql->getSQL(),
864 'domain' => $this->getDomainID(),
865 'runtime_ms' => round( $queryRuntime * 1000, 3 ),
866 'db_log_category' => 'query'
867 ] )
868 );
869 }
870
871 return $status;
872 }
873
874 private function handleErroredQuery(
875 QueryStatus $status, Query $sql, string $fname, float $queryRuntime, CriticalSessionInfo $priorSessInfo
876 ): int {
877 $errflags = self::ERR_NONE;
878 $error = $status->message;
879 $errno = $status->code;
880 if ( $status->res !== false ) {
881 // Statement succeeded
882 return $errflags;
883 }
884 if ( $this->isConnectionError( $errno ) ) {
885 // Connection lost before or during the query...
886 // Determine how to proceed given the lost session state
887 $connLossFlag = $this->assessConnectionLoss(
888 $sql->getVerb(),
889 $queryRuntime,
890 $priorSessInfo
891 );
892 // Update session state tracking and try to reestablish a connection
893 $reconnected = $this->replaceLostConnection( $errno, __METHOD__ );
894 // Check if important server-side session-level state was lost
895 if ( $connLossFlag >= self::ERR_ABORT_SESSION ) {
896 $ex = $this->getQueryException( $error, $errno, $sql->getSQL(), $fname );
897 $this->transactionManager->setSessionError( $ex );
898 }
899 // Check if important server-side transaction-level state was lost
900 if ( $connLossFlag >= self::ERR_ABORT_TRX ) {
901 $ex = $this->getQueryException( $error, $errno, $sql->getSQL(), $fname );
902 $this->transactionManager->setTransactionError( $ex );
903 }
904 // Check if the query should be retried (having made the reconnection attempt)
905 if ( $connLossFlag === self::ERR_RETRY_QUERY ) {
906 $errflags |= ( $reconnected ? self::ERR_RETRY_QUERY : self::ERR_ABORT_QUERY );
907 } else {
908 $errflags |= $connLossFlag;
909 }
910 } elseif ( $this->isKnownStatementRollbackError( $errno ) ) {
911 // Query error triggered a server-side statement-only rollback...
912 $errflags |= self::ERR_ABORT_QUERY;
913 if ( $this->trxLevel() ) {
914 // Allow legacy callers to ignore such errors via QUERY_IGNORE_DBO_TRX and
915 // try/catch. However, a deprecation notice will be logged on the next query.
916 $cause = [ $error, $errno, $fname ];
917 $this->transactionManager->setTrxStatusIgnoredCause( $cause );
918 }
919 } elseif ( $this->trxLevel() ) {
920 // Some other error occurred during the query, within a transaction...
921 // Server-side handling of errors during transactions varies widely depending on
922 // the RDBMS type and configuration. There are several possible results: (a) the
923 // whole transaction is rolled back, (b) only the queries after BEGIN are rolled
924 // back, (c) the transaction is marked as "aborted" and a ROLLBACK is required
925 // before other queries are permitted. For compatibility reasons, pessimistically
926 // require a ROLLBACK query (not using SAVEPOINT) before allowing other queries.
927 $ex = $this->getQueryException( $error, $errno, $sql->getSQL(), $fname );
928 $this->transactionManager->setTransactionError( $ex );
929 $errflags |= self::ERR_ABORT_TRX;
930 } else {
931 // Some other error occurred during the query, without a transaction...
932 $errflags |= self::ERR_ABORT_QUERY;
933 }
934
935 return $errflags;
936 }
937
943 private function makeCommentedSql( $sql, $fname ): string {
944 // Add trace comment to the begin of the sql string, right after the operator.
945 // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598).
946 // NOTE: Don't add varying ids such as request id or session id to the comment.
947 // It would break aggregation of similar queries in analysis tools (see T193050#7512149)
948 $encName = preg_replace( '/[\x00-\x1F\/]/', '-', "$fname {$this->agent}" );
949 return preg_replace( '/\s|$/', " /* $encName */ ", $sql, 1 );
950 }
951
961 private function beginIfImplied( $sql, $fname, $flags ) {
962 if ( !$this->trxLevel() && $this->flagsHolder->hasApplicableImplicitTrxFlag( $flags ) ) {
963 if ( $this->platform->isTransactableQuery( $sql ) ) {
964 $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
965 $this->transactionManager->turnOnAutomatic();
966
967 return true;
968 }
969 }
970
971 return false;
972 }
973
989 private function assertQueryIsCurrentlyAllowed( string $verb, string $fname ) {
990 if ( $verb === 'USE' ) {
991 throw new DBUnexpectedError( $this, "Got USE query; use selectDomain() instead" );
992 }
993
994 if ( $verb === 'ROLLBACK' ) {
995 // Whole transaction rollback is used for recovery
996 // @TODO: T269161; prevent "BEGIN"/"COMMIT"/"ROLLBACK" from outside callers
997 return;
998 }
999
1000 if ( $this->csmError ) {
1001 throw new DBTransactionStateError(
1002 $this,
1003 "Cannot execute query from $fname while session state is out of sync",
1004 [],
1005 $this->csmError
1006 );
1007 }
1008
1009 $this->transactionManager->assertSessionStatus( $this, $fname );
1010
1011 if ( $verb !== 'ROLLBACK TO SAVEPOINT' ) {
1012 $this->transactionManager->assertTransactionStatus(
1013 $this,
1014 $this->deprecationLogger,
1015 $fname
1016 );
1017 }
1018 }
1019
1039 private function assessConnectionLoss(
1040 string $verb,
1041 float $walltime,
1042 CriticalSessionInfo $priorSessInfo
1043 ) {
1044 if ( $walltime < self::DROPPED_CONN_BLAME_THRESHOLD_SEC ) {
1045 // Query failed quickly; the connection was probably lost before the query was sent
1046 $res = self::ERR_RETRY_QUERY;
1047 } else {
1048 // Query took a long time; the connection was probably lost during query execution
1049 $res = self::ERR_ABORT_QUERY;
1050 }
1051
1052 // List of problems causing session/transaction state corruption
1053 $blockers = [];
1054 // Loss of named locks breaks future callers relying on those locks for critical sections
1055 foreach ( $priorSessInfo->namedLocks as $lockName => $lockInfo ) {
1056 if ( $lockInfo['trxId'] && $lockInfo['trxId'] === $priorSessInfo->trxId ) {
1057 // Treat lost locks acquired during the lost transaction as a transaction state
1058 // problem. Connection loss on ROLLBACK (non-SAVEPOINT) is tolerable since
1059 // rollback automatically triggered server-side.
1060 if ( $verb !== 'ROLLBACK' ) {
1061 $res = max( $res, self::ERR_ABORT_TRX );
1062 $blockers[] = "named lock '$lockName'";
1063 }
1064 } else {
1065 // Treat lost locks acquired either during prior transactions or during no
1066 // transaction as a session state problem.
1067 $res = max( $res, self::ERR_ABORT_SESSION );
1068 $blockers[] = "named lock '$lockName'";
1069 }
1070 }
1071 // Loss of temp tables breaks future callers relying on those tables for queries
1072 foreach ( $priorSessInfo->tempTables as $domainTempTables ) {
1073 foreach ( $domainTempTables as $tableName => $tableInfo ) {
1074 if ( $tableInfo->trxId && $tableInfo->trxId === $priorSessInfo->trxId ) {
1075 // Treat lost temp tables created during the lost transaction as a
1076 // transaction state problem. Connection loss on ROLLBACK (non-SAVEPOINT)
1077 // is tolerable since rollback automatically triggered server-side.
1078 if ( $verb !== 'ROLLBACK' ) {
1079 $res = max( $res, self::ERR_ABORT_TRX );
1080 $blockers[] = "temp table '$tableName'";
1081 }
1082 } else {
1083 // Treat lost temp tables created either during prior transactions or during
1084 // no transaction as a session state problem.
1085 $res = max( $res, self::ERR_ABORT_SESSION );
1086 $blockers[] = "temp table '$tableName'";
1087 }
1088 }
1089 }
1090 // Loss of transaction writes breaks future callers and DBO_TRX logic relying on those
1091 // writes to be atomic and still pending. Connection loss on ROLLBACK (non-SAVEPOINT) is
1092 // tolerable since rollback automatically triggered server-side.
1093 if ( $priorSessInfo->trxWriteCallers && $verb !== 'ROLLBACK' ) {
1094 $res = max( $res, self::ERR_ABORT_TRX );
1095 $blockers[] = 'uncommitted writes';
1096 }
1097 if ( $priorSessInfo->trxPreCommitCbCallers && $verb !== 'ROLLBACK' ) {
1098 $res = max( $res, self::ERR_ABORT_TRX );
1099 $blockers[] = 'pre-commit callbacks';
1100 }
1101 if ( $priorSessInfo->trxExplicit && $verb !== 'ROLLBACK' && $verb !== 'COMMIT' ) {
1102 // Transaction automatically rolled back, breaking the expectations of callers
1103 // relying on the continued existence of that transaction for things like atomic
1104 // writes, serializability, or reads from the same point-in-time snapshot. If the
1105 // connection loss occured on ROLLBACK (non-SAVEPOINT) or COMMIT, then we do not
1106 // need to mark the transaction state as corrupt, since no transaction would still
1107 // be open even if the query did succeed (T127428).
1108 $res = max( $res, self::ERR_ABORT_TRX );
1109 $blockers[] = 'explicit transaction';
1110 }
1111
1112 if ( $blockers ) {
1113 $this->logger->warning(
1114 "cannot reconnect to {db_server} silently: {error}",
1115 $this->getLogContext( [
1116 'error' => 'session state loss (' . implode( ', ', $blockers ) . ')',
1117 'exception' => new RuntimeException(),
1118 'db_log_category' => 'connection'
1119 ] )
1120 );
1121 }
1122
1123 return $res;
1124 }
1125
1129 private function handleSessionLossPreconnect() {
1130 // Clean up tracking of session-level things...
1131 // https://mariadb.com/kb/en/create-table/#create-temporary-table
1132 // https://www.postgresql.org/docs/9.2/static/sql-createtable.html (ignoring ON COMMIT)
1133 $this->sessionTempTables = [];
1134 // https://mariadb.com/kb/en/get_lock/
1135 // https://www.postgresql.org/docs/9.4/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1136 $this->sessionNamedLocks = [];
1137 // Session loss implies transaction loss (T67263)
1138 $this->transactionManager->onSessionLoss( $this );
1139 // Clear additional subclass fields
1140 $this->doHandleSessionLossPreconnect();
1141 }
1142
1146 protected function doHandleSessionLossPreconnect() {
1147 // no-op
1148 }
1149
1160 protected function isQueryTimeoutError( $errno ) {
1161 return false;
1162 }
1163
1177 public function reportQueryError( $error, $errno, $sql, $fname, $ignore = false ) {
1178 if ( $ignore ) {
1179 $this->logger->debug(
1180 "SQL ERROR (ignored): $error",
1181 [ 'db_log_category' => 'query' ]
1182 );
1183 } else {
1184 throw $this->getQueryExceptionAndLog( $error, $errno, $sql, $fname );
1185 }
1186 }
1187
1195 private function getQueryExceptionAndLog( $error, $errno, $sql, $fname ) {
1196 // Information that instances of the same problem have in common should
1197 // not be normalized (T255202).
1198 $this->logger->error(
1199 "Error $errno from $fname, {error} {sql1line} {db_server}",
1200 $this->getLogContext( [
1201 'method' => __METHOD__,
1202 'errno' => $errno,
1203 'error' => $error,
1204 'sql1line' => mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 ),
1205 'fname' => $fname,
1206 'db_log_category' => 'query',
1207 'exception' => new RuntimeException()
1208 ] )
1209 );
1210 return $this->getQueryException( $error, $errno, $sql, $fname );
1211 }
1212
1220 private function getQueryException( $error, $errno, $sql, $fname ) {
1221 if ( $this->isQueryTimeoutError( $errno ) ) {
1222 return new DBQueryTimeoutError( $this, $error, $errno, $sql, $fname );
1223 } elseif ( $this->isConnectionError( $errno ) ) {
1224 return new DBQueryDisconnectedError( $this, $error, $errno, $sql, $fname );
1225 } else {
1226 return new DBQueryError( $this, $error, $errno, $sql, $fname );
1227 }
1228 }
1229
1234 final protected function newExceptionAfterConnectError( $error ) {
1235 // Connection was not fully initialized and is not safe for use.
1236 // Stash any error associated with the handle before destroying it.
1237 $this->lastConnectError = $error;
1238 $this->conn = null;
1239
1240 $this->logger->error(
1241 "Error connecting to {db_server} as user {db_user}: {error}",
1242 $this->getLogContext( [
1243 'error' => $error,
1244 'exception' => new RuntimeException(),
1245 'db_log_category' => 'connection',
1246 ] )
1247 );
1248
1249 return new DBConnectionError( $this, $error );
1250 }
1251
1257 return new SelectQueryBuilder( $this );
1258 }
1259
1265 return new UnionQueryBuilder( $this );
1266 }
1267
1273 return new UpdateQueryBuilder( $this );
1274 }
1275
1281 return new DeleteQueryBuilder( $this );
1282 }
1283
1289 return new InsertQueryBuilder( $this );
1290 }
1291
1297 return new ReplaceQueryBuilder( $this );
1298 }
1299
1301 public function selectField(
1302 $tables, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1303 ) {
1304 if ( $var === '*' ) {
1305 throw new DBUnexpectedError( $this, "Cannot use a * field" );
1306 } elseif ( is_array( $var ) && count( $var ) !== 1 ) {
1307 throw new DBUnexpectedError( $this, 'Cannot use more than one field' );
1308 }
1309
1310 $options = $this->platform->normalizeOptions( $options );
1311 $options['LIMIT'] = 1;
1312
1313 $res = $this->select( $tables, $var, $cond, $fname, $options, $join_conds );
1314 if ( $res === false ) {
1315 throw new DBUnexpectedError( $this, "Got false from select()" );
1316 }
1317
1318 $row = $res->fetchRow();
1319 if ( $row === false ) {
1320 return false;
1321 }
1322
1323 return reset( $row );
1324 }
1325
1327 public function selectFieldValues(
1328 $tables, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1329 ): array {
1330 if ( $var === '*' ) {
1331 throw new DBUnexpectedError( $this, "Cannot use a * field" );
1332 } elseif ( !is_string( $var ) ) {
1333 throw new DBUnexpectedError( $this, "Cannot use an array of fields" );
1334 }
1335
1336 $options = $this->platform->normalizeOptions( $options );
1337 $res = $this->select( $tables, [ 'value' => $var ], $cond, $fname, $options, $join_conds );
1338 if ( $res === false ) {
1339 throw new DBUnexpectedError( $this, "Got false from select()" );
1340 }
1341
1342 $values = [];
1343 foreach ( $res as $row ) {
1344 $values[] = $row->value;
1345 }
1346
1347 return $values;
1348 }
1349
1351 public function select(
1352 $tables, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1353 ) {
1354 $options = (array)$options;
1355 // Don't turn this into using platform directly, DatabaseMySQL overrides this.
1356 $sql = $this->selectSQLText( $tables, $vars, $conds, $fname, $options, $join_conds );
1357 // Treat SELECT queries with FOR UPDATE as writes. This matches
1358 // how MySQL enforces read_only (FOR SHARE and LOCK IN SHADE MODE are allowed).
1359 $flags = in_array( 'FOR UPDATE', $options, true )
1360 ? self::QUERY_CHANGE_ROWS
1361 : self::QUERY_CHANGE_NONE;
1362
1363 $query = new Query( $sql, $flags, 'SELECT' );
1364 return $this->query( $query, $fname );
1365 }
1366
1368 public function selectRow( $tables, $vars, $conds, $fname = __METHOD__,
1369 $options = [], $join_conds = []
1370 ) {
1371 $options = (array)$options;
1372 $options['LIMIT'] = 1;
1373
1374 $res = $this->select( $tables, $vars, $conds, $fname, $options, $join_conds );
1375 if ( $res === false ) {
1376 throw new DBUnexpectedError( $this, "Got false from select()" );
1377 }
1378
1379 if ( !$res->numRows() ) {
1380 return false;
1381 }
1382
1383 return $res->fetchObject();
1384 }
1385
1389 public function estimateRowCount(
1390 $tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1391 ): int {
1392 $conds = $this->platform->normalizeConditions( $conds, $fname );
1393 $column = $this->platform->extractSingleFieldFromList( $var );
1394 if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
1395 $conds[] = "$column IS NOT NULL";
1396 }
1397
1398 $res = $this->select(
1399 $tables, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options, $join_conds
1400 );
1401 $row = $res ? $res->fetchRow() : [];
1402
1403 return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
1404 }
1405
1407 public function selectRowCount(
1408 $tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1409 ): int {
1410 $conds = $this->platform->normalizeConditions( $conds, $fname );
1411 $column = $this->platform->extractSingleFieldFromList( $var );
1412 if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
1413 $conds[] = "$column IS NOT NULL";
1414 }
1415 if ( in_array( 'DISTINCT', (array)$options ) ) {
1416 if ( $column === null ) {
1417 throw new DBUnexpectedError( $this,
1418 '$var cannot be empty when the DISTINCT option is given' );
1419 }
1420 $innerVar = $column;
1421 } else {
1422 $innerVar = '1';
1423 }
1424
1425 $res = $this->select(
1426 [
1427 'tmp_count' => $this->platform->buildSelectSubquery(
1428 $tables,
1429 $innerVar,
1430 $conds,
1431 $fname,
1432 $options,
1433 $join_conds
1434 )
1435 ],
1436 [ 'rowcount' => 'COUNT(*)' ],
1437 [],
1438 $fname
1439 );
1440 $row = $res ? $res->fetchRow() : [];
1441
1442 return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
1443 }
1444
1446 public function lockForUpdate(
1447 $table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1448 ) {
1449 if ( !$this->trxLevel() && !$this->flagsHolder->hasImplicitTrxFlag() ) {
1450 throw new DBUnexpectedError(
1451 $this,
1452 __METHOD__ . ': no transaction is active nor is DBO_TRX set'
1453 );
1454 }
1455
1456 $options = (array)$options;
1457 $options[] = 'FOR UPDATE';
1458
1459 return $this->selectRowCount( $table, '*', $conds, $fname, $options, $join_conds );
1460 }
1461
1463 public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1464 $info = $this->fieldInfo( $table, $field );
1465
1466 return (bool)$info;
1467 }
1468
1470 abstract public function tableExists( $table, $fname = __METHOD__ );
1471
1473 public function indexExists( $table, $index, $fname = __METHOD__ ) {
1474 $info = $this->indexInfo( $table, $index, $fname );
1475
1476 return (bool)$info;
1477 }
1478
1480 public function indexUnique( $table, $index, $fname = __METHOD__ ) {
1481 $info = $this->indexInfo( $table, $index, $fname );
1482
1483 return $info ? $info['unique'] : null;
1484 }
1485
1487 abstract public function getPrimaryKeyColumns( $table, $fname = __METHOD__ );
1488
1498 abstract public function indexInfo( $table, $index, $fname = __METHOD__ );
1499
1501 public function insert( $table, $rows, $fname = __METHOD__, $options = [] ) {
1502 $query = $this->platform->dispatchingInsertSqlText( $table, $rows, $options );
1503 if ( !$query ) {
1504 return true;
1505 }
1506 $this->query( $query, $fname );
1507 if ( $this->strictWarnings ) {
1508 $this->checkInsertWarnings( $query, $fname );
1509 }
1510 return true;
1511 }
1512
1521 protected function checkInsertWarnings( Query $query, $fname ) {
1522 }
1523
1525 public function update( $table, $set, $conds, $fname = __METHOD__, $options = [] ) {
1526 $query = $this->platform->updateSqlText( $table, $set, $conds, $options );
1527 $this->query( $query, $fname );
1528
1529 return true;
1530 }
1531
1533 public function databasesAreIndependent() {
1534 return false;
1535 }
1536
1538 final public function selectDomain( $domain ) {
1539 $cs = $this->commenceCriticalSection( __METHOD__ );
1540
1541 try {
1542 $this->doSelectDomain( DatabaseDomain::newFromId( $domain ) );
1543 } catch ( DBError $e ) {
1544 $this->completeCriticalSection( __METHOD__, $cs );
1545 throw $e;
1546 }
1547
1548 $this->completeCriticalSection( __METHOD__, $cs );
1549 }
1550
1557 protected function doSelectDomain( DatabaseDomain $domain ) {
1558 $this->currentDomain = $domain;
1559 $this->platform->setCurrentDomain( $this->currentDomain );
1560 }
1561
1563 public function getDBname() {
1564 return $this->currentDomain->getDatabase();
1565 }
1566
1568 public function getServer() {
1569 return $this->connectionParams[self::CONN_HOST] ?? null;
1570 }
1571
1573 public function getServerName() {
1574 return $this->serverName ?? $this->getServer() ?? 'unknown';
1575 }
1576
1578 public function addQuotes( $s ) {
1579 if ( $s instanceof RawSQLValue ) {
1580 return $s->toSql();
1581 }
1582 if ( $s instanceof Blob ) {
1583 $s = $s->fetch();
1584 }
1585 if ( $s === null ) {
1586 return 'NULL';
1587 } elseif ( is_bool( $s ) ) {
1588 return (string)(int)$s;
1589 } elseif ( is_int( $s ) ) {
1590 return (string)$s;
1591 } else {
1592 return "'" . $this->strencode( $s ) . "'";
1593 }
1594 }
1595
1597 public function expr( string $field, string $op, $value ): Expression {
1598 return new Expression( $field, $op, $value );
1599 }
1600
1602 public function andExpr( array $conds ): AndExpressionGroup {
1603 return AndExpressionGroup::newFromArray( $conds );
1604 }
1605
1607 public function orExpr( array $conds ): OrExpressionGroup {
1608 return OrExpressionGroup::newFromArray( $conds );
1609 }
1610
1612 public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
1613 $uniqueKey = $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
1614 if ( !$rows ) {
1615 return;
1616 }
1617 $affectedRowCount = 0;
1618 $insertId = null;
1619 $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
1620 try {
1621 foreach ( $rows as $row ) {
1622 // Delete any conflicting rows (including ones inserted from $rows)
1623 $query = $this->platform->deleteSqlText(
1624 $table,
1625 [ $this->platform->makeKeyCollisionCondition( [ $row ], $uniqueKey ) ]
1626 );
1627 $this->query( $query, $fname );
1628 // Insert the new row
1629 $query = $this->platform->dispatchingInsertSqlText( $table, $row, [] );
1630 $this->query( $query, $fname );
1631 $affectedRowCount += $this->lastQueryAffectedRows;
1632 $insertId = $insertId ?: $this->lastQueryInsertId;
1633 }
1634 $this->endAtomic( $fname );
1635 } catch ( DBError $e ) {
1636 $this->cancelAtomic( $fname );
1637 throw $e;
1638 }
1639 $this->lastEmulatedAffectedRows = $affectedRowCount;
1640 $this->lastEmulatedInsertId = $insertId;
1641 }
1642
1644 public function upsert( $table, array $rows, $uniqueKeys, array $set, $fname = __METHOD__ ) {
1645 $uniqueKey = $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
1646 if ( !$rows ) {
1647 return true;
1648 }
1649 $this->platform->assertValidUpsertSetArray( $set, $uniqueKey, $rows );
1650
1651 $encTable = $this->tableName( $table );
1652 $sqlColumnAssignments = $this->makeList( $set, self::LIST_SET );
1653 // Get any AUTO_INCREMENT/SERIAL column for this table so we can set insertId()
1654 $autoIncrementColumn = $this->getInsertIdColumnForUpsert( $table );
1655 // Check if there is a SQL assignment expression in $set (as generated by SQLPlatform::buildExcludedValue)
1656 $useWith = (bool)array_filter(
1657 $set,
1658 static function ( $v, $k ) {
1659 return $v instanceof RawSQLValue || is_int( $k );
1660 },
1661 ARRAY_FILTER_USE_BOTH
1662 );
1663 // Subclasses might need explicit type casting within "WITH...AS (VALUES ...)"
1664 // so that these CTE rows can be referenced within the SET clause assigments.
1665 $typeByColumn = $useWith ? $this->getValueTypesForWithClause( $table ) : [];
1666
1667 $first = true;
1668 $affectedRowCount = 0;
1669 $insertId = null;
1670 $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
1671 try {
1672 foreach ( $rows as $row ) {
1673 // Update any existing conflicting row (including ones inserted from $rows)
1674 [ $sqlColumns, $sqlTuples, $sqlVals ] = $this->platform->makeInsertLists(
1675 [ $row ],
1676 '__',
1677 $typeByColumn
1678 );
1679 $sqlConditions = $this->platform->makeKeyCollisionCondition(
1680 [ $row ],
1681 $uniqueKey
1682 );
1683 $query = new Query(
1684 ( $useWith ? "WITH __VALS ($sqlVals) AS (VALUES $sqlTuples) " : "" ) .
1685 "UPDATE $encTable SET $sqlColumnAssignments " .
1686 "WHERE ($sqlConditions)",
1687 self::QUERY_CHANGE_ROWS,
1688 'UPDATE',
1689 $table
1690 );
1691 $this->query( $query, $fname );
1692 $rowsUpdated = $this->lastQueryAffectedRows;
1693 $affectedRowCount += $rowsUpdated;
1694 if ( $rowsUpdated > 0 ) {
1695 // Conflicting row found and updated
1696 if ( $first && $autoIncrementColumn !== null ) {
1697 // @TODO: use "RETURNING" instead (when supported by SQLite)
1698 $query = new Query(
1699 "SELECT $autoIncrementColumn AS id FROM $encTable " .
1700 "WHERE ($sqlConditions)",
1701 self::QUERY_CHANGE_NONE,
1702 'SELECT'
1703 );
1704 $sRes = $this->query( $query, $fname, self::QUERY_CHANGE_ROWS );
1705 $insertId = (int)$sRes->fetchRow()['id'];
1706 }
1707 } else {
1708 // No conflicting row found
1709 $query = new Query(
1710 "INSERT INTO $encTable ($sqlColumns) VALUES $sqlTuples",
1711 self::QUERY_CHANGE_ROWS,
1712 'INSERT',
1713 $table
1714 );
1715 $this->query( $query, $fname );
1716 $affectedRowCount += $this->lastQueryAffectedRows;
1717 }
1718 $first = false;
1719 }
1720 $this->endAtomic( $fname );
1721 } catch ( DBError $e ) {
1722 $this->cancelAtomic( $fname );
1723 throw $e;
1724 }
1725 $this->lastEmulatedAffectedRows = $affectedRowCount;
1726 $this->lastEmulatedInsertId = $insertId;
1727 return true;
1728 }
1729
1734 protected function getInsertIdColumnForUpsert( $table ) {
1735 return null;
1736 }
1737
1742 protected function getValueTypesForWithClause( $table ) {
1743 return [];
1744 }
1745
1747 public function deleteJoin(
1748 $delTable,
1749 $joinTable,
1750 $delVar,
1751 $joinVar,
1752 $conds,
1753 $fname = __METHOD__
1754 ) {
1755 $sql = $this->platform->deleteJoinSqlText( $delTable, $joinTable, $delVar, $joinVar, $conds );
1756 $query = new Query( $sql, self::QUERY_CHANGE_ROWS, 'DELETE', $delTable );
1757 $this->query( $query, $fname );
1758 }
1759
1761 public function delete( $table, $conds, $fname = __METHOD__ ) {
1762 $this->query( $this->platform->deleteSqlText( $table, $conds ), $fname );
1763
1764 return true;
1765 }
1766
1768 final public function insertSelect(
1769 $destTable,
1770 $srcTable,
1771 $varMap,
1772 $conds,
1773 $fname = __METHOD__,
1774 $insertOptions = [],
1775 $selectOptions = [],
1776 $selectJoinConds = []
1777 ) {
1778 static $hints = [ 'NO_AUTO_COLUMNS' ];
1779
1780 $insertOptions = $this->platform->normalizeOptions( $insertOptions );
1781 $selectOptions = $this->platform->normalizeOptions( $selectOptions );
1782
1783 if ( $this->cliMode && $this->isInsertSelectSafe( $insertOptions, $selectOptions, $fname ) ) {
1784 // For massive migrations with downtime, we don't want to select everything
1785 // into memory and OOM, so do all this native on the server side if possible.
1786 $this->doInsertSelectNative(
1787 $destTable,
1788 $srcTable,
1789 $varMap,
1790 $conds,
1791 $fname,
1792 array_diff( $insertOptions, $hints ),
1793 $selectOptions,
1794 $selectJoinConds
1795 );
1796 } else {
1797 $this->doInsertSelectGeneric(
1798 $destTable,
1799 $srcTable,
1800 $varMap,
1801 $conds,
1802 $fname,
1803 array_diff( $insertOptions, $hints ),
1804 $selectOptions,
1805 $selectJoinConds
1806 );
1807 }
1808
1809 return true;
1810 }
1811
1819 protected function isInsertSelectSafe( array $insertOptions, array $selectOptions, $fname ) {
1820 return true;
1821 }
1822
1837 private function doInsertSelectGeneric(
1838 $destTable,
1839 $srcTable,
1840 array $varMap,
1841 $conds,
1842 $fname,
1843 array $insertOptions,
1844 array $selectOptions,
1845 $selectJoinConds
1846 ) {
1847 // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
1848 // on only the primary DB (without needing row-based-replication). It also makes it easy to
1849 // know how big the INSERT is going to be.
1850 $fields = [];
1851 foreach ( $varMap as $dstColumn => $sourceColumnOrSql ) {
1852 $fields[] = $this->platform->fieldNameWithAlias( $sourceColumnOrSql, $dstColumn );
1853 }
1854 $res = $this->select(
1855 $srcTable,
1856 implode( ',', $fields ),
1857 $conds,
1858 $fname,
1859 array_merge( $selectOptions, [ 'FOR UPDATE' ] ),
1860 $selectJoinConds
1861 );
1862
1863 $affectedRowCount = 0;
1864 $insertId = null;
1865 if ( $res ) {
1866 $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
1867 try {
1868 $rows = [];
1869 foreach ( $res as $row ) {
1870 $rows[] = (array)$row;
1871 }
1872 // Avoid inserts that are too huge
1873 $rowBatches = array_chunk( $rows, $this->nonNativeInsertSelectBatchSize );
1874 foreach ( $rowBatches as $rows ) {
1875 $query = $this->platform->dispatchingInsertSqlText( $destTable, $rows, $insertOptions );
1876 $this->query( $query, $fname );
1877 $affectedRowCount += $this->lastQueryAffectedRows;
1878 $insertId = $insertId ?: $this->lastQueryInsertId;
1879 }
1880 $this->endAtomic( $fname );
1881 } catch ( DBError $e ) {
1882 $this->cancelAtomic( $fname );
1883 throw $e;
1884 }
1885 }
1886 $this->lastEmulatedAffectedRows = $affectedRowCount;
1887 $this->lastEmulatedInsertId = $insertId;
1888 }
1889
1905 protected function doInsertSelectNative(
1906 $destTable,
1907 $srcTable,
1908 array $varMap,
1909 $conds,
1910 $fname,
1911 array $insertOptions,
1912 array $selectOptions,
1913 $selectJoinConds
1914 ) {
1915 $sql = $this->platform->insertSelectNativeSqlText(
1916 $destTable,
1917 $srcTable,
1918 $varMap,
1919 $conds,
1920 $fname,
1921 $insertOptions,
1922 $selectOptions,
1923 $selectJoinConds
1924 );
1925 $query = new Query(
1926 $sql,
1927 self::QUERY_CHANGE_ROWS,
1928 'INSERT',
1929 $destTable
1930 );
1931 $this->query( $query, $fname );
1932 }
1933
1941 protected function isConnectionError( $errno ) {
1942 return false;
1943 }
1944
1952 protected function isKnownStatementRollbackError( $errno ) {
1953 return false; // don't know; it could have caused a transaction rollback
1954 }
1955
1959 public function serverIsReadOnly() {
1960 return false;
1961 }
1962
1964 final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
1965 $this->transactionManager->onTransactionResolution( $this, $callback, $fname );
1966 }
1967
1969 final public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
1970 if ( !$this->trxLevel() && $this->getTransactionRoundFname() !== null ) {
1971 // This DB handle is set to participate in LoadBalancer transaction rounds and
1972 // an explicit transaction round is active. Start an implicit transaction on this
1973 // DB handle (setting trxAutomatic) similar to how query() does in such situations.
1974 $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
1975 }
1976
1977 $this->transactionManager->addPostCommitOrIdleCallback( $callback, $fname );
1978 if ( !$this->trxLevel() ) {
1979 $dbErrors = [];
1980 $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE, $dbErrors );
1981 if ( $dbErrors ) {
1982 throw $dbErrors[0];
1983 }
1984 }
1985 }
1986
1988 final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
1989 if ( !$this->trxLevel() && $this->getTransactionRoundFname() !== null ) {
1990 // This DB handle is set to participate in LoadBalancer transaction rounds and
1991 // an explicit transaction round is active. Start an implicit transaction on this
1992 // DB handle (setting trxAutomatic) similar to how query() does in such situations.
1993 $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
1994 }
1995
1996 if ( $this->trxLevel() ) {
1997 $this->transactionManager->addPreCommitOrIdleCallback(
1998 $callback,
1999 $fname
2000 );
2001 } else {
2002 // No transaction is active nor will start implicitly, so make one for this callback
2003 $this->startAtomic( __METHOD__, self::ATOMIC_CANCELABLE );
2004 try {
2005 $callback( $this );
2006 } catch ( Throwable $e ) {
2007 // Avoid confusing error reporting during critical section errors
2008 if ( !$this->csmError ) {
2009 $this->cancelAtomic( __METHOD__ );
2010 }
2011 throw $e;
2012 }
2013 $this->endAtomic( __METHOD__ );
2014 }
2015 }
2016
2018 final public function setTransactionListener( $name, ?callable $callback = null ) {
2019 $this->transactionManager->setTransactionListener( $name, $callback );
2020 }
2021
2030 final public function setTrxEndCallbackSuppression( $suppress ) {
2031 $this->transactionManager->setTrxEndCallbackSuppression( $suppress );
2032 }
2033
2046 public function runOnTransactionIdleCallbacks( $trigger, array &$errors = [] ) {
2047 if ( $this->trxLevel() ) {
2048 throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open' );
2049 }
2050
2051 if ( $this->transactionManager->isEndCallbacksSuppressed() ) {
2052 // Execution deferred by LoadBalancer for explicit execution later
2053 return 0;
2054 }
2055
2056 $cs = $this->commenceCriticalSection( __METHOD__ );
2057
2058 $count = 0;
2059 $autoTrx = $this->flagsHolder->hasImplicitTrxFlag(); // automatic begin() enabled?
2060 // Drain the queues of transaction "idle" and "end" callbacks until they are empty
2061 do {
2062 $callbackEntries = $this->transactionManager->consumeEndCallbacks();
2063 $count += count( $callbackEntries );
2064 foreach ( $callbackEntries as $entry ) {
2065 $this->flagsHolder->clearFlag( self::DBO_TRX ); // make each query its own transaction
2066 try {
2067 $entry[0]( $trigger );
2068 } catch ( DBError $ex ) {
2069 ( $this->errorLogger )( $ex );
2070 $errors[] = $ex;
2071 // Some callbacks may use startAtomic/endAtomic, so make sure
2072 // their transactions are ended so other callbacks don't fail
2073 if ( $this->trxLevel() ) {
2074 $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
2075 }
2076 } finally {
2077 if ( $autoTrx ) {
2078 $this->flagsHolder->setFlag( self::DBO_TRX ); // restore automatic begin()
2079 } else {
2080 $this->flagsHolder->clearFlag( self::DBO_TRX ); // restore auto-commit
2081 }
2082 }
2083 }
2084 } while ( $this->transactionManager->countPostCommitOrIdleCallbacks() );
2085
2086 $this->completeCriticalSection( __METHOD__, $cs );
2087
2088 return $count;
2089 }
2090
2101 public function runTransactionListenerCallbacks( $trigger, array &$errors = [] ) {
2102 if ( $this->transactionManager->isEndCallbacksSuppressed() ) {
2103 // Execution deferred by LoadBalancer for explicit execution later
2104 return;
2105 }
2106
2107 // These callbacks should only be registered in setup, thus no iteration is needed
2108 foreach ( $this->transactionManager->getRecurringCallbacks() as $callback ) {
2109 try {
2110 $callback( $trigger, $this );
2111 } catch ( DBError $ex ) {
2112 ( $this->errorLogger )( $ex );
2113 $errors[] = $ex;
2114 }
2115 }
2116 }
2117
2124 private function runTransactionPostCommitCallbacks() {
2125 $dbErrors = [];
2126 $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT, $dbErrors );
2127 $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT, $dbErrors );
2128 $this->lastEmulatedAffectedRows = 0; // for the sake of consistency
2129 if ( $dbErrors ) {
2130 throw $dbErrors[0];
2131 }
2132 }
2133
2141 private function runTransactionPostRollbackCallbacks() {
2142 $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
2143 $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
2144 $this->lastEmulatedAffectedRows = 0; // for the sake of consistency
2145 }
2146
2148 final public function startAtomic(
2149 $fname = __METHOD__,
2150 $cancelable = self::ATOMIC_NOT_CANCELABLE
2151 ) {
2152 $cs = $this->commenceCriticalSection( __METHOD__ );
2153
2154 if ( $this->trxLevel() ) {
2155 // This atomic section is only one part of a larger transaction
2156 $sectionOwnsTrx = false;
2157 } else {
2158 // Start an implicit transaction (sets trxAutomatic)
2159 try {
2160 $this->begin( $fname, self::TRANSACTION_INTERNAL );
2161 } catch ( DBError $e ) {
2162 $this->completeCriticalSection( __METHOD__, $cs );
2163 throw $e;
2164 }
2165 if ( $this->flagsHolder->hasImplicitTrxFlag() ) {
2166 // This DB handle participates in LoadBalancer transaction rounds; all atomic
2167 // sections should be buffered into one transaction (e.g. to keep web requests
2168 // transactional). Note that an implicit transaction round is considered to be
2169 // active when no there is no explicit transaction round.
2170 $sectionOwnsTrx = false;
2171 } else {
2172 // This DB handle does not participate in LoadBalancer transaction rounds;
2173 // each topmost atomic section will use its own transaction.
2174 $sectionOwnsTrx = true;
2175 }
2176 $this->transactionManager->setAutomaticAtomic( $sectionOwnsTrx );
2177 }
2178
2179 if ( $cancelable === self::ATOMIC_CANCELABLE ) {
2180 if ( $sectionOwnsTrx ) {
2181 // This atomic section is synonymous with the whole transaction; just
2182 // use full COMMIT/ROLLBACK in endAtomic()/cancelAtomic(), respectively
2183 $savepointId = self::NOT_APPLICABLE;
2184 } else {
2185 // This atomic section is only part of the whole transaction; use a SAVEPOINT
2186 // query so that its changes can be cancelled without losing the rest of the
2187 // transaction (e.g. changes from other sections or from outside of sections)
2188 try {
2189 $savepointId = $this->transactionManager->nextSavePointId( $this, $fname );
2190 $sql = $this->platform->savepointSqlText( $savepointId );
2191 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'SAVEPOINT' );
2192 $this->query( $query, $fname );
2193 } catch ( DBError $e ) {
2194 $this->completeCriticalSection( __METHOD__, $cs, $e );
2195 throw $e;
2196 }
2197 }
2198 } else {
2199 $savepointId = null;
2200 }
2201
2202 $sectionId = new AtomicSectionIdentifier;
2203 $this->transactionManager->addToAtomicLevels( $fname, $sectionId, $savepointId );
2204
2205 $this->completeCriticalSection( __METHOD__, $cs );
2206
2207 return $sectionId;
2208 }
2209
2211 final public function endAtomic( $fname = __METHOD__ ) {
2212 [ $savepointId, $sectionId ] = $this->transactionManager->onEndAtomic( $this, $fname );
2213
2214 $runPostCommitCallbacks = false;
2215
2216 $cs = $this->commenceCriticalSection( __METHOD__ );
2217
2218 // Remove the last section (no need to re-index the array)
2219 $finalLevelOfImplicitTrxPopped = $this->transactionManager->popAtomicLevel();
2220
2221 try {
2222 if ( $finalLevelOfImplicitTrxPopped ) {
2223 $this->commit( $fname, self::FLUSHING_INTERNAL );
2224 $runPostCommitCallbacks = true;
2225 } elseif ( $savepointId !== null && $savepointId !== self::NOT_APPLICABLE ) {
2226 $sql = $this->platform->releaseSavepointSqlText( $savepointId );
2227 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'RELEASE SAVEPOINT' );
2228 $this->query( $query, $fname );
2229 }
2230 } catch ( DBError $e ) {
2231 $this->completeCriticalSection( __METHOD__, $cs, $e );
2232 throw $e;
2233 }
2234
2235 $this->transactionManager->onEndAtomicInCriticalSection( $sectionId );
2236
2237 $this->completeCriticalSection( __METHOD__, $cs );
2238
2239 if ( $runPostCommitCallbacks ) {
2240 $this->runTransactionPostCommitCallbacks();
2241 }
2242 }
2243
2245 final public function cancelAtomic(
2246 $fname = __METHOD__,
2247 ?AtomicSectionIdentifier $sectionId = null
2248 ) {
2249 $this->transactionManager->onCancelAtomicBeforeCriticalSection( $this, $fname );
2250 $pos = $this->transactionManager->getPositionFromSectionId( $sectionId );
2251 if ( $pos < 0 ) {
2252 throw new DBUnexpectedError( $this, "Atomic section not found (for $fname)" );
2253 }
2254
2255 $cs = $this->commenceCriticalSection( __METHOD__ );
2256 $runPostRollbackCallbacks = false;
2257 [ $savedFname, $excisedSectionIds, $newTopSectionId, $savedSectionId, $savepointId ] =
2258 $this->transactionManager->cancelAtomic( $pos );
2259
2260 try {
2261 if ( $savedFname !== $fname ) {
2262 $e = new DBUnexpectedError(
2263 $this,
2264 "Invalid atomic section ended (got $fname but expected $savedFname)"
2265 );
2266 $this->completeCriticalSection( __METHOD__, $cs, $e );
2267 throw $e;
2268 }
2269
2270 // Remove the last section (no need to re-index the array)
2271 $this->transactionManager->popAtomicLevel();
2272 $excisedSectionIds[] = $savedSectionId;
2273 $newTopSectionId = $this->transactionManager->currentAtomicSectionId();
2274
2275 if ( $savepointId !== null ) {
2276 // Rollback the transaction changes proposed within this atomic section
2277 if ( $savepointId === self::NOT_APPLICABLE ) {
2278 // Atomic section started the transaction; rollback the whole transaction
2279 // and trigger cancellation callbacks for all active atomic sections
2280 $this->rollback( $fname, self::FLUSHING_INTERNAL );
2281 $runPostRollbackCallbacks = true;
2282 } else {
2283 // Atomic section nested within the transaction; rollback the transaction
2284 // to the state prior to this section and trigger its cancellation callbacks
2285 $sql = $this->platform->rollbackToSavepointSqlText( $savepointId );
2286 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'ROLLBACK TO SAVEPOINT' );
2287 $this->query( $query, $fname );
2288 $this->transactionManager->setTrxStatusToOk(); // no exception; recovered
2289 }
2290 } else {
2291 // Put the transaction into an error state if it's not already in one
2292 $trxError = new DBUnexpectedError(
2293 $this,
2294 "Uncancelable atomic section canceled (got $fname)"
2295 );
2296 $this->transactionManager->setTransactionError( $trxError );
2297 }
2298 } finally {
2299 // Fix up callbacks owned by the sections that were just cancelled.
2300 // All callbacks should have an owner that is present in trxAtomicLevels.
2301 $this->transactionManager->modifyCallbacksForCancel(
2302 $excisedSectionIds,
2303 $newTopSectionId
2304 );
2305 }
2306
2307 $this->lastEmulatedAffectedRows = 0; // for the sake of consistency
2308
2309 $this->completeCriticalSection( __METHOD__, $cs );
2310
2311 if ( $runPostRollbackCallbacks ) {
2312 $this->runTransactionPostRollbackCallbacks();
2313 }
2314 }
2315
2317 final public function doAtomicSection(
2318 $fname,
2319 callable $callback,
2320 $cancelable = self::ATOMIC_NOT_CANCELABLE
2321 ) {
2322 $sectionId = $this->startAtomic( $fname, $cancelable );
2323 try {
2324 $res = $callback( $this, $fname );
2325 } catch ( Throwable $e ) {
2326 // Avoid confusing error reporting during critical section errors
2327 if ( !$this->csmError ) {
2328 $this->cancelAtomic( $fname, $sectionId );
2329 }
2330
2331 throw $e;
2332 }
2333 $this->endAtomic( $fname );
2334
2335 return $res;
2336 }
2337
2339 final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
2340 static $modes = [ self::TRANSACTION_EXPLICIT, self::TRANSACTION_INTERNAL ];
2341 if ( !in_array( $mode, $modes, true ) ) {
2342 throw new DBUnexpectedError( $this, "$fname: invalid mode parameter '$mode'" );
2343 }
2344
2345 $this->transactionManager->onBegin( $this, $fname );
2346
2347 if ( $this->flagsHolder->hasImplicitTrxFlag() && $mode !== self::TRANSACTION_INTERNAL ) {
2348 $msg = "$fname: implicit transaction expected (DBO_TRX set)";
2349 throw new DBUnexpectedError( $this, $msg );
2350 }
2351
2352 $this->assertHasConnectionHandle();
2353
2354 $cs = $this->commenceCriticalSection( __METHOD__ );
2355 $timeStart = microtime( true );
2356 try {
2357 $this->doBegin( $fname );
2358 } catch ( DBError $e ) {
2359 $this->completeCriticalSection( __METHOD__, $cs );
2360 throw $e;
2361 }
2362 $timeEnd = microtime( true );
2363 // Treat "BEGIN" as a trivial query to gauge the RTT delay
2364 $rtt = max( $timeEnd - $timeStart, 0.0 );
2365 $this->transactionManager->onBeginInCriticalSection( $mode, $fname, $rtt );
2366 $this->replicationReporter->resetReplicationLagStatus( $this );
2367 $this->completeCriticalSection( __METHOD__, $cs );
2368 }
2369
2377 protected function doBegin( $fname ) {
2378 $query = new Query( 'BEGIN', self::QUERY_CHANGE_TRX, 'BEGIN' );
2379 $this->query( $query, $fname );
2380 }
2381
2383 final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2384 static $modes = [ self::FLUSHING_ONE, self::FLUSHING_ALL_PEERS, self::FLUSHING_INTERNAL ];
2385 if ( !in_array( $flush, $modes, true ) ) {
2386 throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'" );
2387 }
2388
2389 if ( !$this->transactionManager->onCommit( $this, $fname, $flush ) ) {
2390 return;
2391 }
2392
2393 $this->assertHasConnectionHandle();
2394
2395 $this->runOnTransactionPreCommitCallbacks();
2396
2397 $cs = $this->commenceCriticalSection( __METHOD__ );
2398 try {
2399 if ( $this->trxLevel() ) {
2400 $query = new Query( 'COMMIT', self::QUERY_CHANGE_TRX, 'COMMIT' );
2401 $this->query( $query, $fname );
2402 }
2403 } catch ( DBError $e ) {
2404 $this->completeCriticalSection( __METHOD__, $cs );
2405 throw $e;
2406 }
2407 $lastWriteTime = $this->transactionManager->onCommitInCriticalSection( $this );
2408 if ( $lastWriteTime ) {
2409 $this->lastWriteTime = $lastWriteTime;
2410 }
2411 // With FLUSHING_ALL_PEERS, callbacks will run when requested by a dedicated phase
2412 // within LoadBalancer. With FLUSHING_INTERNAL, callbacks will run when requested by
2413 // the Database caller during a safe point. This avoids isolation and recursion issues.
2414 if ( $flush === self::FLUSHING_ONE ) {
2415 $this->runTransactionPostCommitCallbacks();
2416 }
2417 $this->completeCriticalSection( __METHOD__, $cs );
2418 }
2419
2421 final public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2422 if (
2423 $flush !== self::FLUSHING_INTERNAL &&
2424 $flush !== self::FLUSHING_ALL_PEERS &&
2425 $this->flagsHolder->hasImplicitTrxFlag()
2426 ) {
2427 throw new DBUnexpectedError(
2428 $this,
2429 "$fname: Expected mass rollback of all peer transactions (DBO_TRX set)"
2430 );
2431 }
2432
2433 if ( !$this->trxLevel() ) {
2434 $this->transactionManager->setTrxStatusToNone();
2435 $this->transactionManager->clearPreEndCallbacks();
2436 if ( $this->transactionManager->trxLevel() === TransactionManager::STATUS_TRX_ERROR ) {
2437 $this->logger->info(
2438 "$fname: acknowledged server-side transaction loss on {db_server}",
2439 $this->getLogContext()
2440 );
2441 }
2442
2443 return;
2444 }
2445
2446 $this->assertHasConnectionHandle();
2447
2448 if ( $this->csmError ) {
2449 // Since the session state is corrupt, we cannot just rollback the transaction
2450 // while preserving the non-transaction session state. The handle will remain
2451 // marked as corrupt until flushSession() is called to reset the connection
2452 // and deal with any remaining callbacks.
2453 $this->logger->info(
2454 "$fname: acknowledged client-side transaction loss on {db_server}",
2455 $this->getLogContext()
2456 );
2457
2458 return;
2459 }
2460
2461 $cs = $this->commenceCriticalSection( __METHOD__ );
2462 if ( $this->trxLevel() ) {
2463 // Disconnects cause rollback anyway, so ignore those errors
2464 $query = new Query(
2465 $this->platform->rollbackSqlText(),
2466 self::QUERY_SILENCE_ERRORS | self::QUERY_CHANGE_TRX,
2467 'ROLLBACK'
2468 );
2469 $this->query( $query, $fname );
2470 }
2471 $this->transactionManager->onRollbackInCriticalSection( $this );
2472 // With FLUSHING_ALL_PEERS, callbacks will run when requested by a dedicated phase
2473 // within LoadBalancer. With FLUSHING_INTERNAL, callbacks will run when requested by
2474 // the Database caller during a safe point. This avoids isolation and recursion issues.
2475 if ( $flush === self::FLUSHING_ONE ) {
2476 $this->runTransactionPostRollbackCallbacks();
2477 }
2478 $this->completeCriticalSection( __METHOD__, $cs );
2479 }
2480
2485 public function setTransactionManager( TransactionManager $transactionManager ) {
2486 $this->transactionManager = $transactionManager;
2487 }
2488
2490 public function flushSession( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2491 if (
2492 $flush !== self::FLUSHING_INTERNAL &&
2493 $flush !== self::FLUSHING_ALL_PEERS &&
2494 $this->flagsHolder->hasImplicitTrxFlag()
2495 ) {
2496 throw new DBUnexpectedError(
2497 $this,
2498 "$fname: Expected mass flush of all peer connections (DBO_TRX set)"
2499 );
2500 }
2501
2502 if ( $this->csmError ) {
2503 // If a critical section error occurred, such as Excimer timeout exceptions raised
2504 // before a query response was marshalled, destroy the connection handle and reset
2505 // the session state tracking variables. The value of trxLevel() is irrelevant here,
2506 // and, in fact, might be 1 due to rollback() deferring critical section recovery.
2507 $this->logger->info(
2508 "$fname: acknowledged client-side session loss on {db_server}",
2509 $this->getLogContext()
2510 );
2511 $this->csmError = null;
2512 $this->csmFname = null;
2513 $this->replaceLostConnection( 2048, __METHOD__ );
2514
2515 return;
2516 }
2517
2518 if ( $this->trxLevel() ) {
2519 // Any existing transaction should have been rolled back already
2520 throw new DBUnexpectedError(
2521 $this,
2522 "$fname: transaction still in progress (not yet rolled back)"
2523 );
2524 }
2525
2526 if ( $this->transactionManager->sessionStatus() === TransactionManager::STATUS_SESS_ERROR ) {
2527 // If the session state was already lost due to either an unacknowledged session
2528 // state loss error (e.g. dropped connection) or an explicit connection close call,
2529 // then there is nothing to do here. Note that in such cases, even temporary tables
2530 // and server-side config variables are lost (invocation of this method is assumed
2531 // to imply that such losses are tolerable).
2532 $this->logger->info(
2533 "$fname: acknowledged server-side session loss on {db_server}",
2534 $this->getLogContext()
2535 );
2536 } elseif ( $this->isOpen() ) {
2537 // Connection handle exists; server-side session state must be flushed
2538 $this->doFlushSession( $fname );
2539 $this->sessionNamedLocks = [];
2540 }
2541
2542 $this->transactionManager->clearSessionError();
2543 }
2544
2553 protected function doFlushSession( $fname ) {
2554 // no-op
2555 }
2556
2558 public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2559 $this->transactionManager->onFlushSnapshot(
2560 $this,
2561 $fname,
2562 $flush,
2563 $this->getTransactionRoundFname()
2564 );
2565 if (
2566 $this->transactionManager->sessionStatus() === TransactionManager::STATUS_SESS_ERROR ||
2567 $this->transactionManager->trxStatus() === TransactionManager::STATUS_TRX_ERROR
2568 ) {
2569 $this->rollback( $fname, self::FLUSHING_INTERNAL );
2570 } else {
2571 $this->commit( $fname, self::FLUSHING_INTERNAL );
2572 }
2573 }
2574
2577 $oldName,
2578 $newName,
2579 $temporary = false,
2580 $fname = __METHOD__
2581 ) {
2582 throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2583 }
2584
2586 public function listTables( $prefix = null, $fname = __METHOD__ ) {
2587 throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2588 }
2589
2591 public function affectedRows() {
2592 $this->lastEmulatedAffectedRows ??= $this->lastQueryAffectedRows;
2593
2594 return $this->lastEmulatedAffectedRows;
2595 }
2596
2598 public function insertId() {
2599 if ( $this->lastEmulatedInsertId === null ) {
2600 // Guard against misuse of this method by checking affectedRows(). Note that calls
2601 // to insert() with "IGNORE" and calls to insertSelect() might not add any rows.
2602 if ( $this->affectedRows() ) {
2603 $this->lastEmulatedInsertId = $this->lastInsertId();
2604 } else {
2605 $this->lastEmulatedInsertId = 0;
2606 }
2607 }
2608
2609 return $this->lastEmulatedInsertId;
2610 }
2611
2625 abstract protected function lastInsertId();
2626
2628 public function ping() {
2629 if ( $this->isOpen() ) {
2630 // If the connection was recently used, assume that it is still good
2631 if ( ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
2632 return true;
2633 }
2634 // Send a trivial query to test the connection, triggering an automatic
2635 // reconnection attempt if the connection was lost
2636 $query = new Query(
2637 self::PING_QUERY,
2638 self::QUERY_IGNORE_DBO_TRX | self::QUERY_SILENCE_ERRORS | self::QUERY_CHANGE_NONE,
2639 'SELECT'
2640 );
2641 $res = $this->query( $query, __METHOD__ );
2642 $ok = ( $res !== false );
2643 } else {
2644 // Try to re-establish a connection
2645 $ok = $this->replaceLostConnection( null, __METHOD__ );
2646 }
2647
2648 return $ok;
2649 }
2650
2658 protected function replaceLostConnection( $lastErrno, $fname ) {
2659 if ( $this->conn ) {
2660 $this->closeConnection();
2661 $this->conn = null;
2662 $this->handleSessionLossPreconnect();
2663 }
2664
2665 try {
2666 $this->open(
2667 $this->connectionParams[self::CONN_HOST],
2668 $this->connectionParams[self::CONN_USER],
2669 $this->connectionParams[self::CONN_PASSWORD],
2670 $this->currentDomain->getDatabase(),
2671 $this->currentDomain->getSchema(),
2672 $this->tablePrefix()
2673 );
2674 $this->lastPing = microtime( true );
2675 $ok = true;
2676
2677 $this->logger->warning(
2678 $fname . ': lost connection to {db_server} with error {errno}; reconnected',
2679 $this->getLogContext( [
2680 'exception' => new RuntimeException(),
2681 'db_log_category' => 'connection',
2682 'errno' => $lastErrno
2683 ] )
2684 );
2685 } catch ( DBConnectionError $e ) {
2686 $ok = false;
2687
2688 $this->logger->error(
2689 $fname . ': lost connection to {db_server} with error {errno}; reconnection failed: {connect_msg}',
2690 $this->getLogContext( [
2691 'exception' => new RuntimeException(),
2692 'db_log_category' => 'connection',
2693 'errno' => $lastErrno,
2694 'connect_msg' => $e->getMessage()
2695 ] )
2696 );
2697 }
2698
2699 // Handle callbacks in trxEndCallbacks, e.g. onTransactionResolution().
2700 // If callback suppression is set then the array will remain unhandled.
2701 $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
2702 // Handle callbacks in trxRecurringCallbacks, e.g. setTransactionListener().
2703 // If callback suppression is set then the array will remain unhandled.
2704 $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
2705
2706 return $ok;
2707 }
2708
2731 public static function getCacheSetOptions( ?IReadableDatabase ...$dbs ) {
2732 $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
2733
2734 foreach ( $dbs as $db ) {
2735 if ( $db instanceof IReadableDatabase ) {
2736 $status = $db->getSessionLagStatus();
2737
2738 if ( $status['lag'] === false ) {
2739 $res['lag'] = false;
2740 } elseif ( $res['lag'] !== false ) {
2741 $res['lag'] = max( $res['lag'], $status['lag'] );
2742 }
2743 $res['since'] = min( $res['since'], $status['since'] );
2744 }
2745
2746 if ( $db instanceof IDatabaseForOwner ) {
2747 $res['pending'] = $res['pending'] ?: $db->writesPending();
2748 }
2749 }
2750
2751 return $res;
2752 }
2753
2755 public function encodeBlob( $b ) {
2756 return $b;
2757 }
2758
2760 public function decodeBlob( $b ) {
2761 if ( $b instanceof Blob ) {
2762 $b = $b->fetch();
2763 }
2764 return $b;
2765 }
2766
2767 public function setSessionOptions( array $options ) {
2768 }
2769
2771 public function sourceFile(
2772 $filename,
2773 ?callable $lineCallback = null,
2774 ?callable $resultCallback = null,
2775 $fname = false,
2776 ?callable $inputCallback = null
2777 ) {
2778 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2779 $fp = @fopen( $filename, 'r' );
2780
2781 if ( $fp === false ) {
2782 throw new RuntimeException( "Could not open \"{$filename}\"" );
2783 }
2784
2785 if ( !$fname ) {
2786 $fname = __METHOD__ . "( $filename )";
2787 }
2788
2789 try {
2790 return $this->sourceStream(
2791 $fp,
2792 $lineCallback,
2793 $resultCallback,
2794 $fname,
2795 $inputCallback
2796 );
2797 } finally {
2798 fclose( $fp );
2799 }
2800 }
2801
2803 public function sourceStream(
2804 $fp,
2805 ?callable $lineCallback = null,
2806 ?callable $resultCallback = null,
2807 $fname = __METHOD__,
2808 ?callable $inputCallback = null
2809 ) {
2810 $delimiterReset = new ScopedCallback(
2811 function ( $delimiter ) {
2812 $this->delimiter = $delimiter;
2813 },
2814 [ $this->delimiter ]
2815 );
2816 $cmd = '';
2817
2818 while ( !feof( $fp ) ) {
2819 if ( $lineCallback ) {
2820 $lineCallback();
2821 }
2822
2823 $line = trim( fgets( $fp ) );
2824
2825 if ( $line == '' ) {
2826 continue;
2827 }
2828
2829 if ( $line[0] == '-' && $line[1] == '-' ) {
2830 continue;
2831 }
2832
2833 if ( $cmd != '' ) {
2834 $cmd .= ' ';
2835 }
2836
2837 $done = $this->streamStatementEnd( $cmd, $line );
2838
2839 $cmd .= "$line\n";
2840
2841 if ( $done || feof( $fp ) ) {
2842 $cmd = $this->platform->replaceVars( $cmd );
2843
2844 if ( $inputCallback ) {
2845 $callbackResult = $inputCallback( $cmd );
2846
2847 if ( is_string( $callbackResult ) || !$callbackResult ) {
2848 $cmd = $callbackResult;
2849 }
2850 }
2851
2852 if ( $cmd ) {
2853 $res = $this->query( $cmd, $fname );
2854
2855 if ( $resultCallback ) {
2856 $resultCallback( $res, $this );
2857 }
2858
2859 if ( $res === false ) {
2860 $err = $this->lastError();
2861
2862 return "Query \"{$cmd}\" failed with error code \"$err\".\n";
2863 }
2864 }
2865 $cmd = '';
2866 }
2867 }
2868
2869 ScopedCallback::consume( $delimiterReset );
2870 return true;
2871 }
2872
2880 public function streamStatementEnd( &$sql, &$newLine ) {
2881 if ( $this->delimiter ) {
2882 $prev = $newLine;
2883 $newLine = preg_replace(
2884 '/' . preg_quote( $this->delimiter, '/' ) . '$/',
2885 '',
2886 $newLine
2887 );
2888 if ( $newLine != $prev ) {
2889 return true;
2890 }
2891 }
2892
2893 return false;
2894 }
2895
2899 public function lockIsFree( $lockName, $method ) {
2900 // RDBMs methods for checking named locks may or may not count this thread itself.
2901 // In MySQL, IS_FREE_LOCK() returns 0 if the thread already has the lock. This is
2902 // the behavior chosen by the interface for this method.
2903 if ( isset( $this->sessionNamedLocks[$lockName] ) ) {
2904 $lockIsFree = false;
2905 } else {
2906 $lockIsFree = $this->doLockIsFree( $lockName, $method );
2907 }
2908
2909 return $lockIsFree;
2910 }
2911
2920 protected function doLockIsFree( string $lockName, string $method ) {
2921 return true; // not implemented
2922 }
2923
2927 public function lock( $lockName, $method, $timeout = 5, $flags = 0 ) {
2928 $lockTsUnix = $this->doLock( $lockName, $method, $timeout );
2929 if ( $lockTsUnix !== null ) {
2930 $locked = true;
2931 $this->sessionNamedLocks[$lockName] = [
2932 'ts' => $lockTsUnix,
2933 'trxId' => $this->transactionManager->getTrxId()
2934 ];
2935 } else {
2936 $locked = false;
2937 $this->logger->info(
2938 __METHOD__ . ": failed to acquire lock '{lockname}'",
2939 [
2940 'lockname' => $lockName,
2941 'db_log_category' => 'locking'
2942 ]
2943 );
2944 }
2945
2946 return $this->flagsHolder::contains( $flags, self::LOCK_TIMESTAMP ) ? $lockTsUnix : $locked;
2947 }
2948
2958 protected function doLock( string $lockName, string $method, int $timeout ) {
2959 return microtime( true ); // not implemented
2960 }
2961
2965 public function unlock( $lockName, $method ) {
2966 if ( !isset( $this->sessionNamedLocks[$lockName] ) ) {
2967 $released = false;
2968 $this->logger->warning(
2969 __METHOD__ . ": trying to release unheld lock '$lockName'\n",
2970 [ 'db_log_category' => 'locking' ]
2971 );
2972 } else {
2973 $released = $this->doUnlock( $lockName, $method );
2974 if ( $released ) {
2975 unset( $this->sessionNamedLocks[$lockName] );
2976 } else {
2977 $this->logger->warning(
2978 __METHOD__ . ": failed to release lock '$lockName'\n",
2979 [ 'db_log_category' => 'locking' ]
2980 );
2981 }
2982 }
2983
2984 return $released;
2985 }
2986
2995 protected function doUnlock( string $lockName, string $method ) {
2996 return true; // not implemented
2997 }
2998
3000 #[\NoDiscard]
3001 public function getScopedLockAndFlush( $lockKey, $fname, $timeout ): ?ScopedCallback {
3002 $this->transactionManager->onGetScopedLockAndFlush( $this, $fname );
3003
3004 if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
3005 return null;
3006 }
3007
3008 $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
3009 // Note that the callback can be reached due to an exception making the calling
3010 // function end early. If the transaction/session is in an error state, avoid log
3011 // spam and confusing replacement of an original DBError with one about unlock().
3012 // Unlock query will fail anyway; avoid possibly triggering errors in rollback()
3013 if (
3014 $this->transactionManager->sessionStatus() === TransactionManager::STATUS_SESS_ERROR ||
3015 $this->transactionManager->trxStatus() === TransactionManager::STATUS_TRX_ERROR
3016 ) {
3017 return;
3018 }
3019 if ( $this->trxLevel() ) {
3020 $this->onTransactionResolution(
3021 function () use ( $lockKey, $fname ) {
3022 $this->unlock( $lockKey, $fname );
3023 },
3024 $fname
3025 );
3026 } else {
3027 $this->unlock( $lockKey, $fname );
3028 }
3029 } );
3030
3031 $this->commit( $fname, self::FLUSHING_INTERNAL );
3032
3033 return $unlocker;
3034 }
3035
3037 public function dropTable( $table, $fname = __METHOD__ ) {
3038 if ( !$this->tableExists( $table, $fname ) ) {
3039 return false;
3040 }
3041
3042 $query = new Query(
3043 $this->platform->dropTableSqlText( $table ),
3044 self::QUERY_CHANGE_SCHEMA,
3045 'DROP',
3046 $table
3047 );
3048 $this->query( $query, $fname );
3049
3050 return true;
3051 }
3052
3054 public function truncateTable( $table, $fname = __METHOD__ ) {
3055 $sql = "TRUNCATE TABLE " . $this->tableName( $table );
3056 $query = new Query( $sql, self::QUERY_CHANGE_SCHEMA, 'TRUNCATE', $table );
3057 $this->query( $query, $fname );
3058 }
3059
3061 public function isReadOnly() {
3062 return ( $this->getReadOnlyReason() !== null );
3063 }
3064
3068 protected function getReadOnlyReason() {
3069 $reason = $this->replicationReporter->getTopologyBasedReadOnlyReason();
3070 if ( $reason ) {
3071 return $reason;
3072 }
3073
3074 $reason = $this->getLBInfo( self::LB_READ_ONLY_REASON );
3075 if ( is_string( $reason ) ) {
3076 return [ $reason, 'lb' ];
3077 }
3078
3079 return null;
3080 }
3081
3093 protected function getBindingHandle() {
3094 if ( !$this->conn ) {
3095 throw new DBUnexpectedError(
3096 $this,
3097 'DB connection was already closed or the connection dropped'
3098 );
3099 }
3100
3101 return $this->conn;
3102 }
3103
3140 protected function commenceCriticalSection( string $fname ) {
3141 if ( $this->csmError ) {
3142 throw new DBUnexpectedError(
3143 $this,
3144 "Cannot execute $fname critical section while session state is out of sync.\n\n" .
3145 $this->csmError->getMessage() . "\n" .
3146 $this->csmError->getTraceAsString()
3147 );
3148 }
3149
3150 if ( $this->csmId ) {
3151 $csm = null; // fold into the outer critical section
3152 } elseif ( $this->csProvider ) {
3153 $csm = $this->csProvider->scopedEnter(
3154 $fname,
3155 null, // emergency limit (default)
3156 null, // emergency callback (default)
3157 function () use ( $fname ) {
3158 // Mark a critical section as having been aborted by an error
3159 $e = new RuntimeException( "A critical section from {$fname} has failed" );
3160 $this->csmError = $e;
3161 $this->csmId = null;
3162 }
3163 );
3164 $this->csmId = $csm->getId();
3165 $this->csmFname = $fname;
3166 } else {
3167 $csm = null; // not supported
3168 }
3169
3170 return $csm;
3171 }
3172
3183 protected function completeCriticalSection(
3184 string $fname,
3185 ?CriticalSectionScope $csm,
3186 ?Throwable $trxError = null
3187 ) {
3188 if ( $csm !== null ) {
3189 if ( $this->csmId === null ) {
3190 throw new LogicException( "$fname critical section is not active" );
3191 } elseif ( $csm->getId() !== $this->csmId ) {
3192 throw new LogicException(
3193 "$fname critical section is not the active ({$this->csmFname}) one"
3194 );
3195 }
3196
3197 $csm->exit();
3198 $this->csmId = null;
3199 }
3200
3201 if ( $trxError ) {
3202 $this->transactionManager->setTransactionError( $trxError );
3203 }
3204 }
3205
3206 public function __toString() {
3207 $id = spl_object_id( $this );
3208
3209 $description = $this->getType() . ' object #' . $id;
3210 // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.is_resource
3211 if ( is_resource( $this->conn ) ) {
3212 $description .= ' (' . (string)$this->conn . ')'; // "resource id #<ID>"
3213 } elseif ( is_object( $this->conn ) ) {
3214 $handleId = spl_object_id( $this->conn );
3215 $description .= " (handle id #$handleId)";
3216 }
3217
3218 return $description;
3219 }
3220
3225 public function __clone() {
3226 $this->logger->warning(
3227 "Cloning " . static::class . " is not recommended; forking connection",
3228 [
3229 'exception' => new RuntimeException(),
3230 'db_log_category' => 'connection'
3231 ]
3232 );
3233
3234 if ( $this->isOpen() ) {
3235 // Open a new connection resource without messing with the old one
3236 $this->conn = null;
3237 $this->transactionManager->clearEndCallbacks();
3238 $this->handleSessionLossPreconnect(); // no trx or locks anymore
3239 $this->open(
3240 $this->connectionParams[self::CONN_HOST],
3241 $this->connectionParams[self::CONN_USER],
3242 $this->connectionParams[self::CONN_PASSWORD],
3243 $this->currentDomain->getDatabase(),
3244 $this->currentDomain->getSchema(),
3245 $this->tablePrefix()
3246 );
3247 $this->lastPing = microtime( true );
3248 }
3249 }
3250
3257 public function __sleep(): never {
3258 throw new RuntimeException( 'Database serialization may cause problems, since ' .
3259 'the connection is not restored on wakeup' );
3260 }
3261
3265 public function __destruct() {
3266 if ( $this->transactionManager ) {
3267 // Tests mock this class and disable constructor.
3268 $this->transactionManager->onDestruct();
3269 }
3270
3271 $danglingWriters = $this->pendingWriteAndCallbackCallers();
3272 if ( $danglingWriters ) {
3273 $fnames = implode( ', ', $danglingWriters );
3274 trigger_error( "DB transaction writes or callbacks still pending ($fnames)" );
3275 }
3276
3277 if ( $this->conn ) {
3278 // Avoid connection leaks. Normally, resources close at script completion.
3279 // The connection might already be closed in PHP by now, so suppress warnings.
3280 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
3281 @$this->closeConnection();
3282 $this->conn = null;
3283 }
3284 }
3285
3286 /* Start of methods delegated to DatabaseFlags. Avoid using them outside of rdbms library */
3287
3289 public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
3290 $this->flagsHolder->setFlag( $flag, $remember );
3291 }
3292
3294 public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
3295 $this->flagsHolder->clearFlag( $flag, $remember );
3296 }
3297
3299 public function restoreFlags( $state = self::RESTORE_PRIOR ) {
3300 $this->flagsHolder->restoreFlags( $state );
3301 }
3302
3304 public function getFlag( $flag ) {
3305 return $this->flagsHolder->getFlag( $flag );
3306 }
3307
3308 /* End of methods delegated to DatabaseFlags. */
3309
3310 /* Start of methods delegated to TransactionManager. Avoid using them outside of rdbms library */
3311
3313 final public function trxLevel() {
3314 // FIXME: A lot of tests disable constructor leading to trx manager being
3315 // null and breaking, this is unacceptable but hopefully this should
3316 // happen less by moving these functions to the transaction manager class.
3317 if ( !$this->transactionManager ) {
3318 $this->transactionManager = new TransactionManager( new NullLogger() );
3319 }
3320 return $this->transactionManager->trxLevel();
3321 }
3322
3324 public function trxTimestamp() {
3325 return $this->transactionManager->trxTimestamp();
3326 }
3327
3329 public function trxStatus() {
3330 return $this->transactionManager->trxStatus();
3331 }
3332
3334 public function writesPending() {
3335 return $this->transactionManager->writesPending();
3336 }
3337
3339 public function writesOrCallbacksPending() {
3340 return $this->transactionManager->writesOrCallbacksPending();
3341 }
3342
3344 public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
3345 return $this->transactionManager->pendingWriteQueryDuration( $type );
3346 }
3347
3349 public function pendingWriteCallers() {
3350 if ( !$this->transactionManager ) {
3351 return [];
3352 }
3353 return $this->transactionManager->pendingWriteCallers();
3354 }
3355
3358 if ( !$this->transactionManager ) {
3359 return [];
3360 }
3361 return $this->transactionManager->pendingWriteAndCallbackCallers();
3362 }
3363
3366 return $this->transactionManager->runOnTransactionPreCommitCallbacks();
3367 }
3368
3370 public function explicitTrxActive() {
3371 return $this->transactionManager->explicitTrxActive();
3372 }
3373
3374 /* End of methods delegated to TransactionManager. */
3375
3376 /* Start of methods delegated to SQLPlatform. Avoid using them outside of rdbms library */
3377
3379 public function implicitOrderby() {
3380 return $this->platform->implicitOrderby();
3381 }
3382
3384 public function selectSQLText(
3385 $tables, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
3386 ) {
3387 return $this->platform->selectSQLText( $tables, $vars, $conds, $fname, $options, $join_conds );
3388 }
3389
3391 public function buildComparison( string $op, array $conds ): string {
3392 return $this->platform->buildComparison( $op, $conds );
3393 }
3394
3396 public function makeList( array $a, $mode = self::LIST_COMMA ) {
3397 return $this->platform->makeList( $a, $mode );
3398 }
3399
3401 public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
3402 return $this->platform->makeWhereFrom2d( $data, $baseKey, $subKey );
3403 }
3404
3406 public function factorConds( $condsArray ) {
3407 return $this->platform->factorConds( $condsArray );
3408 }
3409
3411 public function bitNot( $field ) {
3412 return $this->platform->bitNot( $field );
3413 }
3414
3416 public function bitAnd( $fieldLeft, $fieldRight ) {
3417 return $this->platform->bitAnd( $fieldLeft, $fieldRight );
3418 }
3419
3421 public function bitOr( $fieldLeft, $fieldRight ) {
3422 return $this->platform->bitOr( $fieldLeft, $fieldRight );
3423 }
3424
3426 public function buildConcat( $stringList ) {
3427 return $this->platform->buildConcat( $stringList );
3428 }
3429
3431 public function buildGroupConcat( $field, $delim ): string {
3432 return $this->platform->buildGroupConcat( $field, $delim );
3433 }
3434
3436 public function buildGreatest( $fields, $values ) {
3437 return $this->platform->buildGreatest( $fields, $values );
3438 }
3439
3441 public function buildLeast( $fields, $values ) {
3442 return $this->platform->buildLeast( $fields, $values );
3443 }
3444
3446 public function buildSubstring( $input, $startPosition, $length = null ) {
3447 return $this->platform->buildSubstring( $input, $startPosition, $length );
3448 }
3449
3451 public function buildStringCast( $field ) {
3452 return $this->platform->buildStringCast( $field );
3453 }
3454
3456 public function buildIntegerCast( $field ) {
3457 return $this->platform->buildIntegerCast( $field );
3458 }
3459
3461 public function tableName( string $name, $format = 'quoted' ) {
3462 return $this->platform->tableName( $name, $format );
3463 }
3464
3466 public function tableNamesN( ...$tables ) {
3467 return $this->platform->tableNamesN( ...$tables );
3468 }
3469
3471 public function addIdentifierQuotes( $s ) {
3472 return $this->platform->addIdentifierQuotes( $s );
3473 }
3474
3476 public function isQuotedIdentifier( $name ) {
3477 return $this->platform->isQuotedIdentifier( $name );
3478 }
3479
3481 public function buildLike( $param, ...$params ) {
3482 return $this->platform->buildLike( $param, ...$params );
3483 }
3484
3486 public function anyChar() {
3487 return $this->platform->anyChar();
3488 }
3489
3491 public function anyString() {
3492 return $this->platform->anyString();
3493 }
3494
3496 public function limitResult( $sql, $limit, $offset = false ) {
3497 return $this->platform->limitResult( $sql, $limit, $offset );
3498 }
3499
3501 public function unionSupportsOrderAndLimit() {
3502 return $this->platform->unionSupportsOrderAndLimit();
3503 }
3504
3506 public function unionQueries( $sqls, $all, $options = [] ) {
3507 return $this->platform->unionQueries( $sqls, $all, $options );
3508 }
3509
3511 public function conditional( $cond, $caseTrueExpression, $caseFalseExpression ) {
3512 return $this->platform->conditional( $cond, $caseTrueExpression, $caseFalseExpression );
3513 }
3514
3516 public function strreplace( $orig, $old, $new ) {
3517 return $this->platform->strreplace( $orig, $old, $new );
3518 }
3519
3521 public function timestamp( $ts = 0 ) {
3522 return $this->platform->timestamp( $ts );
3523 }
3524
3526 public function timestampOrNull( $ts = null ) {
3527 return $this->platform->timestampOrNull( $ts );
3528 }
3529
3531 public function getInfinity() {
3532 return $this->platform->getInfinity();
3533 }
3534
3536 public function encodeExpiry( $expiry ) {
3537 return $this->platform->encodeExpiry( $expiry );
3538 }
3539
3541 public function decodeExpiry( $expiry, $format = TS::MW ) {
3542 return $this->platform->decodeExpiry( $expiry, $format );
3543 }
3544
3546 public function setTableAliases( array $aliases ) {
3547 $this->platform->setTableAliases( $aliases );
3548 }
3549
3551 public function getTableAliases() {
3552 return $this->platform->getTableAliases();
3553 }
3554
3556 public function buildGroupConcatField(
3557 $delim, $tables, $field, $conds = '', $join_conds = []
3558 ) {
3559 return $this->platform->buildGroupConcatField( $delim, $tables, $field, $conds, $join_conds );
3560 }
3561
3563 public function buildSelectSubquery(
3564 $tables, $vars, $conds = '', $fname = __METHOD__,
3565 $options = [], $join_conds = []
3566 ) {
3567 return $this->platform->buildSelectSubquery( $tables, $vars, $conds, $fname, $options, $join_conds );
3568 }
3569
3571 public function buildExcludedValue( $column ) {
3572 return $this->platform->buildExcludedValue( $column );
3573 }
3574
3576 public function setSchemaVars( $vars ) {
3577 $this->platform->setSchemaVars( $vars );
3578 }
3579
3580 /* End of methods delegated to SQLPlatform. */
3581
3582 /* Start of methods delegated to ReplicationReporter. */
3583
3585 public function primaryPosWait( DBPrimaryPos $pos, $timeout ) {
3586 return $this->replicationReporter->primaryPosWait( $this, $pos, $timeout );
3587 }
3588
3590 public function getPrimaryPos() {
3591 return $this->replicationReporter->getPrimaryPos( $this );
3592 }
3593
3595 public function getLag() {
3596 return $this->replicationReporter->getLag( $this );
3597 }
3598
3600 public function getSessionLagStatus() {
3601 return $this->replicationReporter->getSessionLagStatus( $this );
3602 }
3603
3604 /* End of methods delegated to ReplicationReporter. */
3605}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
Representing a group of expressions chained via AND.
Class used for token representing identifiers for atomic sections from IDatabase instances.
static recordQuery(DatabaseDomain $domain, Query $query)
When tracking is enabled and a query alters tables, record the list of tables that are altered.
Database error base class.
Definition DBError.php:22
Exception class for attempted DB write access to a DBConnRef with the DB_REPLICA role.
Class to handle database/schema/prefix specifications for IDatabase.
A single concrete connection to a relational database.
Definition Database.php:38
getPrimaryKeyColumns( $table, $fname=__METHOD__)
Get the primary key columns of a table.to be used by updater onlystring[] query}
bool $cliMode
Whether this PHP instance is for a CLI script.
Definition Database.php:66
getServerInfo()
Get a human-readable string describing the current software version.Use getServerVersion() to get mac...
Definition Database.php:296
encodeBlob( $b)
Some DBMSs have a special format for inserting into blob fields, they don't allow simple quoted strin...
setLBInfo( $nameOrArray, $value=null)
Set the entire array or a particular key of the managing load balancer info array....
Definition Database.php:354
buildIntegerCast( $field)
string 1.31 in IDatabase, moved to ISQLPlatform in 1.39
expr(string $field, string $op, $value)
See Expression::__construct()1.42 Expression
begin( $fname=__METHOD__, $mode=self::TRANSACTION_EXPLICIT)
Begin a transaction.Only call this from code with outer transaction scope. See https://www....
buildExcludedValue( $column)
Build a reference to a column value from the conflicting proposed upsert() row.The reference comes in...
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition Database.php:427
callable $errorLogger
Error logging callback.
Definition Database.php:44
isReadOnly()
Check if this DB server is marked as read-only according to load balancer info.LoadBalancer checks se...
strencode( $s)
Wrapper for addslashes()
int null $connectTimeout
Maximum seconds to wait on connection attempts.
Definition Database.php:68
__toString()
Get a debugging string that mentions the database type, the ID of this instance, and the ID of any un...
open( $server, $user, $password, $db, $schema, $tablePrefix)
Open a new connection to the database (closing any existing one)
newUpdateQueryBuilder()
Get an UpdateQueryBuilder bound to this connection.
setTransactionListener( $name, ?callable $callback=null)
Run a callback after each time any transaction commits or rolls back.The callback takes two arguments...
primaryPosWait(DBPrimaryPos $pos, $timeout)
Wait for the replica server to catch up to a given primary server position.Note that this does not st...
object resource null $conn
Database connection.
Definition Database.php:61
trxTimestamp()
Get the UNIX timestamp of the time that the transaction was established.This can be used to reason ab...
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.string
tablePrefix( $prefix=null)
Get/set the table prefix.string The previous table prefix
Definition Database.php:301
newSelectQueryBuilder()
Get a SelectQueryBuilder bound to this connection.
setLogger(LoggerInterface $logger)
Set the PSR-3 logger interface to use.
Definition Database.php:291
doInsertSelectNative( $destTable, $srcTable, array $varMap, $conds, $fname, array $insertOptions, array $selectOptions, $selectJoinConds)
Native server-side implementation of insertSelect() for situations where we don't want to select ever...
getSessionLagStatus()
Get a cached estimate of the seconds of replication lag on this database server, using the estimate o...
CriticalSectionProvider null $csProvider
Definition Database.php:40
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.Since MW 1.42, this will no longer include MySQL views....
fieldExists( $table, $field, $fname=__METHOD__)
Determines whether a field exists in a table.bool Whether $table has field $field query}
newExceptionAfterConnectError( $error)
__destruct()
Run a few simple checks and close dangling connections.
dbSchema( $schema=null)
Get/set the db schema.string The previous db schema
Definition Database.php:317
endAtomic( $fname=__METHOD__)
Ends an atomic section of SQL statements.Ends the next section of atomic SQL statements and commits t...
setSessionOptions(array $options)
Override database's default behavior.
string[] int[] float[] $connectionVariables
SQL variables values to use for all new connections.
Definition Database.php:76
string $agent
Agent name for query profiling.
Definition Database.php:72
newDeleteQueryBuilder()
Get a DeleteQueryBuilder bound to this connection.
estimateRowCount( $tables, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Estimate the number of rows in dataset.MySQL allows you to estimate the number of rows that would be ...
getDomainID()
Return the currently selected domain ID.Null components (database/schema) might change once a connect...
Definition Database.php:401
closeConnection()
Closes underlying database connection.
buildSelectSubquery( $tables, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Equivalent to IDatabase::selectSQLText() except wraps the result in Subquery.IDatabase::selectSQLText...
lockForUpdate( $table, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Lock all rows meeting the given conditions/options FOR UPDATE.int Number of matching rows found (and ...
bitNot( $field)
string
buildLeast( $fields, $values)
Build a LEAST function statement comparing columns/values.Integer and float values in $values will no...
DatabaseFlags $flagsHolder
Definition Database.php:57
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
isKnownStatementRollbackError( $errno)
explicitTrxActive()
Check whether there is a transaction open at the specific request of a caller.Explicit transactions a...
serverIsReadOnly()
bool Whether this DB server is running in server-side read-only mode query} 1.28
lastDoneWrites()
Get the last time that the connection was used to commit a write.Should only be called from the rdbms...
Definition Database.php:369
bitAnd( $fieldLeft, $fieldRight)
string
array< string, array > $sessionNamedLocks
Map of (lock name => (UNIX time,trx ID))
Definition Database.php:93
registerTempTables(Query $query)
Register creation and dropping of temporary tables.
Definition Database.php:599
doBegin( $fname)
Issues the BEGIN command to the database server.
writesOrCallbacksPending()
Whether there is a transaction open with either possible write queries or unresolved pre-commit/commi...
initConnection()
Initialize the connection to the database over the wire (or to local files)
Definition Database.php:251
onTransactionResolution(callable $callback, $fname=__METHOD__)
Run a callback when the current transaction commits or rolls back.An error is thrown if no transactio...
dropTable( $table, $fname=__METHOD__)
Delete a table.bool Whether the table already existed
indexUnique( $table, $index, $fname=__METHOD__)
Determines if a given index is unique.bool|null Returns null if the index does not exist query}
string null $serverName
Readable name or host/IP of the database server.
Definition Database.php:64
array< string, mixed > $connectionParams
Connection parameters used by initConnection() and open()
Definition Database.php:74
runTransactionListenerCallbacks( $trigger, array &$errors=[])
Actually run any "transaction listener" callbacks.
databasesAreIndependent()
Returns true if DBs are assumed to be on potentially different servers.In systems like mysql/mariadb,...
assertHasConnectionHandle()
Make sure there is an open connection handle (alive or not)
Definition Database.php:532
andExpr(array $conds)
See Expression::__construct()1.43AndExpressionGroup
callable $deprecationLogger
Deprecation logging callback.
Definition Database.php:46
doHandleSessionLossPreconnect()
Reset any additional subclass trx* and session* fields.
sourceFile( $filename, ?callable $lineCallback=null, ?callable $resultCallback=null, $fname=false, ?callable $inputCallback=null)
Read and execute SQL commands from a file.Returns true on success, error string or exception on failu...
array< string, array< string, $sessionTempTables=[];protected int $lastQueryAffectedRows=0;protected int|null $lastQueryInsertId;protected int|null $lastEmulatedAffectedRows;protected int|null $lastEmulatedInsertId;protected string $lastConnectError='';private float $lastPing=0.0;private float|null $lastWriteTime;private string|false $lastPhpError=false;private int|null $csmId;private string|null $csmFname;private Exception|null $csmError;public const ATTR_DB_IS_FILE='db-is-file';public const ATTR_DB_LEVEL_LOCKING='db-level-locking';public const ATTR_SCHEMAS_AS_TABLE_GROUPS='supports-schemas';public const NEW_UNCONNECTED=0;public const NEW_CONNECTED=1;protected const ERR_NONE=0;protected const ERR_RETRY_QUERY=1;protected const ERR_ABORT_QUERY=2;protected const ERR_ABORT_TRX=4;protected const ERR_ABORT_SESSION=8;protected const DROPPED_CONN_BLAME_THRESHOLD_SEC=3.0;=private const NOT_APPLICABLE 'n/a';private const PING_TTL=1.0;private const PING_QUERY='SELECT 1 AS ping';protected const CONN_HOST='host';protected const CONN_USER='user';protected const CONN_PASSWORD='password';protected const CONN_INITIAL_DB='dbname';protected const CONN_INITIAL_SCHEMA='schema';protected const CONN_INITIAL_TABLE_PREFIX='tablePrefix';protected SQLPlatform $platform;protected ReplicationReporter $replicationReporter;public function __construct(array $params) { $this->logger=$params[ 'logger'] ?? new NullLogger();$this-> transactionManager
TempTableInfo>> Map of (DB name => table name => info)
Definition Database.php:183
deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname=__METHOD__)
Delete all rows in a table that match a condition which includes a join.For safety,...
factorConds( $condsArray)
Given an array of condition arrays representing an OR list of AND lists, for example:(A=1 AND B=2) OR...
unionSupportsOrderAndLimit()
Determine if the RDBMS supports ORDER BY and LIMIT for separate subqueries within UNION....
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Creates a new table with structure copied from existing table.Note that unlike most database abstract...
anyChar()
Returns a token for buildLike() that denotes a '_' to be used in a LIKE query.LikeMatch
doUnlock(string $lockName, string $method)
restoreFlags( $state=self::RESTORE_PRIOR)
Restore the flags to their prior state before the last setFlag/clearFlag call.1.28
getValueTypesForWithClause( $table)
isInsertSelectSafe(array $insertOptions, array $selectOptions, $fname)
doAtomicSection( $fname, callable $callback, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Perform an atomic section of reversible SQL statements from a callback.The $callback takes the follow...
flushSnapshot( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Commit any transaction but error out if writes or callbacks are pending.This is intended for clearing...
setTrxEndCallbackSuppression( $suppress)
Whether to disable running of post-COMMIT/ROLLBACK callbacks.
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition Database.php:416
affectedRows()
Get the number of rows affected by the last query method call.This method should only be called when ...
isQueryTimeoutError( $errno)
Checks whether the cause of the error is detected to be a timeout.
buildSubstring( $input, $startPosition, $length=null)
isConnectionError( $errno)
Do not use this method outside of Database/DBError classes.
insert( $table, $rows, $fname=__METHOD__, $options=[])
Insert row(s) into a table, in the provided order.This operation will be seen by affectedRows()/inser...
selectDomain( $domain)
Set the current domain (database, schema, and table prefix)This will throw an error for some database...
streamStatementEnd(&$sql, &$newLine)
Called by sourceStream() to check if we've reached a statement end.
array $lbInfo
Current LoadBalancer tracking information.
Definition Database.php:85
setTransactionManager(TransactionManager $transactionManager)
newReplaceQueryBuilder()
Get a ReplaceQueryBuilder bound to this connection.
selectRowCount( $tables, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Get the number of rows in dataset.This is useful when trying to do COUNT(*) but with a LIMIT for perf...
bool $ssl
Whether to use SSL connections.
Definition Database.php:81
replaceLostConnection( $lastErrno, $fname)
Close any existing (dead) database connection and open a new connection.
insertId()
Get the sequence-based ID assigned by the last query method call.This method should only be called wh...
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.string
tableName(string $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.This does two important things: it quo...
bitOr( $fieldLeft, $fieldRight)
string
clearFlag( $flag, $remember=self::REMEMBER_NOTHING)
Clear a flag for this connection.
select( $tables, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.New callers should use newSe...
DatabaseDomain $currentDomain
Definition Database.php:55
LoggerInterface $logger
Definition Database.php:42
tableNamesN(... $tables)
Fetch a number of table names into a zero-indexed numerical array.Much like tableName(),...
connectionErrorLogger( $errno, $errstr)
Error handler for logging errors during database connection.
Definition Database.php:458
doFlushSession( $fname)
Reset the server-side session state for named locks and table locks.
unionQueries( $sqls, $all, $options=[])
Construct a UNION query.This is used for providing overload point for other DB abstractions not compa...
replace( $table, $uniqueKeys, $rows, $fname=__METHOD__)
Insert row(s) into a table, in the provided order, while deleting conflicting rows....
indexExists( $table, $index, $fname=__METHOD__)
Determines whether an index exists.bool query}
limitResult( $sql, $limit, $offset=false)
Construct a LIMIT query with optional offset.The SQL should be adjusted so that only the first $limit...
pendingWriteCallers()
Get the list of method names that did write queries for this transaction.array 1.27
pendingWriteQueryDuration( $type=self::ESTIMATE_TOTAL)
Get the time spend running write queries for this transaction.High values could be due to scanning,...
int null $receiveTimeout
Maximum seconds to wait on receiving query results.
Definition Database.php:70
startAtomic( $fname=__METHOD__, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Begin an atomic section of SQL statements.Start an implicit transaction if no transaction is already ...
commit( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Commits a transaction previously started using begin()If no transaction is in progress,...
getLBInfo( $name=null)
Get properties passed down from the server info array of the load balancer.should not be called outsi...
Definition Database.php:341
lock( $lockName, $method, $timeout=5, $flags=0)
Acquire a named lock.Named locks are not related to transactionsbool|float|null Success (bool); acqui...
__sleep()
Called by serialize.
addIdentifierQuotes( $s)
Escape a SQL identifier (e.g.table, column, database) for use in a SQL queryDepending on the database...
callable null $profiler
Definition Database.php:48
buildStringCast( $field)
string 1.28 in IDatabase, moved to ISQLPlatform in 1.39
getInsertIdColumnForUpsert( $table)
int $nonNativeInsertSelectBatchSize
Row batch size to use for emulated INSERT SELECT queries.
Definition Database.php:78
indexInfo( $table, $index, $fname=__METHOD__)
Get information about an index into an object.
sourceStream( $fp, ?callable $lineCallback=null, ?callable $resultCallback=null, $fname=__METHOD__, ?callable $inputCallback=null)
Read and execute commands from an open file handle.Returns true on success, error string or exception...
doSelectDomain(DatabaseDomain $domain)
getLogContext(array $extras=[])
Create a log context to pass to PSR-3 logger functions.
Definition Database.php:468
doSingleStatementQuery(string $sql)
Run a query and return a QueryStatus instance with the query result information.
trxLevel()
Gets the current transaction level.Historically, transactions were allowed to be "nested"....
buildGroupConcat( $field, $delim)
Build a GROUP_CONCAT expression.string
newInsertQueryBuilder()
Get a InsertQueryBuilder bound to this connection.
setSchemaVars( $vars)
Set schema variables to be used when streaming commands from SQL files or stdin.Variables appear as S...
selectFieldValues( $tables, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.If no result rows are r...
upsert( $table, array $rows, $uniqueKeys, array $set, $fname=__METHOD__)
Upsert row(s) into a table, in the provided order, while updating conflicting rows....
commenceCriticalSection(string $fname)
Demark the start of a critical section of session/transaction state changes.
decodeBlob( $b)
Some DBMSs return a special placeholder object representing blob fields in result objects....
setFlag( $flag, $remember=self::REMEMBER_NOTHING)
Set a flag for this connection.
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
truncateTable( $table, $fname=__METHOD__)
Delete all data in a table and reset any sequences owned by that table.1.42
runOnTransactionIdleCallbacks( $trigger, array &$errors=[])
Consume and run any "on transaction idle/resolution" callbacks.
doLockIsFree(string $lockName, string $method)
executeQuery( $sql, $fname, $flags)
Execute a query without enforcing public (non-Database) caller restrictions.
Definition Database.php:663
static getCacheSetOptions(?IReadableDatabase ... $dbs)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
getPrimaryPos()
Get the replication position of this primary DB server.DBPrimaryPos|false Position; false if this is ...
conditional( $cond, $caseTrueExpression, $caseFalseExpression)
Returns an SQL expression for a simple conditional.This doesn't need to be overridden unless CASE isn...
buildLike( $param,... $params)
LIKE statement wrapper.This takes a variable-length argument list with parts of pattern to match cont...
checkInsertWarnings(Query $query, $fname)
Check for warnings after performing an INSERT query, and throw exceptions if necessary.
makeWhereFrom2d( $data, $baseKey, $subKey)
Build a "OR" condition with pairs from a two-dimensional array.The associative array should have inte...
unlock( $lockName, $method)
Release a lock.Named locks are not related to transactionsbool Success query}
orExpr(array $conds)
See Expression::__construct()1.43OrExpressionGroup
ping()
Ping the server and try to reconnect if it there is no connection.bool Success or failure
getServer()
Get the hostname or IP address of the server.string|null
getLag()
Get the seconds of replication lag on this database server.Callers should avoid using this method whi...
onTransactionPreCommitOrIdle(callable $callback, $fname=__METHOD__)
Run a callback before the current transaction commits or now if there is none.If there is a transacti...
writesPending()
bool Whether there is a transaction open with possible write queries 1.27
string false $delimiter
Current SQL query delimiter.
Definition Database.php:87
getTableAliases()
Return current table aliases.only to be used inside rdbms library
selectRow( $tables, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Wrapper to IDatabase::select() that only fetches one row (via LIMIT)If the query returns no rows,...
buildComparison(string $op, array $conds)
Build a condition comparing multiple values, for use with indexes that cover multiple fields,...
flushSession( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Release important session-level state (named lock, table locks) as post-rollback cleanup....
__clone()
Make sure that copies do not share the same client binding handle.
close( $fname=__METHOD__)
Close the database connection.This should only be called after any transactions have been resolved,...
Definition Database.php:477
anyString()
Returns a token for buildLike() that denotes a '' to be used in a LIKE query.LikeMatch
getInfinity()
Find out when 'infinity' is.Most DBMSes support this. This is a special keyword for timestamps in Pos...
getServerName()
Get the readable name for the server.string Readable server name, falling back to the hostname or IP ...
reportQueryError( $error, $errno, $sql, $fname, $ignore=false)
Report a query error.
getScopedLockAndFlush( $lockKey, $fname, $timeout)
Acquire a named lock, flush any transaction, and return an RAII style unlocker object....
decodeExpiry( $expiry, $format=TS::MW)
Decode an expiry time into a DBMS independent format.string
encodeExpiry( $expiry)
Encode an expiry time into the DBMS dependent format.string
tableExists( $table, $fname=__METHOD__)
Query whether a given table exists.bool query}
rollback( $fname=__METHOD__, $flush=self::FLUSHING_ONE)
Rollback a transaction previously started using begin()Only call this from code with outer transactio...
insertSelect( $destTable, $srcTable, $varMap, $conds, $fname=__METHOD__, $insertOptions=[], $selectOptions=[], $selectJoinConds=[])
INSERT SELECT wrapper.If the insert will use an auto-increment or sequence to determine the value of ...
lockIsFree( $lockName, $method)
Check to see if a named lock is not locked by any thread (non-blocking)bool query} 1....
isOpen()
bool Whether a connection to the database open
Definition Database.php:396
getFlag( $flag)
Returns a boolean whether the flag $flag is set for this connection.bool
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.These can be used to make conjunctions or disjunctions...
selectSQLText( $tables, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Take the same arguments as IDatabase::select() and return the SQL it would use.This can be useful for...
timestampOrNull( $ts=null)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
cancelAtomic( $fname=__METHOD__, ?AtomicSectionIdentifier $sectionId=null)
Cancel an atomic section of SQL statements.This will roll back only the statements executed since the...
if(is_string( $params['sqlMode'] ?? null)) $flags
Definition Database.php:210
update( $table, $set, $conds, $fname=__METHOD__, $options=[])
Update all rows in a table that match a given condition.This operation will be seen by affectedRows()...
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query statement and return the result.If a connection loss is detected,...
Definition Database.php:620
buildGreatest( $fields, $values)
Build a GREATEST function statement comparing columns/values.Integer and float values in $values will...
getBindingHandle()
Get the underlying binding connection handle.
implicitOrderby()
Returns true if this database does an implicit order by when the column has an index For example: SEL...
newUnionQueryBuilder()
Get a UnionQueryBuilder bound to this connection.
doLock(string $lockName, string $method, int $timeout)
lastInsertId()
Get a row ID from the last insert statement to implicitly assign one within the session.
bool $strictWarnings
Whether to check for warnings.
Definition Database.php:83
strreplace( $orig, $old, $new)
Returns a SQL expression for simple string replacement (e.g.REPLACE() in mysql)string
buildGroupConcatField( $delim, $tables, $field, $conds='', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.This is useful for combining a field for sev...
getDBname()
Get the current database name; null if there isn't one.string|null
onTransactionCommitOrIdle(callable $callback, $fname=__METHOD__)
Run a callback when the current transaction commits or now if there is none.If there is a transaction...
completeCriticalSection(string $fname, ?CriticalSectionScope $csm, ?Throwable $trxError=null)
Demark the completion of a critical section of session/transaction state changes.
selectField( $tables, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.If no result rows are returned...
A query builder for DELETE queries with a fluent interface.
A composite leaf representing an expression.
static newFromQuery(Query $query, $prefix)
Build INSERT queries with a fluent interface.
Representing a group of expressions chained via OR.
static buildQuery(string $sql, $flags, string $tablePrefix='')
Holds information on Query to be executed.
Definition Query.php:17
getWriteTable()
Get the table which is being written to, or null for a read query or if the destination is unknown.
Definition Query.php:108
Raw SQL value to be used in query builders.
Build REPLACE queries with a fluent interface.
Build SELECT queries with a fluent interface.
A query builder for UNION queries takes SelectQueryBuilder objects.
Build UPDATE queries with a fluent interface.
A no-op tracer that creates no-op spans and persists no data.
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 250, 300,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ImageLinksSchemaMigrationStage'=> 769, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'UseLegacyMediaStyles' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCEngines' => [ 'redis' => 'MediaWiki\\RCFeed\\RedisPubSubFeedEngine', 'udp' => 'MediaWiki\\RCFeed\\UDPRCFeedEngine', ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCache' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => false, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ImageLinksSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'RCEngines' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCache' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], 'required' => [ 'url', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
An object representing a primary or replica DB position in a replicated setup.
Internal interface for relational database handles exposed to their owner.
Advanced database interface for IDatabase handles that include maintenance methods.
A database connection without write operations.
Represents an OpenTelemetry span, i.e.
Base interface for an OpenTelemetry tracer responsible for creating spans.
$source