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_SERVER = 'server';
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 const CONN_HOST = self::CONN_SERVER;
173
175 protected $platform;
176
178 protected $replicationReporter;
179
184 public function __construct( array $params ) {
185 $this->logger = $params['logger'] ?? new NullLogger();
187 $this->logger,
188 $params['trxProfiler']
189 );
191 self::CONN_SERVER => ( isset( $params['host'] ) && $params['host'] !== '' )
192 ? $params['host']
193 : null,
194 self::CONN_USER => ( isset( $params['user'] ) && $params['user'] !== '' )
195 ? $params['user']
196 : null,
197 self::CONN_INITIAL_DB => ( isset( $params['dbname'] ) && $params['dbname'] !== '' )
198 ? $params['dbname']
199 : null,
200 self::CONN_INITIAL_SCHEMA => ( isset( $params['schema'] ) && $params['schema'] !== '' )
201 ? $params['schema']
202 : null,
203 self::CONN_PASSWORD => is_string( $params['password'] ) ? $params['password'] : null,
204 self::CONN_INITIAL_TABLE_PREFIX => (string)$params['tablePrefix']
205 ];
206
207 $this->lbInfo = $params['lbInfo'] ?? [];
208 $this->connectionVariables = $params['variables'] ?? [];
209 // Set SQL mode, default is turning them all off, can be overridden or skipped with null
210 if ( is_string( $params['sqlMode'] ?? null ) ) {
211 $this->connectionVariables['sql_mode'] = $params['sqlMode'];
212 }
213 $flags = (int)$params['flags'];
214 $this->flagsHolder = new DatabaseFlags( $flags );
215 $this->ssl = $params['ssl'] ?? (bool)( $flags & self::DBO_SSL );
216 $this->connectTimeout = $params['connectTimeout'] ?? null;
217 $this->receiveTimeout = $params['receiveTimeout'] ?? null;
218 $this->cliMode = (bool)$params['cliMode'];
219 $this->agent = (string)$params['agent'];
220 $this->serverName = $params['serverName'];
221 $this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'] ?? 10000;
222 $this->strictWarnings = !empty( $params['strictWarnings'] );
223
224 $this->profiler = is_callable( $params['profiler'] ) ? $params['profiler'] : null;
225 $this->errorLogger = $params['errorLogger'];
226 $this->deprecationLogger = $params['deprecationLogger'];
227
228 $this->csProvider = $params['criticalSectionProvider'] ?? null;
229
230 // Set initial dummy domain until open() sets the final DB/prefix
232 $params['dbname'] != '' ? $params['dbname'] : null,
233 $params['schema'] != '' ? $params['schema'] : null,
234 $params['tablePrefix']
235 );
236 $this->platform = new SQLPlatform(
237 $this,
238 $this->logger,
239 $this->currentDomain,
240 $this->errorLogger
241 );
242 $this->tracer = $params['tracer'] ?? new NoopTracer();
243 // Children classes must set $this->replicationReporter.
244 }
245
254 final public function initConnection() {
255 if ( $this->isOpen() ) {
256 throw new LogicException( __METHOD__ . ': already connected' );
257 }
258 // Establish the connection
259 $this->open(
260 $this->connectionParams[self::CONN_SERVER],
261 $this->connectionParams[self::CONN_USER],
262 $this->connectionParams[self::CONN_PASSWORD],
263 $this->connectionParams[self::CONN_INITIAL_DB],
264 $this->connectionParams[self::CONN_INITIAL_SCHEMA],
265 $this->connectionParams[self::CONN_INITIAL_TABLE_PREFIX]
266 );
267 $this->lastPing = microtime( true );
268 }
269
281 abstract protected function open( $server, $user, $password, $db, $schema, $tablePrefix );
282
287 public static function getAttributes() {
288 return [];
289 }
290
294 public function setLogger( LoggerInterface $logger ): void {
295 $this->logger = $logger;
296 }
297
299 public function getServerInfo() {
300 return $this->getServerVersion();
301 }
302
304 public function tablePrefix( $prefix = null ) {
305 $old = $this->currentDomain->getTablePrefix();
306
307 if ( $prefix !== null ) {
308 $this->currentDomain = new DatabaseDomain(
309 $this->currentDomain->getDatabase(),
310 $this->currentDomain->getSchema(),
311 $prefix
312 );
313 $this->platform->setCurrentDomain( $this->currentDomain );
314 }
315
316 return $old;
317 }
318
320 public function dbSchema( $schema = null ) {
321 $old = $this->currentDomain->getSchema();
322
323 if ( $schema !== null ) {
324 if ( $schema !== '' && $this->getDBname() === null ) {
325 throw new DBUnexpectedError(
326 $this,
327 "Cannot set schema to '$schema'; no database set"
328 );
329 }
330
331 $this->currentDomain = new DatabaseDomain(
332 $this->currentDomain->getDatabase(),
333 // DatabaseDomain uses null for unspecified schemas
334 ( $schema !== '' ) ? $schema : null,
335 $this->currentDomain->getTablePrefix()
336 );
337 $this->platform->setCurrentDomain( $this->currentDomain );
338 }
339
340 return (string)$old;
341 }
342
344 public function getLBInfo( $name = null ) {
345 if ( $name === null ) {
346 return $this->lbInfo;
347 }
348
349 if ( array_key_exists( $name, $this->lbInfo ) ) {
350 return $this->lbInfo[$name];
351 }
352
353 return null;
354 }
355
357 public function setLBInfo( $nameOrArray, $value = null ) {
358 if ( is_array( $nameOrArray ) ) {
359 $this->lbInfo = $nameOrArray;
360 } elseif ( is_string( $nameOrArray ) ) {
361 if ( $value !== null ) {
362 $this->lbInfo[$nameOrArray] = $value;
363 } else {
364 unset( $this->lbInfo[$nameOrArray] );
365 }
366 } else {
367 throw new InvalidArgumentException( "Got non-string key" );
368 }
369 }
370
372 public function lastDoneWrites() {
373 return $this->lastWriteTime;
374 }
375
381 public function sessionLocksPending() {
382 return (bool)$this->sessionNamedLocks;
383 }
384
388 final protected function getTransactionRoundFname() {
389 if ( $this->flagsHolder->hasImplicitTrxFlag() ) {
390 // LoadBalancer transaction round participation is enabled for this DB handle;
391 // get the owner of the active explicit transaction round (if any)
392 return $this->getLBInfo( self::LB_TRX_ROUND_FNAME );
393 }
394
395 return null;
396 }
397
399 public function isOpen() {
400 return (bool)$this->conn;
401 }
402
404 public function getDomainID() {
405 return $this->currentDomain->getId();
406 }
407
414 abstract public function strencode( $s );
415
419 protected function installErrorHandler() {
420 $this->lastPhpError = false;
421 $this->htmlErrors = ini_set( 'html_errors', '0' );
422 set_error_handler( $this->connectionErrorLogger( ... ) );
423 }
424
430 protected function restoreErrorHandler() {
431 restore_error_handler();
432 if ( $this->htmlErrors !== false ) {
433 ini_set( 'html_errors', $this->htmlErrors );
434 }
435
436 return $this->getLastPHPError();
437 }
438
442 protected function getLastPHPError() {
443 if ( $this->lastPhpError ) {
444 $error = preg_replace( '!\[<a.*</a>\]!', '', $this->lastPhpError );
445 $error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
446
447 return $error;
448 }
449
450 return false;
451 }
452
461 public function connectionErrorLogger( $errno, $errstr ) {
462 $this->lastPhpError = $errstr;
463 }
464
471 protected function getLogContext( array $extras = [] ) {
472 return $extras + [
473 'db_server' => $this->getServerName(),
474 'db_name' => $this->getDBname(),
475 'db_user' => $this->connectionParams[self::CONN_USER] ?? null,
476 ];
477 }
478
480 final public function close( $fname = __METHOD__ ) {
481 $error = null; // error to throw after disconnecting
482
483 $wasOpen = (bool)$this->conn;
484 // This should mostly do nothing if the connection is already closed
485 if ( $this->conn ) {
486 // Roll back any dangling transaction first
487 if ( $this->trxLevel() ) {
488 $error = $this->transactionManager->trxCheckBeforeClose( $this, $fname );
489 // Rollback the changes and run any callbacks as needed
490 $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
491 $this->runTransactionPostRollbackCallbacks();
492 }
493
494 // Close the actual connection in the binding handle
495 $closed = $this->closeConnection();
496 } else {
497 $closed = true; // already closed; nothing to do
498 }
499
500 $this->conn = null;
501
502 // Log any unexpected errors after having disconnected
503 if ( $error !== null ) {
504 // T217819, T231443: this is probably just LoadBalancer trying to recover from
505 // errors and shutdown. Log any problems and move on since the request has to
506 // end one way or another. Throwing errors is not very useful at some point.
507 $this->logger->error( $error, [ 'db_log_category' => 'query' ] );
508 }
509
510 // Note that various subclasses call close() at the start of open(), which itself is
511 // called by replaceLostConnection(). In that case, just because onTransactionResolution()
512 // callbacks are pending does not mean that an exception should be thrown. Rather, they
513 // will be executed after the reconnection step.
514 if ( $wasOpen ) {
515 // Double check that no callbacks are dangling
516 $fnames = $this->pendingWriteAndCallbackCallers();
517 if ( $fnames ) {
518 throw new RuntimeException(
519 "Transaction callbacks are still pending: " . implode( ', ', $fnames )
520 );
521 }
522 }
523
524 return $closed;
525 }
526
535 final protected function assertHasConnectionHandle() {
536 if ( !$this->isOpen() ) {
537 throw new DBUnexpectedError( $this, "DB connection was already closed" );
538 }
539 }
540
546 abstract protected function closeConnection();
547
575 abstract protected function doSingleStatementQuery( string $sql ): QueryStatus;
576
584 private function hasPermanentTable( Query $query ) {
585 if ( $query->getVerb() === 'CREATE TEMPORARY' ) {
586 // Temporary table creation is allowed
587 return false;
588 }
589 $table = $query->getWriteTable();
590 if ( $table === null ) {
591 // Parse error? Assume permanent.
592 return true;
593 }
594 [ $db, $pt ] = $this->platform->getDatabaseAndTableIdentifier( $table );
595 $tempInfo = $this->sessionTempTables[$db][$pt] ?? null;
596 return !$tempInfo || $tempInfo->pseudoPermanent;
597 }
598
602 protected function registerTempTables( Query $query ) {
603 $table = $query->getWriteTable();
604 if ( $table === null ) {
605 return;
606 }
607 switch ( $query->getVerb() ) {
608 case 'CREATE TEMPORARY':
609 [ $db, $pt ] = $this->platform->getDatabaseAndTableIdentifier( $table );
610 $this->sessionTempTables[$db][$pt] = new TempTableInfo(
611 $this->transactionManager->getTrxId(),
612 (bool)( $query->getFlags() & self::QUERY_PSEUDO_PERMANENT )
613 );
614 break;
615
616 case 'DROP':
617 [ $db, $pt ] = $this->platform->getDatabaseAndTableIdentifier( $table );
618 unset( $this->sessionTempTables[$db][$pt] );
619 }
620 }
621
623 public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
624 if ( !( $sql instanceof Query ) ) {
625 $flags = (int)$flags; // b/c; this field used to be a bool
626 $sql = QueryBuilderFromRawSql::buildQuery( $sql, $flags, $this->currentDomain->getTablePrefix() );
627 } else {
628 $flags = $sql->getFlags();
629 }
630
631 // Make sure that this caller is allowed to issue this query statement
632 $this->assertQueryIsCurrentlyAllowed( $sql->getVerb(), $fname );
633
634 // Send the query to the server and fetch any corresponding errors
635 $status = $this->executeQuery( $sql, $fname, $flags );
636 if ( $status->res === false ) {
637 // An error occurred; log, and, if needed, report an exception.
638 // Errors that corrupt the transaction/session state cannot be silenced.
639 $ignore = (
640 $this->flagsHolder::contains( $flags, self::QUERY_SILENCE_ERRORS ) &&
641 !$this->flagsHolder::contains( $status->flags, self::ERR_ABORT_SESSION ) &&
642 !$this->flagsHolder::contains( $status->flags, self::ERR_ABORT_TRX )
643 );
644 $this->reportQueryError( $status->message, $status->code, $sql->getSQL(), $fname, $ignore );
645 }
646
647 return $status->res;
648 }
649
666 final protected function executeQuery( $sql, $fname, $flags ) {
667 $this->assertHasConnectionHandle();
668
669 $isPermWrite = false;
670 $isWrite = $sql->isWriteQuery();
671 if ( $isWrite ) {
672 ChangedTablesTracker::recordQuery( $this->currentDomain, $sql );
673 // Permit temporary table writes on replica connections, but require a writable
674 // master connection for writes to persistent tables.
675 if ( $this->hasPermanentTable( $sql ) ) {
676 $isPermWrite = true;
677 $info = $this->getReadOnlyReason();
678 if ( $info ) {
679 [ $reason, $source ] = $info;
680 if ( $source === 'role' ) {
681 throw new DBReadOnlyRoleError( $this, "Database is read-only: $reason" );
682 } else {
683 throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
684 }
685 }
686 // DBConnRef uses QUERY_REPLICA_ROLE to enforce replica roles during query()
687 if ( $this->flagsHolder::contains( $sql->getFlags(), self::QUERY_REPLICA_ROLE ) ) {
688 throw new DBReadOnlyRoleError(
689 $this,
690 "Cannot write; target role is DB_REPLICA"
691 );
692 }
693 }
694 }
695
696 // Whether a silent retry attempt is left for recoverable connection loss errors
697 $retryLeft = !$this->flagsHolder::contains( $flags, self::QUERY_NO_RETRY );
698
699 $cs = $this->commenceCriticalSection( __METHOD__ );
700
701 do {
702 // Start a DBO_TRX wrapper transaction as needed (throw an error on failure)
703 if ( $this->beginIfImplied( $sql, $fname, $flags ) ) {
704 // Since begin() was called, any connection loss was already handled
705 $retryLeft = false;
706 }
707 // Send the query statement to the server and fetch any results.
708 $status = $this->attemptQuery( $sql, $fname, $isPermWrite );
709 } while (
710 // An error occurred that can be recovered from via query retry
711 $this->flagsHolder::contains( $status->flags, self::ERR_RETRY_QUERY ) &&
712 // The retry has not been exhausted (consume it now)
713 // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
714 $retryLeft && !( $retryLeft = false )
715 );
716
717 // Register creation and dropping of temporary tables
718 if ( $status->res ) {
719 $this->registerTempTables( $sql );
720 }
721 $this->completeCriticalSection( __METHOD__, $cs );
722
723 return $status;
724 }
725
747 private function attemptQuery(
748 $sql,
749 string $fname,
750 bool $isPermWrite
751 ) {
752 // Transaction attributes before issuing this query
753 $priorSessInfo = new CriticalSessionInfo(
754 $this->transactionManager->getTrxId(),
755 $this->transactionManager->explicitTrxActive(),
756 $this->transactionManager->pendingWriteCallers(),
757 $this->transactionManager->pendingPreCommitCallbackCallers(),
758 $this->sessionNamedLocks,
759 $this->sessionTempTables
760 );
761 // Get the transaction-aware SQL string used for profiling
762 $generalizedSql = GeneralizedSql::newFromQuery(
763 $sql,
764 ( $this->replicationReporter->getTopologyRole() === self::ROLE_STREAMING_MASTER )
765 ? 'role-primary: '
766 : ''
767 );
768 // Add agent and calling method comments to the SQL
769 $cStatement = $this->makeCommentedSql( $sql->getSQL(), $fname );
770 // Start profile section
771 $ps = $this->profiler ? ( $this->profiler )( $generalizedSql->stringify() ) : null;
772 $startTime = microtime( true );
773
774 // Clear any overrides from a prior "query method". Note that this does not affect
775 // any such methods that are currently invoking query() itself since those query
776 // methods set these fields before returning.
777 $this->lastEmulatedAffectedRows = null;
778 $this->lastEmulatedInsertId = null;
779
780 // Record an OTEL span for this query.
781 $writeTableName = $sql->getWriteTable();
782 $spanName = $writeTableName ?
783 "Database {$sql->getVerb()} {$this->getDBname()}.{$writeTableName}" :
784 "Database {$sql->getVerb()} {$this->getDBname()}";
785 $span = $this->tracer->createSpan( $spanName )
786 ->setSpanKind( SpanInterface::SPAN_KIND_CLIENT )
787 ->start();
788 if ( $span->getContext()->isSampled() ) {
789 $span->setAttributes( [
790 'code.function' => $fname,
791 'db.namespace' => $this->getDBname(),
792 'db.operation.name' => $sql->getVerb(),
793 'db.query.text' => $generalizedSql->stringify(),
794 'db.system' => $this->getType(),
795 'server.address' => $this->getServerName(),
796 'db.collection.name' => $writeTableName, # nulls filtered out
797 ] );
798 }
799
800 $status = $this->doSingleStatementQuery( $cStatement );
801
802 // End profile section
803 $endTime = microtime( true );
804 $queryRuntime = max( $endTime - $startTime, 0.0 );
805 unset( $ps );
806 $span->end();
807
808 if ( $status->res !== false ) {
809 $this->lastPing = $endTime;
810 $span->setSpanStatus( SpanInterface::SPAN_STATUS_OK );
811 } else {
812 $span->setSpanStatus( SpanInterface::SPAN_STATUS_ERROR )
813 ->setAttributes( [
814 'db.response.status_code' => $status->code,
815 'exception.message' => $status->message,
816 ] );
817 }
818
819 $affectedRowCount = $status->rowsAffected;
820 $returnedRowCount = $status->rowsReturned;
821 $this->lastQueryAffectedRows = $affectedRowCount;
822
823 if ( $span->getContext()->isSampled() ) {
824 $span->setAttributes( [
825 'db.response.affected_rows' => $affectedRowCount,
826 'db.response.returned_rows' => $returnedRowCount,
827 ] );
828 }
829
830 if ( $status->res !== false ) {
831 if ( $isPermWrite ) {
832 if ( $this->trxLevel() ) {
833 $this->transactionManager->transactionWritingIn(
834 $this->getServerName(),
835 $this->getDomainID(),
836 $startTime
837 );
838 $this->transactionManager->updateTrxWriteQueryReport(
839 $sql->getSQL(),
840 $queryRuntime,
841 $affectedRowCount,
842 $fname
843 );
844 } else {
845 $this->lastWriteTime = $endTime;
846 }
847 }
848 }
849
850 $this->transactionManager->recordQueryCompletion(
851 $generalizedSql,
852 $startTime,
853 $isPermWrite,
854 $isPermWrite ? $affectedRowCount : $returnedRowCount,
855 $this->getServerName(),
856 $fname
857 );
858
859 // Check if the query failed...
860 $status->flags = $this->handleErroredQuery( $status, $sql, $fname, $queryRuntime, $priorSessInfo );
861 // Avoid the overhead of logging calls unless debug mode is enabled
862 if ( $this->flagsHolder->getFlag( self::DBO_DEBUG ) ) {
863 $this->logger->debug(
864 "{method} [{runtime_ms}ms] [{rows} rows] {db_server}: {sql}",
865 $this->getLogContext( [
866 'method' => $fname,
867 'sql' => $sql->getSQL(),
868 'domain' => $this->getDomainID(),
869 'runtime_ms' => round( $queryRuntime * 1000, 3 ),
870 'rows' => $isPermWrite ? $affectedRowCount : $returnedRowCount,
871 'db_log_category' => 'query'
872 ] )
873 );
874 }
875
876 return $status;
877 }
878
879 private function handleErroredQuery(
880 QueryStatus $status, Query $sql, string $fname, float $queryRuntime, CriticalSessionInfo $priorSessInfo
881 ): int {
882 $errflags = self::ERR_NONE;
883 $error = $status->message;
884 $errno = $status->code;
885 if ( $status->res !== false ) {
886 // Statement succeeded
887 return $errflags;
888 }
889 if ( $this->isConnectionError( $errno ) ) {
890 // Connection lost before or during the query...
891 // Determine how to proceed given the lost session state
892 $connLossFlag = $this->assessConnectionLoss(
893 $sql->getVerb(),
894 $queryRuntime,
895 $priorSessInfo
896 );
897 // Update session state tracking and try to reestablish a connection
898 $reconnected = $this->replaceLostConnection( $errno, __METHOD__ );
899 // Check if important server-side session-level state was lost
900 if ( $connLossFlag >= self::ERR_ABORT_SESSION ) {
901 $ex = $this->getQueryException( $error, $errno, $sql->getSQL(), $fname );
902 $this->transactionManager->setSessionError( $ex );
903 }
904 // Check if important server-side transaction-level state was lost
905 if ( $connLossFlag >= self::ERR_ABORT_TRX ) {
906 $ex = $this->getQueryException( $error, $errno, $sql->getSQL(), $fname );
907 $this->transactionManager->setTransactionError( $ex );
908 }
909 // Check if the query should be retried (having made the reconnection attempt)
910 if ( $connLossFlag === self::ERR_RETRY_QUERY ) {
911 $errflags |= ( $reconnected ? self::ERR_RETRY_QUERY : self::ERR_ABORT_QUERY );
912 } else {
913 $errflags |= $connLossFlag;
914 }
915 } elseif ( $this->isKnownStatementRollbackError( $errno ) ) {
916 // Query error triggered a server-side statement-only rollback...
917 $errflags |= self::ERR_ABORT_QUERY;
918 if ( $this->trxLevel() ) {
919 // Allow legacy callers to ignore such errors via QUERY_IGNORE_DBO_TRX and
920 // try/catch. However, a deprecation notice will be logged on the next query.
921 $cause = [ $error, $errno, $fname ];
922 $this->transactionManager->setTrxStatusIgnoredCause( $cause );
923 }
924 } elseif ( $this->trxLevel() ) {
925 // Some other error occurred during the query, within a transaction...
926 // Server-side handling of errors during transactions varies widely depending on
927 // the RDBMS type and configuration. There are several possible results: (a) the
928 // whole transaction is rolled back, (b) only the queries after BEGIN are rolled
929 // back, (c) the transaction is marked as "aborted" and a ROLLBACK is required
930 // before other queries are permitted. For compatibility reasons, pessimistically
931 // require a ROLLBACK query (not using SAVEPOINT) before allowing other queries.
932 $ex = $this->getQueryException( $error, $errno, $sql->getSQL(), $fname );
933 $this->transactionManager->setTransactionError( $ex );
934 $errflags |= self::ERR_ABORT_TRX;
935 } else {
936 // Some other error occurred during the query, without a transaction...
937 $errflags |= self::ERR_ABORT_QUERY;
938 }
939
940 return $errflags;
941 }
942
948 private function makeCommentedSql( $sql, $fname ): string {
949 // Add trace comment to the begin of the sql string, right after the operator.
950 // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598).
951 // NOTE: Don't add varying ids such as request id or session id to the comment.
952 // It would break aggregation of similar queries in analysis tools (see T193050#7512149)
953 $encName = preg_replace( '/[\x00-\x1F\/]/', '-', "$fname {$this->agent}" );
954 return preg_replace( '/\s|$/', " /* $encName */ ", $sql, 1 );
955 }
956
966 private function beginIfImplied( $sql, $fname, $flags ) {
967 if ( !$this->trxLevel() && $this->flagsHolder->hasApplicableImplicitTrxFlag( $flags ) ) {
968 if ( $this->platform->isTransactableQuery( $sql ) ) {
969 $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
970 $this->transactionManager->turnOnAutomatic();
971
972 return true;
973 }
974 }
975
976 return false;
977 }
978
994 private function assertQueryIsCurrentlyAllowed( string $verb, string $fname ) {
995 if ( $verb === 'USE' ) {
996 throw new DBUnexpectedError( $this, "Got USE query; use selectDomain() instead" );
997 }
998
999 if ( $verb === 'ROLLBACK' ) {
1000 // Whole transaction rollback is used for recovery
1001 // @TODO: T269161; prevent "BEGIN"/"COMMIT"/"ROLLBACK" from outside callers
1002 return;
1003 }
1004
1005 if ( $this->csmError ) {
1006 throw new DBTransactionStateError(
1007 $this,
1008 "Cannot execute query from $fname while session state is out of sync",
1009 [],
1010 $this->csmError
1011 );
1012 }
1013
1014 $this->transactionManager->assertSessionStatus( $this, $fname );
1015
1016 if ( $verb !== 'ROLLBACK TO SAVEPOINT' ) {
1017 $this->transactionManager->assertTransactionStatus(
1018 $this,
1019 $this->deprecationLogger,
1020 $fname
1021 );
1022 }
1023 }
1024
1044 private function assessConnectionLoss(
1045 string $verb,
1046 float $walltime,
1047 CriticalSessionInfo $priorSessInfo
1048 ) {
1049 if ( $walltime < self::DROPPED_CONN_BLAME_THRESHOLD_SEC ) {
1050 // Query failed quickly; the connection was probably lost before the query was sent
1051 $res = self::ERR_RETRY_QUERY;
1052 } else {
1053 // Query took a long time; the connection was probably lost during query execution
1054 $res = self::ERR_ABORT_QUERY;
1055 }
1056
1057 // List of problems causing session/transaction state corruption
1058 $blockers = [];
1059 // Loss of named locks breaks future callers relying on those locks for critical sections
1060 foreach ( $priorSessInfo->namedLocks as $lockName => $lockInfo ) {
1061 if ( $lockInfo['trxId'] && $lockInfo['trxId'] === $priorSessInfo->trxId ) {
1062 // Treat lost locks acquired during the lost transaction as a transaction state
1063 // problem. Connection loss on ROLLBACK (non-SAVEPOINT) is tolerable since
1064 // rollback automatically triggered server-side.
1065 if ( $verb !== 'ROLLBACK' ) {
1066 $res = max( $res, self::ERR_ABORT_TRX );
1067 $blockers[] = "named lock '$lockName'";
1068 }
1069 } else {
1070 // Treat lost locks acquired either during prior transactions or during no
1071 // transaction as a session state problem.
1072 $res = max( $res, self::ERR_ABORT_SESSION );
1073 $blockers[] = "named lock '$lockName'";
1074 }
1075 }
1076 // Loss of temp tables breaks future callers relying on those tables for queries
1077 foreach ( $priorSessInfo->tempTables as $domainTempTables ) {
1078 foreach ( $domainTempTables as $tableName => $tableInfo ) {
1079 if ( $tableInfo->trxId && $tableInfo->trxId === $priorSessInfo->trxId ) {
1080 // Treat lost temp tables created during the lost transaction as a
1081 // transaction state problem. Connection loss on ROLLBACK (non-SAVEPOINT)
1082 // is tolerable since rollback automatically triggered server-side.
1083 if ( $verb !== 'ROLLBACK' ) {
1084 $res = max( $res, self::ERR_ABORT_TRX );
1085 $blockers[] = "temp table '$tableName'";
1086 }
1087 } else {
1088 // Treat lost temp tables created either during prior transactions or during
1089 // no transaction as a session state problem.
1090 $res = max( $res, self::ERR_ABORT_SESSION );
1091 $blockers[] = "temp table '$tableName'";
1092 }
1093 }
1094 }
1095 // Loss of transaction writes breaks future callers and DBO_TRX logic relying on those
1096 // writes to be atomic and still pending. Connection loss on ROLLBACK (non-SAVEPOINT) is
1097 // tolerable since rollback automatically triggered server-side.
1098 if ( $priorSessInfo->trxWriteCallers && $verb !== 'ROLLBACK' ) {
1099 $res = max( $res, self::ERR_ABORT_TRX );
1100 $blockers[] = 'uncommitted writes';
1101 }
1102 if ( $priorSessInfo->trxPreCommitCbCallers && $verb !== 'ROLLBACK' ) {
1103 $res = max( $res, self::ERR_ABORT_TRX );
1104 $blockers[] = 'pre-commit callbacks';
1105 }
1106 if ( $priorSessInfo->trxExplicit && $verb !== 'ROLLBACK' && $verb !== 'COMMIT' ) {
1107 // Transaction automatically rolled back, breaking the expectations of callers
1108 // relying on the continued existence of that transaction for things like atomic
1109 // writes, serializability, or reads from the same point-in-time snapshot. If the
1110 // connection loss occurred on ROLLBACK (non-SAVEPOINT) or COMMIT, then we do not
1111 // need to mark the transaction state as corrupt, since no transaction would still
1112 // be open even if the query did succeed (T127428).
1113 $res = max( $res, self::ERR_ABORT_TRX );
1114 $blockers[] = 'explicit transaction';
1115 }
1116
1117 if ( $blockers ) {
1118 $this->logger->warning(
1119 "cannot reconnect to {db_server} silently: {error}",
1120 $this->getLogContext( [
1121 'error' => 'session state loss (' . implode( ', ', $blockers ) . ')',
1122 'exception' => new RuntimeException(),
1123 'db_log_category' => 'connection'
1124 ] )
1125 );
1126 }
1127
1128 return $res;
1129 }
1130
1134 private function handleSessionLossPreconnect() {
1135 // Clean up tracking of session-level things...
1136 // https://mariadb.com/kb/en/create-table/#create-temporary-table
1137 // https://www.postgresql.org/docs/9.2/static/sql-createtable.html (ignoring ON COMMIT)
1138 $this->sessionTempTables = [];
1139 // https://mariadb.com/kb/en/get_lock/
1140 // https://www.postgresql.org/docs/9.4/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1141 $this->sessionNamedLocks = [];
1142 // Session loss implies transaction loss (T67263)
1143 $this->transactionManager->onSessionLoss( $this );
1144 // Clear additional subclass fields
1145 $this->doHandleSessionLossPreconnect();
1146 }
1147
1151 protected function doHandleSessionLossPreconnect() {
1152 // no-op
1153 }
1154
1165 protected function isQueryTimeoutError( $errno ) {
1166 return false;
1167 }
1168
1182 public function reportQueryError( $error, $errno, $sql, $fname, $ignore = false ) {
1183 if ( $ignore ) {
1184 $this->logger->debug(
1185 "SQL ERROR (ignored): $error",
1186 [ 'db_log_category' => 'query' ]
1187 );
1188 } else {
1189 throw $this->getQueryExceptionAndLog( $error, $errno, $sql, $fname );
1190 }
1191 }
1192
1200 private function getQueryExceptionAndLog( $error, $errno, $sql, $fname ) {
1201 // Information that instances of the same problem have in common should
1202 // not be normalized (T255202).
1203 $this->logger->error(
1204 "Error $errno from $fname, {error} {sql1line} {db_server}",
1205 $this->getLogContext( [
1206 'method' => __METHOD__,
1207 'errno' => $errno,
1208 'error' => $error,
1209 'sql1line' => mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 ),
1210 'fname' => $fname,
1211 'db_log_category' => 'query',
1212 'exception' => new RuntimeException()
1213 ] )
1214 );
1215 return $this->getQueryException( $error, $errno, $sql, $fname );
1216 }
1217
1225 private function getQueryException( $error, $errno, $sql, $fname ) {
1226 if ( $this->isQueryTimeoutError( $errno ) ) {
1227 return new DBQueryTimeoutError( $this, $error, $errno, $sql, $fname );
1228 } elseif ( $this->isConnectionError( $errno ) ) {
1229 return new DBQueryDisconnectedError( $this, $error, $errno, $sql, $fname );
1230 } else {
1231 return new DBQueryError( $this, $error, $errno, $sql, $fname );
1232 }
1233 }
1234
1239 final protected function newExceptionAfterConnectError( $error ) {
1240 // Connection was not fully initialized and is not safe for use.
1241 // Stash any error associated with the handle before destroying it.
1242 $this->lastConnectError = $error;
1243 $this->conn = null;
1244
1245 $this->logger->error(
1246 "Error connecting to {db_server} as user {db_user}: {error}",
1247 $this->getLogContext( [
1248 'error' => $error,
1249 'exception' => new RuntimeException(),
1250 'db_log_category' => 'connection',
1251 ] )
1252 );
1253
1254 return new DBConnectionError( $this, $error );
1255 }
1256
1262 return new SelectQueryBuilder( $this );
1263 }
1264
1270 return new UnionQueryBuilder( $this );
1271 }
1272
1278 return new UpdateQueryBuilder( $this );
1279 }
1280
1286 return new DeleteQueryBuilder( $this );
1287 }
1288
1294 return new InsertQueryBuilder( $this );
1295 }
1296
1302 return new ReplaceQueryBuilder( $this );
1303 }
1304
1306 public function selectField(
1307 $tables, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1308 ) {
1309 if ( $var === '*' ) {
1310 throw new DBUnexpectedError( $this, "Cannot use a * field" );
1311 } elseif ( is_array( $var ) && count( $var ) !== 1 ) {
1312 throw new DBUnexpectedError( $this, 'Cannot use more than one field' );
1313 }
1314
1315 $options = $this->platform->normalizeOptions( $options );
1316 $options['LIMIT'] = 1;
1317
1318 $res = $this->select( $tables, $var, $cond, $fname, $options, $join_conds );
1319 if ( $res === false ) {
1320 throw new DBUnexpectedError( $this, "Got false from select()" );
1321 }
1322
1323 $row = $res->fetchRow();
1324 if ( $row === false ) {
1325 return false;
1326 }
1327
1328 return reset( $row );
1329 }
1330
1332 public function selectFieldValues(
1333 $tables, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1334 ): array {
1335 if ( $var === '*' ) {
1336 throw new DBUnexpectedError( $this, "Cannot use a * field" );
1337 } elseif ( !is_string( $var ) ) {
1338 throw new DBUnexpectedError( $this, "Cannot use an array of fields" );
1339 }
1340
1341 $options = $this->platform->normalizeOptions( $options );
1342 $res = $this->select( $tables, [ 'value' => $var ], $cond, $fname, $options, $join_conds );
1343 if ( $res === false ) {
1344 throw new DBUnexpectedError( $this, "Got false from select()" );
1345 }
1346
1347 $values = [];
1348 foreach ( $res as $row ) {
1349 $values[] = $row->value;
1350 }
1351
1352 return $values;
1353 }
1354
1356 public function select(
1357 $tables, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1358 ) {
1359 $options = (array)$options;
1360 // Don't turn this into using platform directly, DatabaseMySQL overrides this.
1361 $sql = $this->selectSQLText( $tables, $vars, $conds, $fname, $options, $join_conds );
1362 // Treat SELECT queries with FOR UPDATE as writes. This matches
1363 // how MySQL enforces read_only (FOR SHARE and LOCK IN SHADE MODE are allowed).
1364 $flags = in_array( 'FOR UPDATE', $options, true )
1365 ? self::QUERY_CHANGE_ROWS
1366 : self::QUERY_CHANGE_NONE;
1367
1368 $query = new Query( $sql, $flags, 'SELECT' );
1369 return $this->query( $query, $fname );
1370 }
1371
1373 public function selectRow( $tables, $vars, $conds, $fname = __METHOD__,
1374 $options = [], $join_conds = []
1375 ) {
1376 $options = (array)$options;
1377 $options['LIMIT'] = 1;
1378
1379 $res = $this->select( $tables, $vars, $conds, $fname, $options, $join_conds );
1380 if ( $res === false ) {
1381 throw new DBUnexpectedError( $this, "Got false from select()" );
1382 }
1383
1384 if ( !$res->numRows() ) {
1385 return false;
1386 }
1387
1388 return $res->fetchObject();
1389 }
1390
1394 public function estimateRowCount(
1395 $tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1396 ): int {
1397 $conds = $this->platform->normalizeConditions( $conds, $fname );
1398 $column = $this->platform->extractSingleFieldFromList( $var );
1399 if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
1400 $conds[] = "$column IS NOT NULL";
1401 }
1402
1403 $res = $this->select(
1404 $tables, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options, $join_conds
1405 );
1406 $row = $res ? $res->fetchRow() : [];
1407
1408 return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
1409 }
1410
1412 public function selectRowCount(
1413 $tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1414 ): int {
1415 $conds = $this->platform->normalizeConditions( $conds, $fname );
1416 $column = $this->platform->extractSingleFieldFromList( $var );
1417 if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
1418 $conds[] = "$column IS NOT NULL";
1419 }
1420 if ( in_array( 'DISTINCT', (array)$options ) ) {
1421 if ( $column === null ) {
1422 throw new DBUnexpectedError( $this,
1423 '$var cannot be empty when the DISTINCT option is given' );
1424 }
1425 $innerVar = $column;
1426 } else {
1427 $innerVar = '1';
1428 }
1429
1430 $res = $this->select(
1431 [
1432 'tmp_count' => $this->platform->buildSelectSubquery(
1433 $tables,
1434 $innerVar,
1435 $conds,
1436 $fname,
1437 $options,
1438 $join_conds
1439 )
1440 ],
1441 [ 'rowcount' => 'COUNT(*)' ],
1442 [],
1443 $fname
1444 );
1445 $row = $res ? $res->fetchRow() : [];
1446
1447 return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
1448 }
1449
1451 public function lockForUpdate(
1452 $table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1453 ) {
1454 if ( !$this->trxLevel() && !$this->flagsHolder->hasImplicitTrxFlag() ) {
1455 throw new DBUnexpectedError(
1456 $this,
1457 __METHOD__ . ': no transaction is active nor is DBO_TRX set'
1458 );
1459 }
1460
1461 $options = (array)$options;
1462 $options[] = 'FOR UPDATE';
1463
1464 return $this->selectRowCount( $table, '*', $conds, $fname, $options, $join_conds );
1465 }
1466
1468 public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1469 $info = $this->fieldInfo( $table, $field );
1470
1471 return (bool)$info;
1472 }
1473
1475 abstract public function tableExists( $table, $fname = __METHOD__ );
1476
1478 public function indexExists( $table, $index, $fname = __METHOD__ ) {
1479 $info = $this->indexInfo( $table, $index, $fname );
1480
1481 return (bool)$info;
1482 }
1483
1485 public function indexUnique( $table, $index, $fname = __METHOD__ ) {
1486 $info = $this->indexInfo( $table, $index, $fname );
1487
1488 return $info ? $info['unique'] : null;
1489 }
1490
1492 abstract public function getPrimaryKeyColumns( $table, $fname = __METHOD__ );
1493
1503 abstract public function indexInfo( $table, $index, $fname = __METHOD__ );
1504
1506 public function insert( $table, $rows, $fname = __METHOD__, $options = [] ) {
1507 $query = $this->platform->dispatchingInsertSqlText( $table, $rows, $options );
1508 if ( !$query ) {
1509 return true;
1510 }
1511 $this->query( $query, $fname );
1512 if ( $this->strictWarnings ) {
1513 $this->checkInsertWarnings( $query, $fname );
1514 }
1515 return true;
1516 }
1517
1526 protected function checkInsertWarnings( Query $query, $fname ) {
1527 }
1528
1530 public function update( $table, $set, $conds, $fname = __METHOD__, $options = [] ) {
1531 $query = $this->platform->updateSqlText( $table, $set, $conds, $options );
1532 $this->query( $query, $fname );
1533
1534 return true;
1535 }
1536
1538 public function databasesAreIndependent() {
1539 return false;
1540 }
1541
1543 final public function selectDomain( $domain ) {
1544 $cs = $this->commenceCriticalSection( __METHOD__ );
1545
1546 try {
1547 $this->doSelectDomain( DatabaseDomain::newFromId( $domain ) );
1548 } catch ( DBError $e ) {
1549 $this->completeCriticalSection( __METHOD__, $cs );
1550 throw $e;
1551 }
1552
1553 $this->completeCriticalSection( __METHOD__, $cs );
1554 }
1555
1562 protected function doSelectDomain( DatabaseDomain $domain ) {
1563 $this->currentDomain = $domain;
1564 $this->platform->setCurrentDomain( $this->currentDomain );
1565 }
1566
1568 public function getDBname() {
1569 return $this->currentDomain->getDatabase();
1570 }
1571
1573 public function getServer() {
1574 return $this->connectionParams[self::CONN_SERVER] ?? null;
1575 }
1576
1578 public function getServerName() {
1579 return $this->serverName ?? $this->getServer() ?? 'unknown';
1580 }
1581
1583 public function addQuotes( $s ) {
1584 if ( $s instanceof RawSQLValue ) {
1585 return $s->toSql();
1586 }
1587 if ( $s instanceof Blob ) {
1588 $s = $s->fetch();
1589 }
1590 if ( $s === null ) {
1591 return 'NULL';
1592 } elseif ( is_bool( $s ) ) {
1593 return (string)(int)$s;
1594 } elseif ( is_int( $s ) ) {
1595 return (string)$s;
1596 } else {
1597 return "'" . $this->strencode( $s ) . "'";
1598 }
1599 }
1600
1602 public function expr( string $field, string $op, $value ): Expression {
1603 return new Expression( $field, $op, $value );
1604 }
1605
1607 public function andExpr( array $conds ): AndExpressionGroup {
1608 return AndExpressionGroup::newFromArray( $conds );
1609 }
1610
1612 public function orExpr( array $conds ): OrExpressionGroup {
1613 return OrExpressionGroup::newFromArray( $conds );
1614 }
1615
1617 public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
1618 $uniqueKey = $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
1619 if ( !$rows ) {
1620 return;
1621 }
1622 $affectedRowCount = 0;
1623 $insertId = null;
1624 $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
1625 try {
1626 foreach ( $rows as $row ) {
1627 // Delete any conflicting rows (including ones inserted from $rows)
1628 $query = $this->platform->deleteSqlText(
1629 $table,
1630 [ $this->platform->makeKeyCollisionCondition( [ $row ], $uniqueKey ) ]
1631 );
1632 $this->query( $query, $fname );
1633 // Insert the new row
1634 $query = $this->platform->dispatchingInsertSqlText( $table, $row, [] );
1635 $this->query( $query, $fname );
1636 $affectedRowCount += $this->lastQueryAffectedRows;
1637 $insertId = $insertId ?: $this->lastQueryInsertId;
1638 }
1639 $this->endAtomic( $fname );
1640 } catch ( DBError $e ) {
1641 $this->cancelAtomic( $fname );
1642 throw $e;
1643 }
1644 $this->lastEmulatedAffectedRows = $affectedRowCount;
1645 $this->lastEmulatedInsertId = $insertId;
1646 }
1647
1649 public function upsert( $table, array $rows, $uniqueKeys, array $set, $fname = __METHOD__ ) {
1650 $uniqueKey = $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
1651 if ( !$rows ) {
1652 return true;
1653 }
1654 $this->platform->assertValidUpsertSetArray( $set, $uniqueKey, $rows );
1655
1656 $encTable = $this->tableName( $table );
1657 $sqlColumnAssignments = $this->makeList( $set, self::LIST_SET );
1658 // Get any AUTO_INCREMENT/SERIAL column for this table so we can set insertId()
1659 $autoIncrementColumn = $this->getInsertIdColumnForUpsert( $table );
1660 // Check if there is a SQL assignment expression in $set (as generated by SQLPlatform::buildExcludedValue)
1661 $useWith = array_any(
1662 $set,
1663 static fn ( $v, $k ) => ( $v instanceof RawSQLValue || is_int( $k ) )
1664 );
1665 // Subclasses might need explicit type casting within "WITH...AS (VALUES ...)"
1666 // so that these CTE rows can be referenced within the SET clause assignments.
1667 $typeByColumn = $useWith ? $this->getValueTypesForWithClause( $table ) : [];
1668
1669 $first = true;
1670 $affectedRowCount = 0;
1671 $insertId = null;
1672 $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
1673 try {
1674 foreach ( $rows as $row ) {
1675 // Update any existing conflicting row (including ones inserted from $rows)
1676 [ $sqlColumns, $sqlTuples, $sqlVals ] = $this->platform->makeInsertLists(
1677 [ $row ],
1678 '__',
1679 $typeByColumn
1680 );
1681 $sqlConditions = $this->platform->makeKeyCollisionCondition(
1682 [ $row ],
1683 $uniqueKey
1684 );
1685 $query = new Query(
1686 ( $useWith ? "WITH __VALS ($sqlVals) AS (VALUES $sqlTuples) " : "" ) .
1687 "UPDATE $encTable SET $sqlColumnAssignments " .
1688 "WHERE ($sqlConditions)",
1689 self::QUERY_CHANGE_ROWS,
1690 'UPDATE',
1691 $table
1692 );
1693 $this->query( $query, $fname );
1694 $rowsUpdated = $this->lastQueryAffectedRows;
1695 $affectedRowCount += $rowsUpdated;
1696 if ( $rowsUpdated > 0 ) {
1697 // Conflicting row found and updated
1698 if ( $first && $autoIncrementColumn !== null ) {
1699 // @TODO: use "RETURNING" instead (when supported by SQLite)
1700 $query = new Query(
1701 "SELECT $autoIncrementColumn AS id FROM $encTable " .
1702 "WHERE ($sqlConditions)",
1703 self::QUERY_CHANGE_NONE,
1704 'SELECT'
1705 );
1706 $sRes = $this->query( $query, $fname, self::QUERY_CHANGE_ROWS );
1707 $insertId = (int)$sRes->fetchRow()['id'];
1708 }
1709 } else {
1710 // No conflicting row found
1711 $query = new Query(
1712 "INSERT INTO $encTable ($sqlColumns) VALUES $sqlTuples",
1713 self::QUERY_CHANGE_ROWS,
1714 'INSERT',
1715 $table
1716 );
1717 $this->query( $query, $fname );
1718 $affectedRowCount += $this->lastQueryAffectedRows;
1719 }
1720 $first = false;
1721 }
1722 $this->endAtomic( $fname );
1723 } catch ( DBError $e ) {
1724 $this->cancelAtomic( $fname );
1725 throw $e;
1726 }
1727 $this->lastEmulatedAffectedRows = $affectedRowCount;
1728 $this->lastEmulatedInsertId = $insertId;
1729 return true;
1730 }
1731
1736 protected function getInsertIdColumnForUpsert( $table ) {
1737 return null;
1738 }
1739
1744 protected function getValueTypesForWithClause( $table ) {
1745 return [];
1746 }
1747
1749 public function deleteJoin(
1750 $delTable,
1751 $joinTable,
1752 $delVar,
1753 $joinVar,
1754 $conds,
1755 $fname = __METHOD__
1756 ) {
1757 $sql = $this->platform->deleteJoinSqlText( $delTable, $joinTable, $delVar, $joinVar, $conds );
1758 $query = new Query( $sql, self::QUERY_CHANGE_ROWS, 'DELETE', $delTable );
1759 $this->query( $query, $fname );
1760 }
1761
1763 public function delete( $table, $conds, $fname = __METHOD__ ) {
1764 $this->query( $this->platform->deleteSqlText( $table, $conds ), $fname );
1765
1766 return true;
1767 }
1768
1770 final public function insertSelect(
1771 $destTable,
1772 $srcTable,
1773 $varMap,
1774 $conds,
1775 $fname = __METHOD__,
1776 $insertOptions = [],
1777 $selectOptions = [],
1778 $selectJoinConds = []
1779 ) {
1780 static $hints = [ 'NO_AUTO_COLUMNS' ];
1781
1782 $insertOptions = $this->platform->normalizeOptions( $insertOptions );
1783 $selectOptions = $this->platform->normalizeOptions( $selectOptions );
1784
1785 if ( $this->cliMode && $this->isInsertSelectSafe( $insertOptions, $selectOptions, $fname ) ) {
1786 // For massive migrations with downtime, we don't want to select everything
1787 // into memory and OOM, so do all this native on the server side if possible.
1788 $this->doInsertSelectNative(
1789 $destTable,
1790 $srcTable,
1791 $varMap,
1792 $conds,
1793 $fname,
1794 array_diff( $insertOptions, $hints ),
1795 $selectOptions,
1796 $selectJoinConds
1797 );
1798 } else {
1799 $this->doInsertSelectGeneric(
1800 $destTable,
1801 $srcTable,
1802 $varMap,
1803 $conds,
1804 $fname,
1805 array_diff( $insertOptions, $hints ),
1806 $selectOptions,
1807 $selectJoinConds
1808 );
1809 }
1810
1811 return true;
1812 }
1813
1821 protected function isInsertSelectSafe( array $insertOptions, array $selectOptions, $fname ) {
1822 return true;
1823 }
1824
1839 private function doInsertSelectGeneric(
1840 $destTable,
1841 $srcTable,
1842 array $varMap,
1843 $conds,
1844 $fname,
1845 array $insertOptions,
1846 array $selectOptions,
1847 $selectJoinConds
1848 ) {
1849 // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
1850 // on only the primary DB (without needing row-based-replication). It also makes it easy to
1851 // know how big the INSERT is going to be.
1852 $fields = [];
1853 foreach ( $varMap as $dstColumn => $sourceColumnOrSql ) {
1854 $fields[] = $this->platform->fieldNameWithAlias( $sourceColumnOrSql, $dstColumn );
1855 }
1856 $res = $this->select(
1857 $srcTable,
1858 implode( ',', $fields ),
1859 $conds,
1860 $fname,
1861 array_merge( $selectOptions, [ 'FOR UPDATE' ] ),
1862 $selectJoinConds
1863 );
1864
1865 $affectedRowCount = 0;
1866 $insertId = null;
1867 if ( $res ) {
1868 $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
1869 try {
1870 $rows = [];
1871 foreach ( $res as $row ) {
1872 $rows[] = (array)$row;
1873 }
1874 // Avoid inserts that are too huge
1875 $rowBatches = array_chunk( $rows, $this->nonNativeInsertSelectBatchSize );
1876 foreach ( $rowBatches as $rows ) {
1877 $query = $this->platform->dispatchingInsertSqlText( $destTable, $rows, $insertOptions );
1878 $this->query( $query, $fname );
1879 $affectedRowCount += $this->lastQueryAffectedRows;
1880 $insertId = $insertId ?: $this->lastQueryInsertId;
1881 }
1882 $this->endAtomic( $fname );
1883 } catch ( DBError $e ) {
1884 $this->cancelAtomic( $fname );
1885 throw $e;
1886 }
1887 }
1888 $this->lastEmulatedAffectedRows = $affectedRowCount;
1889 $this->lastEmulatedInsertId = $insertId;
1890 }
1891
1907 protected function doInsertSelectNative(
1908 $destTable,
1909 $srcTable,
1910 array $varMap,
1911 $conds,
1912 $fname,
1913 array $insertOptions,
1914 array $selectOptions,
1915 $selectJoinConds
1916 ) {
1917 $sql = $this->platform->insertSelectNativeSqlText(
1918 $destTable,
1919 $srcTable,
1920 $varMap,
1921 $conds,
1922 $fname,
1923 $insertOptions,
1924 $selectOptions,
1925 $selectJoinConds
1926 );
1927 $query = new Query(
1928 $sql,
1929 self::QUERY_CHANGE_ROWS,
1930 'INSERT',
1931 $destTable
1932 );
1933 $this->query( $query, $fname );
1934 }
1935
1943 protected function isConnectionError( $errno ) {
1944 return false;
1945 }
1946
1954 protected function isKnownStatementRollbackError( $errno ) {
1955 return false; // don't know; it could have caused a transaction rollback
1956 }
1957
1961 public function serverIsReadOnly() {
1962 return false;
1963 }
1964
1966 final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
1967 $this->transactionManager->onTransactionResolution( $this, $callback, $fname );
1968 }
1969
1971 final public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
1972 if ( !$this->trxLevel() && $this->getTransactionRoundFname() !== null ) {
1973 // This DB handle is set to participate in LoadBalancer transaction rounds and
1974 // an explicit transaction round is active. Start an implicit transaction on this
1975 // DB handle (setting trxAutomatic) similar to how query() does in such situations.
1976 $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
1977 }
1978
1979 $this->transactionManager->addPostCommitOrIdleCallback( $callback, $fname );
1980 if ( !$this->trxLevel() ) {
1981 $dbErrors = [];
1982 $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE, $dbErrors );
1983 if ( $dbErrors ) {
1984 throw $dbErrors[0];
1985 }
1986 }
1987 }
1988
1990 final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
1991 if ( !$this->trxLevel() && $this->getTransactionRoundFname() !== null ) {
1992 // This DB handle is set to participate in LoadBalancer transaction rounds and
1993 // an explicit transaction round is active. Start an implicit transaction on this
1994 // DB handle (setting trxAutomatic) similar to how query() does in such situations.
1995 $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
1996 }
1997
1998 if ( $this->trxLevel() ) {
1999 $this->transactionManager->addPreCommitOrIdleCallback(
2000 $callback,
2001 $fname
2002 );
2003 } else {
2004 // No transaction is active nor will start implicitly, so make one for this callback
2005 $this->startAtomic( __METHOD__, self::ATOMIC_CANCELABLE );
2006 try {
2007 $callback( $this );
2008 } catch ( Throwable $e ) {
2009 // Avoid confusing error reporting during critical section errors
2010 if ( !$this->csmError ) {
2011 $this->cancelAtomic( __METHOD__ );
2012 }
2013 throw $e;
2014 }
2015 $this->endAtomic( __METHOD__ );
2016 }
2017 }
2018
2020 final public function setTransactionListener( $name, ?callable $callback = null ) {
2021 $this->transactionManager->setTransactionListener( $name, $callback );
2022 }
2023
2032 final public function setTrxEndCallbackSuppression( $suppress ) {
2033 $this->transactionManager->setTrxEndCallbackSuppression( $suppress );
2034 }
2035
2048 public function runOnTransactionIdleCallbacks( $trigger, array &$errors = [] ) {
2049 if ( $this->trxLevel() ) {
2050 throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open' );
2051 }
2052
2053 if ( $this->transactionManager->isEndCallbacksSuppressed() ) {
2054 // Execution deferred by LoadBalancer for explicit execution later
2055 return 0;
2056 }
2057
2058 $cs = $this->commenceCriticalSection( __METHOD__ );
2059
2060 $count = 0;
2061 $autoTrx = $this->flagsHolder->hasImplicitTrxFlag(); // automatic begin() enabled?
2062 // Drain the queues of transaction "idle" and "end" callbacks until they are empty
2063 do {
2064 $callbackEntries = $this->transactionManager->consumeEndCallbacks();
2065 $count += count( $callbackEntries );
2066 foreach ( $callbackEntries as $entry ) {
2067 $this->flagsHolder->clearFlag( self::DBO_TRX ); // make each query its own transaction
2068 try {
2069 $entry[0]( $trigger );
2070 } catch ( DBError $ex ) {
2071 ( $this->errorLogger )( $ex );
2072 $errors[] = $ex;
2073 // Some callbacks may use startAtomic/endAtomic, so make sure
2074 // their transactions are ended so other callbacks don't fail
2075 if ( $this->trxLevel() ) {
2076 $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
2077 }
2078 } finally {
2079 if ( $autoTrx ) {
2080 $this->flagsHolder->setFlag( self::DBO_TRX ); // restore automatic begin()
2081 } else {
2082 $this->flagsHolder->clearFlag( self::DBO_TRX ); // restore auto-commit
2083 }
2084 }
2085 }
2086 } while ( $this->transactionManager->countPostCommitOrIdleCallbacks() );
2087
2088 $this->completeCriticalSection( __METHOD__, $cs );
2089
2090 return $count;
2091 }
2092
2103 public function runTransactionListenerCallbacks( $trigger, array &$errors = [] ) {
2104 if ( $this->transactionManager->isEndCallbacksSuppressed() ) {
2105 // Execution deferred by LoadBalancer for explicit execution later
2106 return;
2107 }
2108
2109 // These callbacks should only be registered in setup, thus no iteration is needed
2110 foreach ( $this->transactionManager->getRecurringCallbacks() as $callback ) {
2111 try {
2112 $callback( $trigger, $this );
2113 } catch ( DBError $ex ) {
2114 ( $this->errorLogger )( $ex );
2115 $errors[] = $ex;
2116 }
2117 }
2118 }
2119
2126 private function runTransactionPostCommitCallbacks() {
2127 $dbErrors = [];
2128 $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT, $dbErrors );
2129 $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT, $dbErrors );
2130 $this->lastEmulatedAffectedRows = 0; // for the sake of consistency
2131 if ( $dbErrors ) {
2132 throw $dbErrors[0];
2133 }
2134 }
2135
2143 private function runTransactionPostRollbackCallbacks() {
2144 $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
2145 $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
2146 $this->lastEmulatedAffectedRows = 0; // for the sake of consistency
2147 }
2148
2150 final public function startAtomic(
2151 $fname = __METHOD__,
2152 $cancelable = self::ATOMIC_NOT_CANCELABLE
2153 ) {
2154 $cs = $this->commenceCriticalSection( __METHOD__ );
2155
2156 if ( $this->trxLevel() ) {
2157 // This atomic section is only one part of a larger transaction
2158 $sectionOwnsTrx = false;
2159 } else {
2160 // Start an implicit transaction (sets trxAutomatic)
2161 try {
2162 $this->begin( $fname, self::TRANSACTION_INTERNAL );
2163 } catch ( DBError $e ) {
2164 $this->completeCriticalSection( __METHOD__, $cs );
2165 throw $e;
2166 }
2167 if ( $this->flagsHolder->hasImplicitTrxFlag() ) {
2168 // This DB handle participates in LoadBalancer transaction rounds; all atomic
2169 // sections should be buffered into one transaction (e.g. to keep web requests
2170 // transactional). Note that an implicit transaction round is considered to be
2171 // active when no there is no explicit transaction round.
2172 $sectionOwnsTrx = false;
2173 } else {
2174 // This DB handle does not participate in LoadBalancer transaction rounds;
2175 // each topmost atomic section will use its own transaction.
2176 $sectionOwnsTrx = true;
2177 }
2178 $this->transactionManager->setAutomaticAtomic( $sectionOwnsTrx );
2179 }
2180
2181 if ( $cancelable === self::ATOMIC_CANCELABLE ) {
2182 if ( $sectionOwnsTrx ) {
2183 // This atomic section is synonymous with the whole transaction; just
2184 // use full COMMIT/ROLLBACK in endAtomic()/cancelAtomic(), respectively
2185 $savepointId = self::NOT_APPLICABLE;
2186 } else {
2187 // This atomic section is only part of the whole transaction; use a SAVEPOINT
2188 // query so that its changes can be cancelled without losing the rest of the
2189 // transaction (e.g. changes from other sections or from outside of sections)
2190 try {
2191 $savepointId = $this->transactionManager->nextSavePointId( $this, $fname );
2192 $sql = $this->platform->savepointSqlText( $savepointId );
2193 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'SAVEPOINT' );
2194 $this->query( $query, $fname );
2195 } catch ( DBError $e ) {
2196 $this->completeCriticalSection( __METHOD__, $cs, $e );
2197 throw $e;
2198 }
2199 }
2200 } else {
2201 $savepointId = null;
2202 }
2203
2204 $sectionId = new AtomicSectionIdentifier;
2205 $this->transactionManager->addToAtomicLevels( $fname, $sectionId, $savepointId );
2206
2207 $this->completeCriticalSection( __METHOD__, $cs );
2208
2209 return $sectionId;
2210 }
2211
2213 final public function endAtomic( $fname = __METHOD__ ) {
2214 [ $savepointId, $sectionId ] = $this->transactionManager->onEndAtomic( $this, $fname );
2215
2216 $runPostCommitCallbacks = false;
2217
2218 $cs = $this->commenceCriticalSection( __METHOD__ );
2219
2220 // Remove the last section (no need to re-index the array)
2221 $finalLevelOfImplicitTrxPopped = $this->transactionManager->popAtomicLevel();
2222
2223 try {
2224 if ( $finalLevelOfImplicitTrxPopped ) {
2225 $this->commit( $fname, self::FLUSHING_INTERNAL );
2226 $runPostCommitCallbacks = true;
2227 } elseif ( $savepointId !== null && $savepointId !== self::NOT_APPLICABLE ) {
2228 $sql = $this->platform->releaseSavepointSqlText( $savepointId );
2229 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'RELEASE SAVEPOINT' );
2230 $this->query( $query, $fname );
2231 }
2232 } catch ( DBError $e ) {
2233 $this->completeCriticalSection( __METHOD__, $cs, $e );
2234 throw $e;
2235 }
2236
2237 $this->transactionManager->onEndAtomicInCriticalSection( $sectionId );
2238
2239 $this->completeCriticalSection( __METHOD__, $cs );
2240
2241 if ( $runPostCommitCallbacks ) {
2242 $this->runTransactionPostCommitCallbacks();
2243 }
2244 }
2245
2247 final public function cancelAtomic(
2248 $fname = __METHOD__,
2249 ?AtomicSectionIdentifier $sectionId = null
2250 ) {
2251 $this->transactionManager->onCancelAtomicBeforeCriticalSection( $this, $fname );
2252 $pos = $this->transactionManager->getPositionFromSectionId( $sectionId );
2253 if ( $pos < 0 ) {
2254 throw new DBUnexpectedError( $this, "Atomic section not found (for $fname)" );
2255 }
2256
2257 $cs = $this->commenceCriticalSection( __METHOD__ );
2258 $runPostRollbackCallbacks = false;
2259 [ $savedFname, $excisedSectionIds, $newTopSectionId, $savedSectionId, $savepointId ] =
2260 $this->transactionManager->cancelAtomic( $pos );
2261
2262 try {
2263 if ( $savedFname !== $fname ) {
2264 $e = new DBUnexpectedError(
2265 $this,
2266 "Invalid atomic section ended (got $fname but expected $savedFname)"
2267 );
2268 $this->completeCriticalSection( __METHOD__, $cs, $e );
2269 throw $e;
2270 }
2271
2272 // Remove the last section (no need to re-index the array)
2273 $this->transactionManager->popAtomicLevel();
2274 $excisedSectionIds[] = $savedSectionId;
2275 $newTopSectionId = $this->transactionManager->currentAtomicSectionId();
2276
2277 if ( $savepointId !== null ) {
2278 // Rollback the transaction changes proposed within this atomic section
2279 if ( $savepointId === self::NOT_APPLICABLE ) {
2280 // Atomic section started the transaction; rollback the whole transaction
2281 // and trigger cancellation callbacks for all active atomic sections
2282 $this->rollback( $fname, self::FLUSHING_INTERNAL );
2283 $runPostRollbackCallbacks = true;
2284 } else {
2285 // Atomic section nested within the transaction; rollback the transaction
2286 // to the state prior to this section and trigger its cancellation callbacks
2287 $sql = $this->platform->rollbackToSavepointSqlText( $savepointId );
2288 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'ROLLBACK TO SAVEPOINT' );
2289 $this->query( $query, $fname );
2290 $this->transactionManager->setTrxStatusToOk(); // no exception; recovered
2291 }
2292 } else {
2293 // Put the transaction into an error state if it's not already in one
2294 $trxError = new DBUnexpectedError(
2295 $this,
2296 "Uncancelable atomic section canceled (got $fname)"
2297 );
2298 $this->transactionManager->setTransactionError( $trxError );
2299 }
2300 } finally {
2301 // Fix up callbacks owned by the sections that were just cancelled.
2302 // All callbacks should have an owner that is present in trxAtomicLevels.
2303 $this->transactionManager->modifyCallbacksForCancel(
2304 $excisedSectionIds,
2305 $newTopSectionId
2306 );
2307 }
2308
2309 $this->lastEmulatedAffectedRows = 0; // for the sake of consistency
2310
2311 $this->completeCriticalSection( __METHOD__, $cs );
2312
2313 if ( $runPostRollbackCallbacks ) {
2314 $this->runTransactionPostRollbackCallbacks();
2315 }
2316 }
2317
2319 final public function doAtomicSection(
2320 $fname,
2321 callable $callback,
2322 $cancelable = self::ATOMIC_NOT_CANCELABLE
2323 ) {
2324 $sectionId = $this->startAtomic( $fname, $cancelable );
2325 try {
2326 $res = $callback( $this, $fname );
2327 } catch ( Throwable $e ) {
2328 // Avoid confusing error reporting during critical section errors
2329 if ( !$this->csmError ) {
2330 $this->cancelAtomic( $fname, $sectionId );
2331 }
2332
2333 throw $e;
2334 }
2335 $this->endAtomic( $fname );
2336
2337 return $res;
2338 }
2339
2341 final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
2342 static $modes = [ self::TRANSACTION_EXPLICIT, self::TRANSACTION_INTERNAL ];
2343 if ( !in_array( $mode, $modes, true ) ) {
2344 throw new DBUnexpectedError( $this, "$fname: invalid mode parameter '$mode'" );
2345 }
2346
2347 $this->transactionManager->onBegin( $this, $fname );
2348
2349 if ( $this->flagsHolder->hasImplicitTrxFlag() && $mode !== self::TRANSACTION_INTERNAL ) {
2350 $msg = "$fname: implicit transaction expected (DBO_TRX set)";
2351 throw new DBUnexpectedError( $this, $msg );
2352 }
2353
2354 $this->assertHasConnectionHandle();
2355
2356 $cs = $this->commenceCriticalSection( __METHOD__ );
2357 $timeStart = microtime( true );
2358 try {
2359 $this->doBegin( $fname );
2360 } catch ( DBError $e ) {
2361 $this->completeCriticalSection( __METHOD__, $cs );
2362 throw $e;
2363 }
2364 $timeEnd = microtime( true );
2365 // Treat "BEGIN" as a trivial query to gauge the RTT delay
2366 $rtt = max( $timeEnd - $timeStart, 0.0 );
2367 $this->transactionManager->onBeginInCriticalSection( $mode, $fname, $rtt );
2368 $this->replicationReporter->resetReplicationLagStatus( $this );
2369 $this->completeCriticalSection( __METHOD__, $cs );
2370 }
2371
2379 protected function doBegin( $fname ) {
2380 $query = new Query( 'BEGIN', self::QUERY_CHANGE_TRX, 'BEGIN' );
2381 $this->query( $query, $fname );
2382 }
2383
2385 final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2386 static $modes = [ self::FLUSHING_ONE, self::FLUSHING_ALL_PEERS, self::FLUSHING_INTERNAL ];
2387 if ( !in_array( $flush, $modes, true ) ) {
2388 throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'" );
2389 }
2390
2391 if ( !$this->transactionManager->onCommit( $this, $fname, $flush ) ) {
2392 return;
2393 }
2394
2395 $this->assertHasConnectionHandle();
2396
2397 $this->runOnTransactionPreCommitCallbacks();
2398
2399 $cs = $this->commenceCriticalSection( __METHOD__ );
2400 try {
2401 if ( $this->trxLevel() ) {
2402 $query = new Query( 'COMMIT', self::QUERY_CHANGE_TRX, 'COMMIT' );
2403 $this->query( $query, $fname );
2404 }
2405 } catch ( DBError $e ) {
2406 $this->completeCriticalSection( __METHOD__, $cs );
2407 throw $e;
2408 }
2409 $lastWriteTime = $this->transactionManager->onCommitInCriticalSection( $this );
2410 if ( $lastWriteTime ) {
2411 $this->lastWriteTime = $lastWriteTime;
2412 }
2413 // With FLUSHING_ALL_PEERS, callbacks will run when requested by a dedicated phase
2414 // within LoadBalancer. With FLUSHING_INTERNAL, callbacks will run when requested by
2415 // the Database caller during a safe point. This avoids isolation and recursion issues.
2416 if ( $flush === self::FLUSHING_ONE ) {
2417 $this->runTransactionPostCommitCallbacks();
2418 }
2419 $this->completeCriticalSection( __METHOD__, $cs );
2420 }
2421
2423 final public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2424 if (
2425 $flush !== self::FLUSHING_INTERNAL &&
2426 $flush !== self::FLUSHING_ALL_PEERS &&
2427 $this->flagsHolder->hasImplicitTrxFlag()
2428 ) {
2429 throw new DBUnexpectedError(
2430 $this,
2431 "$fname: Expected mass rollback of all peer transactions (DBO_TRX set)"
2432 );
2433 }
2434
2435 if ( !$this->trxLevel() ) {
2436 $this->transactionManager->setTrxStatusToNone();
2437 $this->transactionManager->clearPreEndCallbacks();
2438 if ( $this->transactionManager->trxLevel() === TransactionManager::STATUS_TRX_ERROR ) {
2439 $this->logger->info(
2440 "$fname: acknowledged server-side transaction loss on {db_server}",
2441 $this->getLogContext()
2442 );
2443 }
2444
2445 return;
2446 }
2447
2448 $this->assertHasConnectionHandle();
2449
2450 if ( $this->csmError ) {
2451 // Since the session state is corrupt, we cannot just rollback the transaction
2452 // while preserving the non-transaction session state. The handle will remain
2453 // marked as corrupt until flushSession() is called to reset the connection
2454 // and deal with any remaining callbacks.
2455 $this->logger->info(
2456 "$fname: acknowledged client-side transaction loss on {db_server}",
2457 $this->getLogContext()
2458 );
2459
2460 return;
2461 }
2462
2463 $cs = $this->commenceCriticalSection( __METHOD__ );
2464 if ( $this->trxLevel() ) {
2465 // Disconnects cause rollback anyway, so ignore those errors
2466 $query = new Query(
2467 $this->platform->rollbackSqlText(),
2468 self::QUERY_SILENCE_ERRORS | self::QUERY_CHANGE_TRX,
2469 'ROLLBACK'
2470 );
2471 $this->query( $query, $fname );
2472 }
2473 $this->transactionManager->onRollbackInCriticalSection( $this );
2474 // With FLUSHING_ALL_PEERS, callbacks will run when requested by a dedicated phase
2475 // within LoadBalancer. With FLUSHING_INTERNAL, callbacks will run when requested by
2476 // the Database caller during a safe point. This avoids isolation and recursion issues.
2477 if ( $flush === self::FLUSHING_ONE ) {
2478 $this->runTransactionPostRollbackCallbacks();
2479 }
2480 $this->completeCriticalSection( __METHOD__, $cs );
2481 }
2482
2487 public function setTransactionManager( TransactionManager $transactionManager ) {
2488 $this->transactionManager = $transactionManager;
2489 }
2490
2492 public function flushSession( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2493 if (
2494 $flush !== self::FLUSHING_INTERNAL &&
2495 $flush !== self::FLUSHING_ALL_PEERS &&
2496 $this->flagsHolder->hasImplicitTrxFlag()
2497 ) {
2498 throw new DBUnexpectedError(
2499 $this,
2500 "$fname: Expected mass flush of all peer connections (DBO_TRX set)"
2501 );
2502 }
2503
2504 if ( $this->csmError ) {
2505 // If a critical section error occurred, such as Excimer timeout exceptions raised
2506 // before a query response was marshalled, destroy the connection handle and reset
2507 // the session state tracking variables. The value of trxLevel() is irrelevant here,
2508 // and, in fact, might be 1 due to rollback() deferring critical section recovery.
2509 $this->logger->info(
2510 "$fname: acknowledged client-side session loss on {db_server}",
2511 $this->getLogContext()
2512 );
2513 $this->csmError = null;
2514 $this->csmFname = null;
2515 $this->replaceLostConnection( 2048, __METHOD__ );
2516
2517 return;
2518 }
2519
2520 if ( $this->trxLevel() ) {
2521 // Any existing transaction should have been rolled back already
2522 throw new DBUnexpectedError(
2523 $this,
2524 "$fname: transaction still in progress (not yet rolled back)"
2525 );
2526 }
2527
2528 if ( $this->transactionManager->sessionStatus() === TransactionManager::STATUS_SESS_ERROR ) {
2529 // If the session state was already lost due to either an unacknowledged session
2530 // state loss error (e.g. dropped connection) or an explicit connection close call,
2531 // then there is nothing to do here. Note that in such cases, even temporary tables
2532 // and server-side config variables are lost (invocation of this method is assumed
2533 // to imply that such losses are tolerable).
2534 $this->logger->info(
2535 "$fname: acknowledged server-side session loss on {db_server}",
2536 $this->getLogContext()
2537 );
2538 } elseif ( $this->isOpen() ) {
2539 // Connection handle exists; server-side session state must be flushed
2540 $this->doFlushSession( $fname );
2541 $this->sessionNamedLocks = [];
2542 }
2543
2544 $this->transactionManager->clearSessionError();
2545 }
2546
2555 protected function doFlushSession( $fname ) {
2556 // no-op
2557 }
2558
2560 public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2561 $this->transactionManager->onFlushSnapshot(
2562 $this,
2563 $fname,
2564 $flush,
2565 $this->getTransactionRoundFname()
2566 );
2567 if (
2568 $this->transactionManager->sessionStatus() === TransactionManager::STATUS_SESS_ERROR ||
2569 $this->transactionManager->trxStatus() === TransactionManager::STATUS_TRX_ERROR
2570 ) {
2571 $this->rollback( $fname, self::FLUSHING_INTERNAL );
2572 } else {
2573 $this->commit( $fname, self::FLUSHING_INTERNAL );
2574 }
2575 }
2576
2579 $oldName,
2580 $newName,
2581 $temporary = false,
2582 $fname = __METHOD__
2583 ) {
2584 throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2585 }
2586
2588 public function listTables( $prefix = null, $fname = __METHOD__ ) {
2589 throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2590 }
2591
2593 public function affectedRows() {
2594 $this->lastEmulatedAffectedRows ??= $this->lastQueryAffectedRows;
2595
2596 return $this->lastEmulatedAffectedRows;
2597 }
2598
2600 public function insertId() {
2601 if ( $this->lastEmulatedInsertId === null ) {
2602 // Guard against misuse of this method by checking affectedRows(). Note that calls
2603 // to insert() with "IGNORE" and calls to insertSelect() might not add any rows.
2604 if ( $this->affectedRows() ) {
2605 $this->lastEmulatedInsertId = $this->lastInsertId();
2606 } else {
2607 $this->lastEmulatedInsertId = 0;
2608 }
2609 }
2610
2611 return $this->lastEmulatedInsertId;
2612 }
2613
2627 abstract protected function lastInsertId();
2628
2630 public function ping() {
2631 if ( $this->isOpen() ) {
2632 // If the connection was recently used, assume that it is still good
2633 if ( ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
2634 return true;
2635 }
2636 // Send a trivial query to test the connection, triggering an automatic
2637 // reconnection attempt if the connection was lost
2638 $query = new Query(
2639 self::PING_QUERY,
2640 self::QUERY_IGNORE_DBO_TRX | self::QUERY_SILENCE_ERRORS | self::QUERY_CHANGE_NONE,
2641 'SELECT'
2642 );
2643 $res = $this->query( $query, __METHOD__ );
2644 $ok = ( $res !== false );
2645 } else {
2646 // Try to re-establish a connection
2647 $ok = $this->replaceLostConnection( null, __METHOD__ );
2648 }
2649
2650 return $ok;
2651 }
2652
2660 protected function replaceLostConnection( $lastErrno, $fname ) {
2661 if ( $this->conn ) {
2662 $this->closeConnection();
2663 $this->conn = null;
2664 $this->handleSessionLossPreconnect();
2665 }
2666
2667 try {
2668 $this->open(
2669 $this->connectionParams[self::CONN_SERVER],
2670 $this->connectionParams[self::CONN_USER],
2671 $this->connectionParams[self::CONN_PASSWORD],
2672 $this->currentDomain->getDatabase(),
2673 $this->currentDomain->getSchema(),
2674 $this->tablePrefix()
2675 );
2676 $this->lastPing = microtime( true );
2677 $ok = true;
2678
2679 $this->logger->warning(
2680 $fname . ': lost connection to {db_server} with error {errno}; reconnected',
2681 $this->getLogContext( [
2682 'exception' => new RuntimeException(),
2683 'db_log_category' => 'connection',
2684 'errno' => $lastErrno
2685 ] )
2686 );
2687 } catch ( DBConnectionError $e ) {
2688 $ok = false;
2689
2690 $this->logger->error(
2691 $fname . ': lost connection to {db_server} with error {errno}; reconnection failed: {connect_msg}',
2692 $this->getLogContext( [
2693 'exception' => new RuntimeException(),
2694 'db_log_category' => 'connection',
2695 'errno' => $lastErrno,
2696 'connect_msg' => $e->getMessage()
2697 ] )
2698 );
2699 }
2700
2701 // Handle callbacks in trxEndCallbacks, e.g. onTransactionResolution().
2702 // If callback suppression is set then the array will remain unhandled.
2703 $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
2704 // Handle callbacks in trxRecurringCallbacks, e.g. setTransactionListener().
2705 // If callback suppression is set then the array will remain unhandled.
2706 $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
2707
2708 return $ok;
2709 }
2710
2734 public static function getCacheSetOptions( ?IReadableDatabase ...$dbs ) {
2735 wfDeprecated( __METHOD__, '1.47' );
2736 $res = [ 'pending' => false ];
2737
2738 foreach ( $dbs as $db ) {
2739 if ( $db instanceof IDatabaseForOwner ) {
2740 $res['pending'] = $res['pending'] ?: $db->writesPending();
2741 }
2742 }
2743
2744 return $res;
2745 }
2746
2748 public function encodeBlob( $b ) {
2749 return $b;
2750 }
2751
2753 public function decodeBlob( $b ) {
2754 if ( $b instanceof Blob ) {
2755 $b = $b->fetch();
2756 }
2757 return $b;
2758 }
2759
2760 public function setSessionOptions( array $options ) {
2761 }
2762
2764 public function sourceFile(
2765 $filename,
2766 ?callable $lineCallback = null,
2767 ?callable $resultCallback = null,
2768 $fname = false,
2769 ?callable $inputCallback = null
2770 ) {
2771 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2772 $fp = @fopen( $filename, 'r' );
2773
2774 if ( $fp === false ) {
2775 throw new RuntimeException( "Could not open \"{$filename}\"" );
2776 }
2777
2778 if ( !$fname ) {
2779 $fname = __METHOD__ . "( $filename )";
2780 }
2781
2782 try {
2783 return $this->sourceStream(
2784 $fp,
2785 $lineCallback,
2786 $resultCallback,
2787 $fname,
2788 $inputCallback
2789 );
2790 } finally {
2791 fclose( $fp );
2792 }
2793 }
2794
2796 public function sourceStream(
2797 $fp,
2798 ?callable $lineCallback = null,
2799 ?callable $resultCallback = null,
2800 $fname = __METHOD__,
2801 ?callable $inputCallback = null
2802 ) {
2803 $delimiterReset = new ScopedCallback(
2804 function ( $delimiter ) {
2805 $this->delimiter = $delimiter;
2806 },
2807 [ $this->delimiter ]
2808 );
2809 $cmd = '';
2810
2811 while ( !feof( $fp ) ) {
2812 if ( $lineCallback ) {
2813 $lineCallback();
2814 }
2815
2816 $line = trim( fgets( $fp ) );
2817
2818 if ( $line == '' ) {
2819 continue;
2820 }
2821
2822 if ( $line[0] == '-' && $line[1] == '-' ) {
2823 continue;
2824 }
2825
2826 if ( $cmd != '' ) {
2827 $cmd .= ' ';
2828 }
2829
2830 $done = $this->streamStatementEnd( $cmd, $line );
2831
2832 $cmd .= "$line\n";
2833
2834 if ( $done || feof( $fp ) ) {
2835 $cmd = $this->platform->replaceVars( $cmd );
2836
2837 if ( $inputCallback ) {
2838 $callbackResult = $inputCallback( $cmd );
2839
2840 if ( is_string( $callbackResult ) || !$callbackResult ) {
2841 $cmd = $callbackResult;
2842 }
2843 }
2844
2845 if ( $cmd ) {
2846 $res = $this->query( $cmd, $fname );
2847
2848 if ( $resultCallback ) {
2849 $resultCallback( $res, $this );
2850 }
2851
2852 if ( $res === false ) {
2853 $err = $this->lastError();
2854
2855 return "Query \"{$cmd}\" failed with error code \"$err\".\n";
2856 }
2857 }
2858 $cmd = '';
2859 }
2860 }
2861
2862 ScopedCallback::consume( $delimiterReset );
2863 return true;
2864 }
2865
2873 public function streamStatementEnd( &$sql, &$newLine ) {
2874 if ( $this->delimiter ) {
2875 $prev = $newLine;
2876 $newLine = preg_replace(
2877 '/' . preg_quote( $this->delimiter, '/' ) . '$/',
2878 '',
2879 $newLine
2880 );
2881 if ( $newLine != $prev ) {
2882 return true;
2883 }
2884 }
2885
2886 return false;
2887 }
2888
2892 public function lock( $lockName, $method, $timeout = 5, $flags = 0 ) {
2893 $logContext = [
2894 'lockname' => $lockName,
2895 'db_log_category' => 'locking'
2896 ];
2897 $lockTsUnix = $this->doLock( $lockName, $method, $timeout );
2898 if ( $lockTsUnix !== null ) {
2899 $locked = true;
2900 $this->sessionNamedLocks[$lockName] = [
2901 'ts' => $lockTsUnix,
2902 'trxId' => $this->transactionManager->getTrxId()
2903 ];
2904 $this->logger->debug(
2905 __METHOD__ . ": acquired lock '{lockname}'",
2906 $logContext
2907 );
2908 } else {
2909 $locked = false;
2910 $this->logger->info(
2911 __METHOD__ . ": failed to acquire lock '{lockname}'",
2912 $logContext
2913 );
2914 }
2915
2916 return $this->flagsHolder::contains( $flags, self::LOCK_TIMESTAMP ) ? $lockTsUnix : $locked;
2917 }
2918
2928 protected function doLock( string $lockName, string $method, int $timeout ) {
2929 return microtime( true ); // not implemented
2930 }
2931
2935 public function unlock( $lockName, $method ) {
2936 $logContext = [
2937 'lockname' => $lockName,
2938 'db_log_category' => 'locking'
2939 ];
2940 if ( !isset( $this->sessionNamedLocks[$lockName] ) ) {
2941 $released = false;
2942 $this->logger->warning(
2943 __METHOD__ . ": trying to release unheld lock '{lockname}'\n",
2944 $logContext
2945 );
2946 } else {
2947 $released = $this->doUnlock( $lockName, $method );
2948 if ( $released ) {
2949 unset( $this->sessionNamedLocks[$lockName] );
2950 $this->logger->debug(
2951 __METHOD__ . ": released lock '{lockname}'",
2952 $logContext
2953 );
2954 } else {
2955 $this->logger->warning(
2956 __METHOD__ . ": failed to release lock '{lockname}'\n",
2957 $logContext
2958 );
2959 }
2960 }
2961
2962 return $released;
2963 }
2964
2973 protected function doUnlock( string $lockName, string $method ) {
2974 return true; // not implemented
2975 }
2976
2978 #[\NoDiscard]
2979 public function getScopedLockAndFlush( $lockKey, $fname, $timeout ): ?ScopedCallback {
2980 $this->transactionManager->onGetScopedLockAndFlush( $this, $fname );
2981
2982 if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
2983 return null;
2984 }
2985
2986 $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
2987 // Note that the callback can be reached due to an exception making the calling
2988 // function end early. If the transaction/session is in an error state, avoid log
2989 // spam and confusing replacement of an original DBError with one about unlock().
2990 // Unlock query will fail anyway; avoid possibly triggering errors in rollback()
2991 if (
2992 $this->transactionManager->sessionStatus() === TransactionManager::STATUS_SESS_ERROR ||
2993 $this->transactionManager->trxStatus() === TransactionManager::STATUS_TRX_ERROR
2994 ) {
2995 return;
2996 }
2997 if ( $this->trxLevel() ) {
2998 $this->onTransactionResolution(
2999 function () use ( $lockKey, $fname ) {
3000 $this->unlock( $lockKey, $fname );
3001 },
3002 $fname
3003 );
3004 } else {
3005 $this->unlock( $lockKey, $fname );
3006 }
3007 } );
3008
3009 $this->commit( $fname, self::FLUSHING_INTERNAL );
3010
3011 return $unlocker;
3012 }
3013
3015 public function dropTable( $table, $fname = __METHOD__ ) {
3016 if ( !$this->tableExists( $table, $fname ) ) {
3017 return false;
3018 }
3019
3020 $query = new Query(
3021 $this->platform->dropTableSqlText( $table ),
3022 self::QUERY_CHANGE_SCHEMA,
3023 'DROP',
3024 $table
3025 );
3026 $this->query( $query, $fname );
3027
3028 return true;
3029 }
3030
3032 public function truncateTable( $table, $fname = __METHOD__ ) {
3033 $sql = "TRUNCATE TABLE " . $this->tableName( $table );
3034 $query = new Query( $sql, self::QUERY_CHANGE_SCHEMA, 'TRUNCATE', $table );
3035 $this->query( $query, $fname );
3036 }
3037
3039 public function isReadOnly() {
3040 return ( $this->getReadOnlyReason() !== null );
3041 }
3042
3046 protected function getReadOnlyReason() {
3047 $reason = $this->replicationReporter->getTopologyBasedReadOnlyReason();
3048 if ( $reason ) {
3049 return $reason;
3050 }
3051
3052 $reason = $this->getLBInfo( self::LB_READ_ONLY_REASON );
3053 if ( is_string( $reason ) ) {
3054 return [ $reason, 'lb' ];
3055 }
3056
3057 return null;
3058 }
3059
3071 protected function getBindingHandle() {
3072 if ( !$this->conn ) {
3073 throw new DBUnexpectedError(
3074 $this,
3075 'DB connection was already closed or the connection dropped'
3076 );
3077 }
3078
3079 return $this->conn;
3080 }
3081
3118 protected function commenceCriticalSection( string $fname ) {
3119 if ( $this->csmError ) {
3120 throw new DBUnexpectedError(
3121 $this,
3122 "Cannot execute $fname critical section while session state is out of sync.\n\n" .
3123 $this->csmError->getMessage() . "\n" .
3124 $this->csmError->getTraceAsString()
3125 );
3126 }
3127
3128 if ( $this->csmId ) {
3129 $csm = null; // fold into the outer critical section
3130 } elseif ( $this->csProvider ) {
3131 $csm = $this->csProvider->scopedEnter(
3132 $fname,
3133 null, // emergency limit (default)
3134 null, // emergency callback (default)
3135 function () use ( $fname ) {
3136 // Mark a critical section as having been aborted by an error
3137 $e = new RuntimeException( "A critical section from {$fname} has failed" );
3138 $this->csmError = $e;
3139 $this->csmId = null;
3140 }
3141 );
3142 $this->csmId = $csm->getId();
3143 $this->csmFname = $fname;
3144 } else {
3145 $csm = null; // not supported
3146 }
3147
3148 return $csm;
3149 }
3150
3161 protected function completeCriticalSection(
3162 string $fname,
3163 ?CriticalSectionScope $csm,
3164 ?Throwable $trxError = null
3165 ) {
3166 if ( $csm !== null ) {
3167 if ( $this->csmId === null ) {
3168 throw new LogicException( "$fname critical section is not active" );
3169 } elseif ( $csm->getId() !== $this->csmId ) {
3170 throw new LogicException(
3171 "$fname critical section is not the active ({$this->csmFname}) one"
3172 );
3173 }
3174
3175 $csm->exit();
3176 $this->csmId = null;
3177 }
3178
3179 if ( $trxError ) {
3180 $this->transactionManager->setTransactionError( $trxError );
3181 }
3182 }
3183
3184 public function __toString() {
3185 $id = spl_object_id( $this );
3186
3187 $description = $this->getType() . ' object #' . $id;
3188 // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.is_resource
3189 if ( is_resource( $this->conn ) ) {
3190 $description .= ' (' . (string)$this->conn . ')'; // "resource id #<ID>"
3191 } elseif ( is_object( $this->conn ) ) {
3192 $handleId = spl_object_id( $this->conn );
3193 $description .= " (handle id #$handleId)";
3194 }
3195
3196 return $description;
3197 }
3198
3203 public function __clone() {
3204 $this->logger->warning(
3205 "Cloning " . static::class . " is not recommended; forking connection",
3206 [
3207 'exception' => new RuntimeException(),
3208 'db_log_category' => 'connection'
3209 ]
3210 );
3211
3212 if ( $this->isOpen() ) {
3213 // Open a new connection resource without messing with the old one
3214 $this->conn = null;
3215 $this->transactionManager->clearEndCallbacks();
3216 $this->handleSessionLossPreconnect(); // no trx or locks anymore
3217 $this->open(
3218 $this->connectionParams[self::CONN_SERVER],
3219 $this->connectionParams[self::CONN_USER],
3220 $this->connectionParams[self::CONN_PASSWORD],
3221 $this->currentDomain->getDatabase(),
3222 $this->currentDomain->getSchema(),
3223 $this->tablePrefix()
3224 );
3225 $this->lastPing = microtime( true );
3226 }
3227 }
3228
3235 public function __sleep(): never {
3236 throw new RuntimeException( 'Database serialization may cause problems, since ' .
3237 'the connection is not restored on wakeup' );
3238 }
3239
3243 public function __destruct() {
3244 if ( $this->transactionManager ) {
3245 // Tests mock this class and disable constructor.
3246 $this->transactionManager->onDestruct();
3247 }
3248
3249 $danglingWriters = $this->pendingWriteAndCallbackCallers();
3250 if ( $danglingWriters ) {
3251 $fnames = implode( ', ', $danglingWriters );
3252 trigger_error( "DB transaction writes or callbacks still pending ($fnames)" );
3253 }
3254
3255 if ( $this->conn ) {
3256 // Avoid connection leaks. Normally, resources close at script completion.
3257 // The connection might already be closed in PHP by now, so suppress warnings.
3258 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
3259 @$this->closeConnection();
3260 $this->conn = null;
3261 }
3262 }
3263
3264 /* Start of methods delegated to DatabaseFlags. Avoid using them outside of rdbms library */
3265
3267 public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
3268 $this->flagsHolder->setFlag( $flag, $remember );
3269 }
3270
3272 public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
3273 $this->flagsHolder->clearFlag( $flag, $remember );
3274 }
3275
3277 public function restoreFlags( $state = self::RESTORE_PRIOR ) {
3278 $this->flagsHolder->restoreFlags( $state );
3279 }
3280
3282 public function getFlag( $flag ) {
3283 return $this->flagsHolder->getFlag( $flag );
3284 }
3285
3286 /* End of methods delegated to DatabaseFlags. */
3287
3288 /* Start of methods delegated to TransactionManager. Avoid using them outside of rdbms library */
3289
3291 final public function trxLevel() {
3292 // FIXME: A lot of tests disable constructor leading to trx manager being
3293 // null and breaking, this is unacceptable but hopefully this should
3294 // happen less by moving these functions to the transaction manager class.
3295 if ( !$this->transactionManager ) {
3296 $this->transactionManager = new TransactionManager( new NullLogger() );
3297 }
3298 return $this->transactionManager->trxLevel();
3299 }
3300
3302 public function trxTimestamp() {
3303 return $this->transactionManager->trxTimestamp();
3304 }
3305
3307 public function trxStatus() {
3308 return $this->transactionManager->trxStatus();
3309 }
3310
3312 public function writesPending() {
3313 return $this->transactionManager->writesPending();
3314 }
3315
3317 public function writesOrCallbacksPending() {
3318 return $this->transactionManager->writesOrCallbacksPending();
3319 }
3320
3322 public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
3323 return $this->transactionManager->pendingWriteQueryDuration( $type );
3324 }
3325
3327 public function pendingWriteCallers() {
3328 if ( !$this->transactionManager ) {
3329 return [];
3330 }
3331 return $this->transactionManager->pendingWriteCallers();
3332 }
3333
3336 if ( !$this->transactionManager ) {
3337 return [];
3338 }
3339 return $this->transactionManager->pendingWriteAndCallbackCallers();
3340 }
3341
3347 return $this->transactionManager->runOnTransactionPreCommitCallbacks();
3348 }
3349
3351 public function explicitTrxActive() {
3352 return $this->transactionManager->explicitTrxActive();
3353 }
3354
3355 /* End of methods delegated to TransactionManager. */
3356
3357 /* Start of methods delegated to SQLPlatform. Avoid using them outside of rdbms library */
3358
3360 public function implicitOrderby() {
3361 return $this->platform->implicitOrderby();
3362 }
3363
3365 public function selectSQLText(
3366 $tables, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
3367 ) {
3368 return $this->platform->selectSQLText( $tables, $vars, $conds, $fname, $options, $join_conds );
3369 }
3370
3372 public function buildComparison( string $op, array $conds ): string {
3373 return $this->platform->buildComparison( $op, $conds );
3374 }
3375
3377 public function makeList( array $a, $mode = self::LIST_COMMA ) {
3378 return $this->platform->makeList( $a, $mode );
3379 }
3380
3382 public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
3383 return $this->platform->makeWhereFrom2d( $data, $baseKey, $subKey );
3384 }
3385
3387 public function factorConds( $condsArray ) {
3388 return $this->platform->factorConds( $condsArray );
3389 }
3390
3392 public function bitNot( $field ) {
3393 return $this->platform->bitNot( $field );
3394 }
3395
3397 public function bitAnd( $fieldLeft, $fieldRight ) {
3398 return $this->platform->bitAnd( $fieldLeft, $fieldRight );
3399 }
3400
3402 public function bitOr( $fieldLeft, $fieldRight ) {
3403 return $this->platform->bitOr( $fieldLeft, $fieldRight );
3404 }
3405
3407 public function buildConcat( $stringList ) {
3408 return $this->platform->buildConcat( $stringList );
3409 }
3410
3412 public function buildGroupConcat( $field, $delim ): string {
3413 return $this->platform->buildGroupConcat( $field, $delim );
3414 }
3415
3417 public function buildGreatest( $fields, $values ) {
3418 return $this->platform->buildGreatest( $fields, $values );
3419 }
3420
3422 public function buildLeast( $fields, $values ) {
3423 return $this->platform->buildLeast( $fields, $values );
3424 }
3425
3427 public function buildSubstring( $input, $startPosition, $length = null ) {
3428 return $this->platform->buildSubstring( $input, $startPosition, $length );
3429 }
3430
3432 public function buildStringCast( $field ) {
3433 return $this->platform->buildStringCast( $field );
3434 }
3435
3437 public function buildIntegerCast( $field ) {
3438 return $this->platform->buildIntegerCast( $field );
3439 }
3440
3442 public function tableName( string $name, $format = 'quoted' ) {
3443 return $this->platform->tableName( $name, $format );
3444 }
3445
3447 public function tableNamesN( ...$tables ) {
3448 return $this->platform->tableNamesN( ...$tables );
3449 }
3450
3452 public function addIdentifierQuotes( $s ) {
3453 return $this->platform->addIdentifierQuotes( $s );
3454 }
3455
3460 public function isQuotedIdentifier( $name ) {
3461 return $this->platform->isQuotedIdentifier( $name );
3462 }
3463
3465 public function buildLike( $param, ...$params ) {
3466 return $this->platform->buildLike( $param, ...$params );
3467 }
3468
3470 public function anyChar() {
3471 return $this->platform->anyChar();
3472 }
3473
3475 public function anyString() {
3476 return $this->platform->anyString();
3477 }
3478
3480 public function limitResult( $sql, $limit, $offset = false ) {
3481 return $this->platform->limitResult( $sql, $limit, $offset );
3482 }
3483
3485 public function unionSupportsOrderAndLimit() {
3486 return $this->platform->unionSupportsOrderAndLimit();
3487 }
3488
3490 public function unionQueries( $sqls, $all, $options = [] ) {
3491 return $this->platform->unionQueries( $sqls, $all, $options );
3492 }
3493
3495 public function conditional( $cond, $caseTrueExpression, $caseFalseExpression ) {
3496 return $this->platform->conditional( $cond, $caseTrueExpression, $caseFalseExpression );
3497 }
3498
3500 public function strreplace( $orig, $old, $new ) {
3501 return $this->platform->strreplace( $orig, $old, $new );
3502 }
3503
3505 public function timestamp( $ts = 0 ) {
3506 return $this->platform->timestamp( $ts );
3507 }
3508
3510 public function timestampOrNull( $ts = null ) {
3511 return $this->platform->timestampOrNull( $ts );
3512 }
3513
3515 public function getInfinity() {
3516 return $this->platform->getInfinity();
3517 }
3518
3520 public function encodeExpiry( $expiry ) {
3521 return $this->platform->encodeExpiry( $expiry );
3522 }
3523
3525 public function decodeExpiry( $expiry, $format = TS::MW ) {
3526 return $this->platform->decodeExpiry( $expiry, $format );
3527 }
3528
3530 public function setTableAliases( array $aliases ) {
3531 $this->platform->setTableAliases( $aliases );
3532 }
3533
3535 public function getTableAliases() {
3536 return $this->platform->getTableAliases();
3537 }
3538
3540 public function buildGroupConcatField(
3541 $delim, $tables, $field, $conds = '', $join_conds = []
3542 ) {
3543 return $this->platform->buildGroupConcatField( $delim, $tables, $field, $conds, $join_conds );
3544 }
3545
3547 public function buildSelectSubquery(
3548 $tables, $vars, $conds = '', $fname = __METHOD__,
3549 $options = [], $join_conds = []
3550 ) {
3551 return $this->platform->buildSelectSubquery( $tables, $vars, $conds, $fname, $options, $join_conds );
3552 }
3553
3555 public function buildExcludedValue( $column ) {
3556 return $this->platform->buildExcludedValue( $column );
3557 }
3558
3560 public function setSchemaVars( $vars ) {
3561 $this->platform->setSchemaVars( $vars );
3562 }
3563
3564 /* End of methods delegated to SQLPlatform. */
3565
3566 /* Start of methods delegated to ReplicationReporter. */
3567
3569 public function primaryPosWait( DBPrimaryPos $pos, $timeout ) {
3570 return $this->replicationReporter->primaryPosWait( $this, $pos, $timeout );
3571 }
3572
3574 public function getPrimaryPos() {
3575 return $this->replicationReporter->getPrimaryPos( $this );
3576 }
3577
3579 public function getLag() {
3580 return $this->replicationReporter->getLag( $this );
3581 }
3582
3584 public function getSessionLagStatus() {
3585 return $this->replicationReporter->getSessionLagStatus( $this );
3586 }
3587
3588 /* End of methods delegated to ReplicationReporter. */
3589}
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
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:299
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:357
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:430
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:304
newSelectQueryBuilder()
Get a SelectQueryBuilder bound to this connection.
setLogger(LoggerInterface $logger)
Set the PSR-3 logger interface to use.
Definition Database.php:294
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:320
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:404
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:372
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:602
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:254
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:535
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...
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:419
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:461
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:344
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:471
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.
executeQuery( $sql, $fname, $flags)
Execute a query without enforcing public (non-Database) caller restrictions.
Definition Database.php:666
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:480
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...
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_SERVER='server';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 const CONN_HOST=self::CONN_SERVER;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:186
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 ...
isOpen()
bool Whether a connection to the database open
Definition Database.php:399
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:213
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:623
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, 'EnableChunkedUploads'=> 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'=> '', 'SharedUploadDBschema'=> null, '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'=>[], 'DefaultLockManager'=> null, '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'=> true, '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 -l $lang -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'MaxAnimatedWebPArea'=> 12500000, 'WebPThumbnailType'=>['webp', 'image/webp',], '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, 220, 250, 300, 400,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'TrackMediaRequestProvenance'=> 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, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EmailConfirmationBanner'=> false, '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'=>[], 'RemoteVirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup', 'CodeHighlighter',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'CodeHighlighter',],], '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, 'SplitParsoidParserCache'=> true, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'JwtSessionCookieIssuer'=> null, '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',],],], 'EnableSectionShare'=> false, '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, ], 'NamespacesWithoutAutoSummaries' => [ ], '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, '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, ], 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\PreviouslyRenamedAccountPreAuthenticationProvider', 'services' => [ 'ConnectionProvider', 'UserFactory', ], '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, ], '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, 'autocreateaccount' => 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, 'createwithcontentmodel' => true, 'logout' => 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, 'createpreviouslyrenamedaccount' => 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' => [ ], 'RestrictUserPageEditing' => false, '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, 'createwithcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createwithcontentmodel' => 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, 'createwithcontentmodel' => 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, 'createwithcontentmodel' => 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, 'createwithcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], 'managesessions' => [ 'logout' => 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', 'editmywatchlist' => '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', 'managesessions' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editmywatchlist' => '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', 'managesessions' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'ReauthenticateForActions' => [ 'edituserjs' => 'edituserjscss', 'editusercss' => 'edituserjscss', 'editsitejs' => 'editsitejscss', 'editsitecss' => 'editsitejscss', ], 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPUseReportURIDirective' => 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, 'DebugToolbar' => false, 'ApiClientErrorSampleRate' => 1.0, '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: 'https: 'git@github\\.com:(.*?)(\\.git)?' => 'https: ], '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' => [ ], '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, 'mw-edited-other-users-js' => true, 'mw-edited-other-users-css' => true, ], 'RestrictedTagViewRights' => [ ], '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, 'EnableWatchstarPopover' => 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\\RecentChanges\\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', 'Promise-Non-Write-API-Action', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestLocalModuleTestBaseUrl' => null, 'RestModuleOverrides' => [ ], 'RestExternalModules' => [ ], 'RestTermsOfServiceUrl' => null, '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, 'HTTPUserAgentContact' => false, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => 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, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, 'ReturnExperimentalPFragmentTypes' => [ ], 'UseParsoidLinksUpdate' => null, 'UseParsoidMessages' => true, ], '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', 'SharedUploadDBschema' => [ 'string', 'null', ], 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'DefaultLockManager' => [ 'string', 'null', ], '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', ], 'WebPThumbnailType' => 'array', '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', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EmailConfirmationBanner' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'RemoteVirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => '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', 'SplitParsoidParserCache' => 'boolean', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => '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', 'NamespacesWithoutAutoSummaries' => 'array', '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', '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', 'RestrictUserPageEditing' => 'boolean', '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', ], 'BotPasswordsLimit' => 'integer', 'ReauthenticateForActions' => 'object', 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPUseReportURIDirective' => [ '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', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RestrictedTagViewRights' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchstarPopover' => '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', 'RestLocalModuleTestBaseUrl' => [ 'string', 'null', ], 'RestModuleOverrides' => 'object', 'RestExternalModules' => 'object', 'RestTermsOfServiceUrl' => [ 'string', 'null', ], 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'HTTPUserAgentContact' => [ 'string', 'boolean', ], 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', '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', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', 'ReturnExperimentalPFragmentTypes' => 'array', 'UseParsoidLinksUpdate' => [ 'boolean', 'null', ], 'UseParsoidMessages' => [ 'boolean', 'null', ], ], 'mergeStrategy' => [ 'WebPThumbnailType' => 'replace', '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', 'RestModuleOverrides' => 'array_replace_recursive', 'RestExternalModules' => 'array_replace_recursive', ], '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', ], ], ], ], ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], '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', ], 'skipRedirects' => [ 'type' => 'bool', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'UseCopyrightUpload' => [ 'deprecated' => 'since 1.47 This feature is being removed.', ], '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', ], ], 'RestModuleOverrides' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'availability' => [ 'type' => 'string', ], ], 'required' => [ 'availability', ], ], ], 'RestExternalModules' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'info' => [ 'type' => 'object', 'properties' => [ 'version' => [ 'type' => 'string', ], 'title' => [ 'type' => 'string', ], 'x-i18n-title' => [ 'type' => 'string', ], 'description' => [ 'type' => 'string', ], 'x-i18n-description' => [ 'type' => 'string', ], ], 'required' => [ 'version', ], ], 'base' => [ 'type' => 'string', 'format' => 'uri', ], 'spec' => [ 'type' => 'string', 'format' => 'uri', ], ], 'required' => [ 'info', 'base', 'spec', ], ], ], '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