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 );
857
858 // Check if the query failed...
859 $status->flags = $this->handleErroredQuery( $status, $sql, $fname, $queryRuntime, $priorSessInfo );
860 // Avoid the overhead of logging calls unless debug mode is enabled
861 if ( $this->flagsHolder->getFlag( self::DBO_DEBUG ) ) {
862 $this->logger->debug(
863 "{method} [{runtime_ms}ms] [{rows} rows] {db_server}: {sql}",
864 $this->getLogContext( [
865 'method' => $fname,
866 'sql' => $sql->getSQL(),
867 'domain' => $this->getDomainID(),
868 'runtime_ms' => round( $queryRuntime * 1000, 3 ),
869 'rows' => $isPermWrite ? $affectedRowCount : $returnedRowCount,
870 'db_log_category' => 'query'
871 ] )
872 );
873 }
874
875 return $status;
876 }
877
878 private function handleErroredQuery(
879 QueryStatus $status, Query $sql, string $fname, float $queryRuntime, CriticalSessionInfo $priorSessInfo
880 ): int {
881 $errflags = self::ERR_NONE;
882 $error = $status->message;
883 $errno = $status->code;
884 if ( $status->res !== false ) {
885 // Statement succeeded
886 return $errflags;
887 }
888 if ( $this->isConnectionError( $errno ) ) {
889 // Connection lost before or during the query...
890 // Determine how to proceed given the lost session state
891 $connLossFlag = $this->assessConnectionLoss(
892 $sql->getVerb(),
893 $queryRuntime,
894 $priorSessInfo
895 );
896 // Update session state tracking and try to reestablish a connection
897 $reconnected = $this->replaceLostConnection( $errno, __METHOD__ );
898 // Check if important server-side session-level state was lost
899 if ( $connLossFlag >= self::ERR_ABORT_SESSION ) {
900 $ex = $this->getQueryException( $error, $errno, $sql->getSQL(), $fname );
901 $this->transactionManager->setSessionError( $ex );
902 }
903 // Check if important server-side transaction-level state was lost
904 if ( $connLossFlag >= self::ERR_ABORT_TRX ) {
905 $ex = $this->getQueryException( $error, $errno, $sql->getSQL(), $fname );
906 $this->transactionManager->setTransactionError( $ex );
907 }
908 // Check if the query should be retried (having made the reconnection attempt)
909 if ( $connLossFlag === self::ERR_RETRY_QUERY ) {
910 $errflags |= ( $reconnected ? self::ERR_RETRY_QUERY : self::ERR_ABORT_QUERY );
911 } else {
912 $errflags |= $connLossFlag;
913 }
914 } elseif ( $this->isKnownStatementRollbackError( $errno ) ) {
915 // Query error triggered a server-side statement-only rollback...
916 $errflags |= self::ERR_ABORT_QUERY;
917 if ( $this->trxLevel() ) {
918 // Allow legacy callers to ignore such errors via QUERY_IGNORE_DBO_TRX and
919 // try/catch. However, a deprecation notice will be logged on the next query.
920 $cause = [ $error, $errno, $fname ];
921 $this->transactionManager->setTrxStatusIgnoredCause( $cause );
922 }
923 } elseif ( $this->trxLevel() ) {
924 // Some other error occurred during the query, within a transaction...
925 // Server-side handling of errors during transactions varies widely depending on
926 // the RDBMS type and configuration. There are several possible results: (a) the
927 // whole transaction is rolled back, (b) only the queries after BEGIN are rolled
928 // back, (c) the transaction is marked as "aborted" and a ROLLBACK is required
929 // before other queries are permitted. For compatibility reasons, pessimistically
930 // require a ROLLBACK query (not using SAVEPOINT) before allowing other queries.
931 $ex = $this->getQueryException( $error, $errno, $sql->getSQL(), $fname );
932 $this->transactionManager->setTransactionError( $ex );
933 $errflags |= self::ERR_ABORT_TRX;
934 } else {
935 // Some other error occurred during the query, without a transaction...
936 $errflags |= self::ERR_ABORT_QUERY;
937 }
938
939 return $errflags;
940 }
941
947 private function makeCommentedSql( $sql, $fname ): string {
948 // Add trace comment to the begin of the sql string, right after the operator.
949 // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598).
950 // NOTE: Don't add varying ids such as request id or session id to the comment.
951 // It would break aggregation of similar queries in analysis tools (see T193050#7512149)
952 $encName = preg_replace( '/[\x00-\x1F\/]/', '-', "$fname {$this->agent}" );
953 return preg_replace( '/\s|$/', " /* $encName */ ", $sql, 1 );
954 }
955
965 private function beginIfImplied( $sql, $fname, $flags ) {
966 if ( !$this->trxLevel() && $this->flagsHolder->hasApplicableImplicitTrxFlag( $flags ) ) {
967 if ( $this->platform->isTransactableQuery( $sql ) ) {
968 $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
969 $this->transactionManager->turnOnAutomatic();
970
971 return true;
972 }
973 }
974
975 return false;
976 }
977
993 private function assertQueryIsCurrentlyAllowed( string $verb, string $fname ) {
994 if ( $verb === 'USE' ) {
995 throw new DBUnexpectedError( $this, "Got USE query; use selectDomain() instead" );
996 }
997
998 if ( $verb === 'ROLLBACK' ) {
999 // Whole transaction rollback is used for recovery
1000 // @TODO: T269161; prevent "BEGIN"/"COMMIT"/"ROLLBACK" from outside callers
1001 return;
1002 }
1003
1004 if ( $this->csmError ) {
1005 throw new DBTransactionStateError(
1006 $this,
1007 "Cannot execute query from $fname while session state is out of sync",
1008 [],
1009 $this->csmError
1010 );
1011 }
1012
1013 $this->transactionManager->assertSessionStatus( $this, $fname );
1014
1015 if ( $verb !== 'ROLLBACK TO SAVEPOINT' ) {
1016 $this->transactionManager->assertTransactionStatus(
1017 $this,
1018 $this->deprecationLogger,
1019 $fname
1020 );
1021 }
1022 }
1023
1043 private function assessConnectionLoss(
1044 string $verb,
1045 float $walltime,
1046 CriticalSessionInfo $priorSessInfo
1047 ) {
1048 if ( $walltime < self::DROPPED_CONN_BLAME_THRESHOLD_SEC ) {
1049 // Query failed quickly; the connection was probably lost before the query was sent
1050 $res = self::ERR_RETRY_QUERY;
1051 } else {
1052 // Query took a long time; the connection was probably lost during query execution
1053 $res = self::ERR_ABORT_QUERY;
1054 }
1055
1056 // List of problems causing session/transaction state corruption
1057 $blockers = [];
1058 // Loss of named locks breaks future callers relying on those locks for critical sections
1059 foreach ( $priorSessInfo->namedLocks as $lockName => $lockInfo ) {
1060 if ( $lockInfo['trxId'] && $lockInfo['trxId'] === $priorSessInfo->trxId ) {
1061 // Treat lost locks acquired during the lost transaction as a transaction state
1062 // problem. Connection loss on ROLLBACK (non-SAVEPOINT) is tolerable since
1063 // rollback automatically triggered server-side.
1064 if ( $verb !== 'ROLLBACK' ) {
1065 $res = max( $res, self::ERR_ABORT_TRX );
1066 $blockers[] = "named lock '$lockName'";
1067 }
1068 } else {
1069 // Treat lost locks acquired either during prior transactions or during no
1070 // transaction as a session state problem.
1071 $res = max( $res, self::ERR_ABORT_SESSION );
1072 $blockers[] = "named lock '$lockName'";
1073 }
1074 }
1075 // Loss of temp tables breaks future callers relying on those tables for queries
1076 foreach ( $priorSessInfo->tempTables as $domainTempTables ) {
1077 foreach ( $domainTempTables as $tableName => $tableInfo ) {
1078 if ( $tableInfo->trxId && $tableInfo->trxId === $priorSessInfo->trxId ) {
1079 // Treat lost temp tables created during the lost transaction as a
1080 // transaction state problem. Connection loss on ROLLBACK (non-SAVEPOINT)
1081 // is tolerable since rollback automatically triggered server-side.
1082 if ( $verb !== 'ROLLBACK' ) {
1083 $res = max( $res, self::ERR_ABORT_TRX );
1084 $blockers[] = "temp table '$tableName'";
1085 }
1086 } else {
1087 // Treat lost temp tables created either during prior transactions or during
1088 // no transaction as a session state problem.
1089 $res = max( $res, self::ERR_ABORT_SESSION );
1090 $blockers[] = "temp table '$tableName'";
1091 }
1092 }
1093 }
1094 // Loss of transaction writes breaks future callers and DBO_TRX logic relying on those
1095 // writes to be atomic and still pending. Connection loss on ROLLBACK (non-SAVEPOINT) is
1096 // tolerable since rollback automatically triggered server-side.
1097 if ( $priorSessInfo->trxWriteCallers && $verb !== 'ROLLBACK' ) {
1098 $res = max( $res, self::ERR_ABORT_TRX );
1099 $blockers[] = 'uncommitted writes';
1100 }
1101 if ( $priorSessInfo->trxPreCommitCbCallers && $verb !== 'ROLLBACK' ) {
1102 $res = max( $res, self::ERR_ABORT_TRX );
1103 $blockers[] = 'pre-commit callbacks';
1104 }
1105 if ( $priorSessInfo->trxExplicit && $verb !== 'ROLLBACK' && $verb !== 'COMMIT' ) {
1106 // Transaction automatically rolled back, breaking the expectations of callers
1107 // relying on the continued existence of that transaction for things like atomic
1108 // writes, serializability, or reads from the same point-in-time snapshot. If the
1109 // connection loss occurred on ROLLBACK (non-SAVEPOINT) or COMMIT, then we do not
1110 // need to mark the transaction state as corrupt, since no transaction would still
1111 // be open even if the query did succeed (T127428).
1112 $res = max( $res, self::ERR_ABORT_TRX );
1113 $blockers[] = 'explicit transaction';
1114 }
1115
1116 if ( $blockers ) {
1117 $this->logger->warning(
1118 "cannot reconnect to {db_server} silently: {error}",
1119 $this->getLogContext( [
1120 'error' => 'session state loss (' . implode( ', ', $blockers ) . ')',
1121 'exception' => new RuntimeException(),
1122 'db_log_category' => 'connection'
1123 ] )
1124 );
1125 }
1126
1127 return $res;
1128 }
1129
1133 private function handleSessionLossPreconnect() {
1134 // Clean up tracking of session-level things...
1135 // https://mariadb.com/kb/en/create-table/#create-temporary-table
1136 // https://www.postgresql.org/docs/9.2/static/sql-createtable.html (ignoring ON COMMIT)
1137 $this->sessionTempTables = [];
1138 // https://mariadb.com/kb/en/get_lock/
1139 // https://www.postgresql.org/docs/9.4/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
1140 $this->sessionNamedLocks = [];
1141 // Session loss implies transaction loss (T67263)
1142 $this->transactionManager->onSessionLoss( $this );
1143 // Clear additional subclass fields
1144 $this->doHandleSessionLossPreconnect();
1145 }
1146
1150 protected function doHandleSessionLossPreconnect() {
1151 // no-op
1152 }
1153
1164 protected function isQueryTimeoutError( $errno ) {
1165 return false;
1166 }
1167
1181 public function reportQueryError( $error, $errno, $sql, $fname, $ignore = false ) {
1182 if ( $ignore ) {
1183 $this->logger->debug(
1184 "SQL ERROR (ignored): $error",
1185 [ 'db_log_category' => 'query' ]
1186 );
1187 } else {
1188 throw $this->getQueryExceptionAndLog( $error, $errno, $sql, $fname );
1189 }
1190 }
1191
1199 private function getQueryExceptionAndLog( $error, $errno, $sql, $fname ) {
1200 // Information that instances of the same problem have in common should
1201 // not be normalized (T255202).
1202 $this->logger->error(
1203 "Error $errno from $fname, {error} {sql1line} {db_server}",
1204 $this->getLogContext( [
1205 'method' => __METHOD__,
1206 'errno' => $errno,
1207 'error' => $error,
1208 'sql1line' => mb_substr( str_replace( "\n", "\\n", $sql ), 0, 5 * 1024 ),
1209 'fname' => $fname,
1210 'db_log_category' => 'query',
1211 'exception' => new RuntimeException()
1212 ] )
1213 );
1214 return $this->getQueryException( $error, $errno, $sql, $fname );
1215 }
1216
1224 private function getQueryException( $error, $errno, $sql, $fname ) {
1225 if ( $this->isQueryTimeoutError( $errno ) ) {
1226 return new DBQueryTimeoutError( $this, $error, $errno, $sql, $fname );
1227 } elseif ( $this->isConnectionError( $errno ) ) {
1228 return new DBQueryDisconnectedError( $this, $error, $errno, $sql, $fname );
1229 } else {
1230 return new DBQueryError( $this, $error, $errno, $sql, $fname );
1231 }
1232 }
1233
1238 final protected function newExceptionAfterConnectError( $error ) {
1239 // Connection was not fully initialized and is not safe for use.
1240 // Stash any error associated with the handle before destroying it.
1241 $this->lastConnectError = $error;
1242 $this->conn = null;
1243
1244 $this->logger->error(
1245 "Error connecting to {db_server} as user {db_user}: {error}",
1246 $this->getLogContext( [
1247 'error' => $error,
1248 'exception' => new RuntimeException(),
1249 'db_log_category' => 'connection',
1250 ] )
1251 );
1252
1253 return new DBConnectionError( $this, $error );
1254 }
1255
1261 return new SelectQueryBuilder( $this );
1262 }
1263
1269 return new UnionQueryBuilder( $this );
1270 }
1271
1277 return new UpdateQueryBuilder( $this );
1278 }
1279
1285 return new DeleteQueryBuilder( $this );
1286 }
1287
1293 return new InsertQueryBuilder( $this );
1294 }
1295
1301 return new ReplaceQueryBuilder( $this );
1302 }
1303
1305 public function selectField(
1306 $tables, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1307 ) {
1308 if ( $var === '*' ) {
1309 throw new DBUnexpectedError( $this, "Cannot use a * field" );
1310 } elseif ( is_array( $var ) && count( $var ) !== 1 ) {
1311 throw new DBUnexpectedError( $this, 'Cannot use more than one field' );
1312 }
1313
1314 $options = $this->platform->normalizeOptions( $options );
1315 $options['LIMIT'] = 1;
1316
1317 $res = $this->select( $tables, $var, $cond, $fname, $options, $join_conds );
1318 if ( $res === false ) {
1319 throw new DBUnexpectedError( $this, "Got false from select()" );
1320 }
1321
1322 $row = $res->fetchRow();
1323 if ( $row === false ) {
1324 return false;
1325 }
1326
1327 return reset( $row );
1328 }
1329
1331 public function selectFieldValues(
1332 $tables, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
1333 ): array {
1334 if ( $var === '*' ) {
1335 throw new DBUnexpectedError( $this, "Cannot use a * field" );
1336 } elseif ( !is_string( $var ) ) {
1337 throw new DBUnexpectedError( $this, "Cannot use an array of fields" );
1338 }
1339
1340 $options = $this->platform->normalizeOptions( $options );
1341 $res = $this->select( $tables, [ 'value' => $var ], $cond, $fname, $options, $join_conds );
1342 if ( $res === false ) {
1343 throw new DBUnexpectedError( $this, "Got false from select()" );
1344 }
1345
1346 $values = [];
1347 foreach ( $res as $row ) {
1348 $values[] = $row->value;
1349 }
1350
1351 return $values;
1352 }
1353
1355 public function select(
1356 $tables, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1357 ) {
1358 $options = (array)$options;
1359 // Don't turn this into using platform directly, DatabaseMySQL overrides this.
1360 $sql = $this->selectSQLText( $tables, $vars, $conds, $fname, $options, $join_conds );
1361 // Treat SELECT queries with FOR UPDATE as writes. This matches
1362 // how MySQL enforces read_only (FOR SHARE and LOCK IN SHADE MODE are allowed).
1363 $flags = in_array( 'FOR UPDATE', $options, true )
1364 ? self::QUERY_CHANGE_ROWS
1365 : self::QUERY_CHANGE_NONE;
1366
1367 $query = new Query( $sql, $flags, 'SELECT' );
1368 return $this->query( $query, $fname );
1369 }
1370
1372 public function selectRow( $tables, $vars, $conds, $fname = __METHOD__,
1373 $options = [], $join_conds = []
1374 ) {
1375 $options = (array)$options;
1376 $options['LIMIT'] = 1;
1377
1378 $res = $this->select( $tables, $vars, $conds, $fname, $options, $join_conds );
1379 if ( $res === false ) {
1380 throw new DBUnexpectedError( $this, "Got false from select()" );
1381 }
1382
1383 if ( !$res->numRows() ) {
1384 return false;
1385 }
1386
1387 return $res->fetchObject();
1388 }
1389
1393 public function estimateRowCount(
1394 $tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1395 ): int {
1396 $conds = $this->platform->normalizeConditions( $conds, $fname );
1397 $column = $this->platform->extractSingleFieldFromList( $var );
1398 if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
1399 $conds[] = "$column IS NOT NULL";
1400 }
1401
1402 $res = $this->select(
1403 $tables, [ 'rowcount' => 'COUNT(*)' ], $conds, $fname, $options, $join_conds
1404 );
1405 $row = $res ? $res->fetchRow() : [];
1406
1407 return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
1408 }
1409
1411 public function selectRowCount(
1412 $tables, $var = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1413 ): int {
1414 $conds = $this->platform->normalizeConditions( $conds, $fname );
1415 $column = $this->platform->extractSingleFieldFromList( $var );
1416 if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
1417 $conds[] = "$column IS NOT NULL";
1418 }
1419 if ( in_array( 'DISTINCT', (array)$options ) ) {
1420 if ( $column === null ) {
1421 throw new DBUnexpectedError( $this,
1422 '$var cannot be empty when the DISTINCT option is given' );
1423 }
1424 $innerVar = $column;
1425 } else {
1426 $innerVar = '1';
1427 }
1428
1429 $res = $this->select(
1430 [
1431 'tmp_count' => $this->platform->buildSelectSubquery(
1432 $tables,
1433 $innerVar,
1434 $conds,
1435 $fname,
1436 $options,
1437 $join_conds
1438 )
1439 ],
1440 [ 'rowcount' => 'COUNT(*)' ],
1441 [],
1442 $fname
1443 );
1444 $row = $res ? $res->fetchRow() : [];
1445
1446 return isset( $row['rowcount'] ) ? (int)$row['rowcount'] : 0;
1447 }
1448
1450 public function lockForUpdate(
1451 $table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
1452 ) {
1453 if ( !$this->trxLevel() && !$this->flagsHolder->hasImplicitTrxFlag() ) {
1454 throw new DBUnexpectedError(
1455 $this,
1456 __METHOD__ . ': no transaction is active nor is DBO_TRX set'
1457 );
1458 }
1459
1460 $options = (array)$options;
1461 $options[] = 'FOR UPDATE';
1462
1463 return $this->selectRowCount( $table, '*', $conds, $fname, $options, $join_conds );
1464 }
1465
1467 public function fieldExists( $table, $field, $fname = __METHOD__ ) {
1468 $info = $this->fieldInfo( $table, $field );
1469
1470 return (bool)$info;
1471 }
1472
1474 abstract public function tableExists( $table, $fname = __METHOD__ );
1475
1477 public function indexExists( $table, $index, $fname = __METHOD__ ) {
1478 $info = $this->indexInfo( $table, $index, $fname );
1479
1480 return (bool)$info;
1481 }
1482
1484 public function indexUnique( $table, $index, $fname = __METHOD__ ) {
1485 $info = $this->indexInfo( $table, $index, $fname );
1486
1487 return $info ? $info['unique'] : null;
1488 }
1489
1491 abstract public function getPrimaryKeyColumns( $table, $fname = __METHOD__ );
1492
1502 abstract public function indexInfo( $table, $index, $fname = __METHOD__ );
1503
1505 public function insert( $table, $rows, $fname = __METHOD__, $options = [] ) {
1506 $query = $this->platform->dispatchingInsertSqlText( $table, $rows, $options );
1507 if ( !$query ) {
1508 return true;
1509 }
1510 $this->query( $query, $fname );
1511 if ( $this->strictWarnings ) {
1512 $this->checkInsertWarnings( $query, $fname );
1513 }
1514 return true;
1515 }
1516
1525 protected function checkInsertWarnings( Query $query, $fname ) {
1526 }
1527
1529 public function update( $table, $set, $conds, $fname = __METHOD__, $options = [] ) {
1530 $query = $this->platform->updateSqlText( $table, $set, $conds, $options );
1531 $this->query( $query, $fname );
1532
1533 return true;
1534 }
1535
1537 public function databasesAreIndependent() {
1538 return false;
1539 }
1540
1542 final public function selectDomain( $domain ) {
1543 $cs = $this->commenceCriticalSection( __METHOD__ );
1544
1545 try {
1546 $this->doSelectDomain( DatabaseDomain::newFromId( $domain ) );
1547 } catch ( DBError $e ) {
1548 $this->completeCriticalSection( __METHOD__, $cs );
1549 throw $e;
1550 }
1551
1552 $this->completeCriticalSection( __METHOD__, $cs );
1553 }
1554
1561 protected function doSelectDomain( DatabaseDomain $domain ) {
1562 $this->currentDomain = $domain;
1563 $this->platform->setCurrentDomain( $this->currentDomain );
1564 }
1565
1567 public function getDBname() {
1568 return $this->currentDomain->getDatabase();
1569 }
1570
1572 public function getServer() {
1573 return $this->connectionParams[self::CONN_SERVER] ?? null;
1574 }
1575
1577 public function getServerName() {
1578 return $this->serverName ?? $this->getServer() ?? 'unknown';
1579 }
1580
1582 public function addQuotes( $s ) {
1583 if ( $s instanceof RawSQLValue ) {
1584 return $s->toSql();
1585 }
1586 if ( $s instanceof Blob ) {
1587 $s = $s->fetch();
1588 }
1589 if ( $s === null ) {
1590 return 'NULL';
1591 } elseif ( is_bool( $s ) ) {
1592 return (string)(int)$s;
1593 } elseif ( is_int( $s ) ) {
1594 return (string)$s;
1595 } else {
1596 return "'" . $this->strencode( $s ) . "'";
1597 }
1598 }
1599
1601 public function expr( string $field, string $op, $value ): Expression {
1602 return new Expression( $field, $op, $value );
1603 }
1604
1606 public function andExpr( array $conds ): AndExpressionGroup {
1607 return AndExpressionGroup::newFromArray( $conds );
1608 }
1609
1611 public function orExpr( array $conds ): OrExpressionGroup {
1612 return OrExpressionGroup::newFromArray( $conds );
1613 }
1614
1616 public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
1617 $uniqueKey = $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
1618 if ( !$rows ) {
1619 return;
1620 }
1621 $affectedRowCount = 0;
1622 $insertId = null;
1623 $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
1624 try {
1625 foreach ( $rows as $row ) {
1626 // Delete any conflicting rows (including ones inserted from $rows)
1627 $query = $this->platform->deleteSqlText(
1628 $table,
1629 [ $this->platform->makeKeyCollisionCondition( [ $row ], $uniqueKey ) ]
1630 );
1631 $this->query( $query, $fname );
1632 // Insert the new row
1633 $query = $this->platform->dispatchingInsertSqlText( $table, $row, [] );
1634 $this->query( $query, $fname );
1635 $affectedRowCount += $this->lastQueryAffectedRows;
1636 $insertId = $insertId ?: $this->lastQueryInsertId;
1637 }
1638 $this->endAtomic( $fname );
1639 } catch ( DBError $e ) {
1640 $this->cancelAtomic( $fname );
1641 throw $e;
1642 }
1643 $this->lastEmulatedAffectedRows = $affectedRowCount;
1644 $this->lastEmulatedInsertId = $insertId;
1645 }
1646
1648 public function upsert( $table, array $rows, $uniqueKeys, array $set, $fname = __METHOD__ ) {
1649 $uniqueKey = $this->platform->normalizeUpsertParams( $uniqueKeys, $rows );
1650 if ( !$rows ) {
1651 return true;
1652 }
1653 $this->platform->assertValidUpsertSetArray( $set, $uniqueKey, $rows );
1654
1655 $encTable = $this->tableName( $table );
1656 $sqlColumnAssignments = $this->makeList( $set, self::LIST_SET );
1657 // Get any AUTO_INCREMENT/SERIAL column for this table so we can set insertId()
1658 $autoIncrementColumn = $this->getInsertIdColumnForUpsert( $table );
1659 // Check if there is a SQL assignment expression in $set (as generated by SQLPlatform::buildExcludedValue)
1660 $useWith = (bool)array_filter(
1661 $set,
1662 static function ( $v, $k ) {
1663 return $v instanceof RawSQLValue || is_int( $k );
1664 },
1665 ARRAY_FILTER_USE_BOTH
1666 );
1667 // Subclasses might need explicit type casting within "WITH...AS (VALUES ...)"
1668 // so that these CTE rows can be referenced within the SET clause assigments.
1669 $typeByColumn = $useWith ? $this->getValueTypesForWithClause( $table ) : [];
1670
1671 $first = true;
1672 $affectedRowCount = 0;
1673 $insertId = null;
1674 $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
1675 try {
1676 foreach ( $rows as $row ) {
1677 // Update any existing conflicting row (including ones inserted from $rows)
1678 [ $sqlColumns, $sqlTuples, $sqlVals ] = $this->platform->makeInsertLists(
1679 [ $row ],
1680 '__',
1681 $typeByColumn
1682 );
1683 $sqlConditions = $this->platform->makeKeyCollisionCondition(
1684 [ $row ],
1685 $uniqueKey
1686 );
1687 $query = new Query(
1688 ( $useWith ? "WITH __VALS ($sqlVals) AS (VALUES $sqlTuples) " : "" ) .
1689 "UPDATE $encTable SET $sqlColumnAssignments " .
1690 "WHERE ($sqlConditions)",
1691 self::QUERY_CHANGE_ROWS,
1692 'UPDATE',
1693 $table
1694 );
1695 $this->query( $query, $fname );
1696 $rowsUpdated = $this->lastQueryAffectedRows;
1697 $affectedRowCount += $rowsUpdated;
1698 if ( $rowsUpdated > 0 ) {
1699 // Conflicting row found and updated
1700 if ( $first && $autoIncrementColumn !== null ) {
1701 // @TODO: use "RETURNING" instead (when supported by SQLite)
1702 $query = new Query(
1703 "SELECT $autoIncrementColumn AS id FROM $encTable " .
1704 "WHERE ($sqlConditions)",
1705 self::QUERY_CHANGE_NONE,
1706 'SELECT'
1707 );
1708 $sRes = $this->query( $query, $fname, self::QUERY_CHANGE_ROWS );
1709 $insertId = (int)$sRes->fetchRow()['id'];
1710 }
1711 } else {
1712 // No conflicting row found
1713 $query = new Query(
1714 "INSERT INTO $encTable ($sqlColumns) VALUES $sqlTuples",
1715 self::QUERY_CHANGE_ROWS,
1716 'INSERT',
1717 $table
1718 );
1719 $this->query( $query, $fname );
1720 $affectedRowCount += $this->lastQueryAffectedRows;
1721 }
1722 $first = false;
1723 }
1724 $this->endAtomic( $fname );
1725 } catch ( DBError $e ) {
1726 $this->cancelAtomic( $fname );
1727 throw $e;
1728 }
1729 $this->lastEmulatedAffectedRows = $affectedRowCount;
1730 $this->lastEmulatedInsertId = $insertId;
1731 return true;
1732 }
1733
1738 protected function getInsertIdColumnForUpsert( $table ) {
1739 return null;
1740 }
1741
1746 protected function getValueTypesForWithClause( $table ) {
1747 return [];
1748 }
1749
1751 public function deleteJoin(
1752 $delTable,
1753 $joinTable,
1754 $delVar,
1755 $joinVar,
1756 $conds,
1757 $fname = __METHOD__
1758 ) {
1759 $sql = $this->platform->deleteJoinSqlText( $delTable, $joinTable, $delVar, $joinVar, $conds );
1760 $query = new Query( $sql, self::QUERY_CHANGE_ROWS, 'DELETE', $delTable );
1761 $this->query( $query, $fname );
1762 }
1763
1765 public function delete( $table, $conds, $fname = __METHOD__ ) {
1766 $this->query( $this->platform->deleteSqlText( $table, $conds ), $fname );
1767
1768 return true;
1769 }
1770
1772 final public function insertSelect(
1773 $destTable,
1774 $srcTable,
1775 $varMap,
1776 $conds,
1777 $fname = __METHOD__,
1778 $insertOptions = [],
1779 $selectOptions = [],
1780 $selectJoinConds = []
1781 ) {
1782 static $hints = [ 'NO_AUTO_COLUMNS' ];
1783
1784 $insertOptions = $this->platform->normalizeOptions( $insertOptions );
1785 $selectOptions = $this->platform->normalizeOptions( $selectOptions );
1786
1787 if ( $this->cliMode && $this->isInsertSelectSafe( $insertOptions, $selectOptions, $fname ) ) {
1788 // For massive migrations with downtime, we don't want to select everything
1789 // into memory and OOM, so do all this native on the server side if possible.
1790 $this->doInsertSelectNative(
1791 $destTable,
1792 $srcTable,
1793 $varMap,
1794 $conds,
1795 $fname,
1796 array_diff( $insertOptions, $hints ),
1797 $selectOptions,
1798 $selectJoinConds
1799 );
1800 } else {
1801 $this->doInsertSelectGeneric(
1802 $destTable,
1803 $srcTable,
1804 $varMap,
1805 $conds,
1806 $fname,
1807 array_diff( $insertOptions, $hints ),
1808 $selectOptions,
1809 $selectJoinConds
1810 );
1811 }
1812
1813 return true;
1814 }
1815
1823 protected function isInsertSelectSafe( array $insertOptions, array $selectOptions, $fname ) {
1824 return true;
1825 }
1826
1841 private function doInsertSelectGeneric(
1842 $destTable,
1843 $srcTable,
1844 array $varMap,
1845 $conds,
1846 $fname,
1847 array $insertOptions,
1848 array $selectOptions,
1849 $selectJoinConds
1850 ) {
1851 // For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
1852 // on only the primary DB (without needing row-based-replication). It also makes it easy to
1853 // know how big the INSERT is going to be.
1854 $fields = [];
1855 foreach ( $varMap as $dstColumn => $sourceColumnOrSql ) {
1856 $fields[] = $this->platform->fieldNameWithAlias( $sourceColumnOrSql, $dstColumn );
1857 }
1858 $res = $this->select(
1859 $srcTable,
1860 implode( ',', $fields ),
1861 $conds,
1862 $fname,
1863 array_merge( $selectOptions, [ 'FOR UPDATE' ] ),
1864 $selectJoinConds
1865 );
1866
1867 $affectedRowCount = 0;
1868 $insertId = null;
1869 if ( $res ) {
1870 $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
1871 try {
1872 $rows = [];
1873 foreach ( $res as $row ) {
1874 $rows[] = (array)$row;
1875 }
1876 // Avoid inserts that are too huge
1877 $rowBatches = array_chunk( $rows, $this->nonNativeInsertSelectBatchSize );
1878 foreach ( $rowBatches as $rows ) {
1879 $query = $this->platform->dispatchingInsertSqlText( $destTable, $rows, $insertOptions );
1880 $this->query( $query, $fname );
1881 $affectedRowCount += $this->lastQueryAffectedRows;
1882 $insertId = $insertId ?: $this->lastQueryInsertId;
1883 }
1884 $this->endAtomic( $fname );
1885 } catch ( DBError $e ) {
1886 $this->cancelAtomic( $fname );
1887 throw $e;
1888 }
1889 }
1890 $this->lastEmulatedAffectedRows = $affectedRowCount;
1891 $this->lastEmulatedInsertId = $insertId;
1892 }
1893
1909 protected function doInsertSelectNative(
1910 $destTable,
1911 $srcTable,
1912 array $varMap,
1913 $conds,
1914 $fname,
1915 array $insertOptions,
1916 array $selectOptions,
1917 $selectJoinConds
1918 ) {
1919 $sql = $this->platform->insertSelectNativeSqlText(
1920 $destTable,
1921 $srcTable,
1922 $varMap,
1923 $conds,
1924 $fname,
1925 $insertOptions,
1926 $selectOptions,
1927 $selectJoinConds
1928 );
1929 $query = new Query(
1930 $sql,
1931 self::QUERY_CHANGE_ROWS,
1932 'INSERT',
1933 $destTable
1934 );
1935 $this->query( $query, $fname );
1936 }
1937
1945 protected function isConnectionError( $errno ) {
1946 return false;
1947 }
1948
1956 protected function isKnownStatementRollbackError( $errno ) {
1957 return false; // don't know; it could have caused a transaction rollback
1958 }
1959
1963 public function serverIsReadOnly() {
1964 return false;
1965 }
1966
1968 final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
1969 $this->transactionManager->onTransactionResolution( $this, $callback, $fname );
1970 }
1971
1973 final public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
1974 if ( !$this->trxLevel() && $this->getTransactionRoundFname() !== null ) {
1975 // This DB handle is set to participate in LoadBalancer transaction rounds and
1976 // an explicit transaction round is active. Start an implicit transaction on this
1977 // DB handle (setting trxAutomatic) similar to how query() does in such situations.
1978 $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
1979 }
1980
1981 $this->transactionManager->addPostCommitOrIdleCallback( $callback, $fname );
1982 if ( !$this->trxLevel() ) {
1983 $dbErrors = [];
1984 $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE, $dbErrors );
1985 if ( $dbErrors ) {
1986 throw $dbErrors[0];
1987 }
1988 }
1989 }
1990
1992 final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
1993 if ( !$this->trxLevel() && $this->getTransactionRoundFname() !== null ) {
1994 // This DB handle is set to participate in LoadBalancer transaction rounds and
1995 // an explicit transaction round is active. Start an implicit transaction on this
1996 // DB handle (setting trxAutomatic) similar to how query() does in such situations.
1997 $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
1998 }
1999
2000 if ( $this->trxLevel() ) {
2001 $this->transactionManager->addPreCommitOrIdleCallback(
2002 $callback,
2003 $fname
2004 );
2005 } else {
2006 // No transaction is active nor will start implicitly, so make one for this callback
2007 $this->startAtomic( __METHOD__, self::ATOMIC_CANCELABLE );
2008 try {
2009 $callback( $this );
2010 } catch ( Throwable $e ) {
2011 // Avoid confusing error reporting during critical section errors
2012 if ( !$this->csmError ) {
2013 $this->cancelAtomic( __METHOD__ );
2014 }
2015 throw $e;
2016 }
2017 $this->endAtomic( __METHOD__ );
2018 }
2019 }
2020
2022 final public function setTransactionListener( $name, ?callable $callback = null ) {
2023 $this->transactionManager->setTransactionListener( $name, $callback );
2024 }
2025
2034 final public function setTrxEndCallbackSuppression( $suppress ) {
2035 $this->transactionManager->setTrxEndCallbackSuppression( $suppress );
2036 }
2037
2050 public function runOnTransactionIdleCallbacks( $trigger, array &$errors = [] ) {
2051 if ( $this->trxLevel() ) {
2052 throw new DBUnexpectedError( $this, __METHOD__ . ': a transaction is still open' );
2053 }
2054
2055 if ( $this->transactionManager->isEndCallbacksSuppressed() ) {
2056 // Execution deferred by LoadBalancer for explicit execution later
2057 return 0;
2058 }
2059
2060 $cs = $this->commenceCriticalSection( __METHOD__ );
2061
2062 $count = 0;
2063 $autoTrx = $this->flagsHolder->hasImplicitTrxFlag(); // automatic begin() enabled?
2064 // Drain the queues of transaction "idle" and "end" callbacks until they are empty
2065 do {
2066 $callbackEntries = $this->transactionManager->consumeEndCallbacks();
2067 $count += count( $callbackEntries );
2068 foreach ( $callbackEntries as $entry ) {
2069 $this->flagsHolder->clearFlag( self::DBO_TRX ); // make each query its own transaction
2070 try {
2071 $entry[0]( $trigger );
2072 } catch ( DBError $ex ) {
2073 ( $this->errorLogger )( $ex );
2074 $errors[] = $ex;
2075 // Some callbacks may use startAtomic/endAtomic, so make sure
2076 // their transactions are ended so other callbacks don't fail
2077 if ( $this->trxLevel() ) {
2078 $this->rollback( __METHOD__, self::FLUSHING_INTERNAL );
2079 }
2080 } finally {
2081 if ( $autoTrx ) {
2082 $this->flagsHolder->setFlag( self::DBO_TRX ); // restore automatic begin()
2083 } else {
2084 $this->flagsHolder->clearFlag( self::DBO_TRX ); // restore auto-commit
2085 }
2086 }
2087 }
2088 } while ( $this->transactionManager->countPostCommitOrIdleCallbacks() );
2089
2090 $this->completeCriticalSection( __METHOD__, $cs );
2091
2092 return $count;
2093 }
2094
2105 public function runTransactionListenerCallbacks( $trigger, array &$errors = [] ) {
2106 if ( $this->transactionManager->isEndCallbacksSuppressed() ) {
2107 // Execution deferred by LoadBalancer for explicit execution later
2108 return;
2109 }
2110
2111 // These callbacks should only be registered in setup, thus no iteration is needed
2112 foreach ( $this->transactionManager->getRecurringCallbacks() as $callback ) {
2113 try {
2114 $callback( $trigger, $this );
2115 } catch ( DBError $ex ) {
2116 ( $this->errorLogger )( $ex );
2117 $errors[] = $ex;
2118 }
2119 }
2120 }
2121
2128 private function runTransactionPostCommitCallbacks() {
2129 $dbErrors = [];
2130 $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT, $dbErrors );
2131 $this->runTransactionListenerCallbacks( self::TRIGGER_COMMIT, $dbErrors );
2132 $this->lastEmulatedAffectedRows = 0; // for the sake of consistency
2133 if ( $dbErrors ) {
2134 throw $dbErrors[0];
2135 }
2136 }
2137
2145 private function runTransactionPostRollbackCallbacks() {
2146 $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
2147 $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
2148 $this->lastEmulatedAffectedRows = 0; // for the sake of consistency
2149 }
2150
2152 final public function startAtomic(
2153 $fname = __METHOD__,
2154 $cancelable = self::ATOMIC_NOT_CANCELABLE
2155 ) {
2156 $cs = $this->commenceCriticalSection( __METHOD__ );
2157
2158 if ( $this->trxLevel() ) {
2159 // This atomic section is only one part of a larger transaction
2160 $sectionOwnsTrx = false;
2161 } else {
2162 // Start an implicit transaction (sets trxAutomatic)
2163 try {
2164 $this->begin( $fname, self::TRANSACTION_INTERNAL );
2165 } catch ( DBError $e ) {
2166 $this->completeCriticalSection( __METHOD__, $cs );
2167 throw $e;
2168 }
2169 if ( $this->flagsHolder->hasImplicitTrxFlag() ) {
2170 // This DB handle participates in LoadBalancer transaction rounds; all atomic
2171 // sections should be buffered into one transaction (e.g. to keep web requests
2172 // transactional). Note that an implicit transaction round is considered to be
2173 // active when no there is no explicit transaction round.
2174 $sectionOwnsTrx = false;
2175 } else {
2176 // This DB handle does not participate in LoadBalancer transaction rounds;
2177 // each topmost atomic section will use its own transaction.
2178 $sectionOwnsTrx = true;
2179 }
2180 $this->transactionManager->setAutomaticAtomic( $sectionOwnsTrx );
2181 }
2182
2183 if ( $cancelable === self::ATOMIC_CANCELABLE ) {
2184 if ( $sectionOwnsTrx ) {
2185 // This atomic section is synonymous with the whole transaction; just
2186 // use full COMMIT/ROLLBACK in endAtomic()/cancelAtomic(), respectively
2187 $savepointId = self::NOT_APPLICABLE;
2188 } else {
2189 // This atomic section is only part of the whole transaction; use a SAVEPOINT
2190 // query so that its changes can be cancelled without losing the rest of the
2191 // transaction (e.g. changes from other sections or from outside of sections)
2192 try {
2193 $savepointId = $this->transactionManager->nextSavePointId( $this, $fname );
2194 $sql = $this->platform->savepointSqlText( $savepointId );
2195 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'SAVEPOINT' );
2196 $this->query( $query, $fname );
2197 } catch ( DBError $e ) {
2198 $this->completeCriticalSection( __METHOD__, $cs, $e );
2199 throw $e;
2200 }
2201 }
2202 } else {
2203 $savepointId = null;
2204 }
2205
2206 $sectionId = new AtomicSectionIdentifier;
2207 $this->transactionManager->addToAtomicLevels( $fname, $sectionId, $savepointId );
2208
2209 $this->completeCriticalSection( __METHOD__, $cs );
2210
2211 return $sectionId;
2212 }
2213
2215 final public function endAtomic( $fname = __METHOD__ ) {
2216 [ $savepointId, $sectionId ] = $this->transactionManager->onEndAtomic( $this, $fname );
2217
2218 $runPostCommitCallbacks = false;
2219
2220 $cs = $this->commenceCriticalSection( __METHOD__ );
2221
2222 // Remove the last section (no need to re-index the array)
2223 $finalLevelOfImplicitTrxPopped = $this->transactionManager->popAtomicLevel();
2224
2225 try {
2226 if ( $finalLevelOfImplicitTrxPopped ) {
2227 $this->commit( $fname, self::FLUSHING_INTERNAL );
2228 $runPostCommitCallbacks = true;
2229 } elseif ( $savepointId !== null && $savepointId !== self::NOT_APPLICABLE ) {
2230 $sql = $this->platform->releaseSavepointSqlText( $savepointId );
2231 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'RELEASE SAVEPOINT' );
2232 $this->query( $query, $fname );
2233 }
2234 } catch ( DBError $e ) {
2235 $this->completeCriticalSection( __METHOD__, $cs, $e );
2236 throw $e;
2237 }
2238
2239 $this->transactionManager->onEndAtomicInCriticalSection( $sectionId );
2240
2241 $this->completeCriticalSection( __METHOD__, $cs );
2242
2243 if ( $runPostCommitCallbacks ) {
2244 $this->runTransactionPostCommitCallbacks();
2245 }
2246 }
2247
2249 final public function cancelAtomic(
2250 $fname = __METHOD__,
2251 ?AtomicSectionIdentifier $sectionId = null
2252 ) {
2253 $this->transactionManager->onCancelAtomicBeforeCriticalSection( $this, $fname );
2254 $pos = $this->transactionManager->getPositionFromSectionId( $sectionId );
2255 if ( $pos < 0 ) {
2256 throw new DBUnexpectedError( $this, "Atomic section not found (for $fname)" );
2257 }
2258
2259 $cs = $this->commenceCriticalSection( __METHOD__ );
2260 $runPostRollbackCallbacks = false;
2261 [ $savedFname, $excisedSectionIds, $newTopSectionId, $savedSectionId, $savepointId ] =
2262 $this->transactionManager->cancelAtomic( $pos );
2263
2264 try {
2265 if ( $savedFname !== $fname ) {
2266 $e = new DBUnexpectedError(
2267 $this,
2268 "Invalid atomic section ended (got $fname but expected $savedFname)"
2269 );
2270 $this->completeCriticalSection( __METHOD__, $cs, $e );
2271 throw $e;
2272 }
2273
2274 // Remove the last section (no need to re-index the array)
2275 $this->transactionManager->popAtomicLevel();
2276 $excisedSectionIds[] = $savedSectionId;
2277 $newTopSectionId = $this->transactionManager->currentAtomicSectionId();
2278
2279 if ( $savepointId !== null ) {
2280 // Rollback the transaction changes proposed within this atomic section
2281 if ( $savepointId === self::NOT_APPLICABLE ) {
2282 // Atomic section started the transaction; rollback the whole transaction
2283 // and trigger cancellation callbacks for all active atomic sections
2284 $this->rollback( $fname, self::FLUSHING_INTERNAL );
2285 $runPostRollbackCallbacks = true;
2286 } else {
2287 // Atomic section nested within the transaction; rollback the transaction
2288 // to the state prior to this section and trigger its cancellation callbacks
2289 $sql = $this->platform->rollbackToSavepointSqlText( $savepointId );
2290 $query = new Query( $sql, self::QUERY_CHANGE_TRX, 'ROLLBACK TO SAVEPOINT' );
2291 $this->query( $query, $fname );
2292 $this->transactionManager->setTrxStatusToOk(); // no exception; recovered
2293 }
2294 } else {
2295 // Put the transaction into an error state if it's not already in one
2296 $trxError = new DBUnexpectedError(
2297 $this,
2298 "Uncancelable atomic section canceled (got $fname)"
2299 );
2300 $this->transactionManager->setTransactionError( $trxError );
2301 }
2302 } finally {
2303 // Fix up callbacks owned by the sections that were just cancelled.
2304 // All callbacks should have an owner that is present in trxAtomicLevels.
2305 $this->transactionManager->modifyCallbacksForCancel(
2306 $excisedSectionIds,
2307 $newTopSectionId
2308 );
2309 }
2310
2311 $this->lastEmulatedAffectedRows = 0; // for the sake of consistency
2312
2313 $this->completeCriticalSection( __METHOD__, $cs );
2314
2315 if ( $runPostRollbackCallbacks ) {
2316 $this->runTransactionPostRollbackCallbacks();
2317 }
2318 }
2319
2321 final public function doAtomicSection(
2322 $fname,
2323 callable $callback,
2324 $cancelable = self::ATOMIC_NOT_CANCELABLE
2325 ) {
2326 $sectionId = $this->startAtomic( $fname, $cancelable );
2327 try {
2328 $res = $callback( $this, $fname );
2329 } catch ( Throwable $e ) {
2330 // Avoid confusing error reporting during critical section errors
2331 if ( !$this->csmError ) {
2332 $this->cancelAtomic( $fname, $sectionId );
2333 }
2334
2335 throw $e;
2336 }
2337 $this->endAtomic( $fname );
2338
2339 return $res;
2340 }
2341
2343 final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
2344 static $modes = [ self::TRANSACTION_EXPLICIT, self::TRANSACTION_INTERNAL ];
2345 if ( !in_array( $mode, $modes, true ) ) {
2346 throw new DBUnexpectedError( $this, "$fname: invalid mode parameter '$mode'" );
2347 }
2348
2349 $this->transactionManager->onBegin( $this, $fname );
2350
2351 if ( $this->flagsHolder->hasImplicitTrxFlag() && $mode !== self::TRANSACTION_INTERNAL ) {
2352 $msg = "$fname: implicit transaction expected (DBO_TRX set)";
2353 throw new DBUnexpectedError( $this, $msg );
2354 }
2355
2356 $this->assertHasConnectionHandle();
2357
2358 $cs = $this->commenceCriticalSection( __METHOD__ );
2359 $timeStart = microtime( true );
2360 try {
2361 $this->doBegin( $fname );
2362 } catch ( DBError $e ) {
2363 $this->completeCriticalSection( __METHOD__, $cs );
2364 throw $e;
2365 }
2366 $timeEnd = microtime( true );
2367 // Treat "BEGIN" as a trivial query to gauge the RTT delay
2368 $rtt = max( $timeEnd - $timeStart, 0.0 );
2369 $this->transactionManager->onBeginInCriticalSection( $mode, $fname, $rtt );
2370 $this->replicationReporter->resetReplicationLagStatus( $this );
2371 $this->completeCriticalSection( __METHOD__, $cs );
2372 }
2373
2381 protected function doBegin( $fname ) {
2382 $query = new Query( 'BEGIN', self::QUERY_CHANGE_TRX, 'BEGIN' );
2383 $this->query( $query, $fname );
2384 }
2385
2387 final public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2388 static $modes = [ self::FLUSHING_ONE, self::FLUSHING_ALL_PEERS, self::FLUSHING_INTERNAL ];
2389 if ( !in_array( $flush, $modes, true ) ) {
2390 throw new DBUnexpectedError( $this, "$fname: invalid flush parameter '$flush'" );
2391 }
2392
2393 if ( !$this->transactionManager->onCommit( $this, $fname, $flush ) ) {
2394 return;
2395 }
2396
2397 $this->assertHasConnectionHandle();
2398
2399 $this->runOnTransactionPreCommitCallbacks();
2400
2401 $cs = $this->commenceCriticalSection( __METHOD__ );
2402 try {
2403 if ( $this->trxLevel() ) {
2404 $query = new Query( 'COMMIT', self::QUERY_CHANGE_TRX, 'COMMIT' );
2405 $this->query( $query, $fname );
2406 }
2407 } catch ( DBError $e ) {
2408 $this->completeCriticalSection( __METHOD__, $cs );
2409 throw $e;
2410 }
2411 $lastWriteTime = $this->transactionManager->onCommitInCriticalSection( $this );
2412 if ( $lastWriteTime ) {
2413 $this->lastWriteTime = $lastWriteTime;
2414 }
2415 // With FLUSHING_ALL_PEERS, callbacks will run when requested by a dedicated phase
2416 // within LoadBalancer. With FLUSHING_INTERNAL, callbacks will run when requested by
2417 // the Database caller during a safe point. This avoids isolation and recursion issues.
2418 if ( $flush === self::FLUSHING_ONE ) {
2419 $this->runTransactionPostCommitCallbacks();
2420 }
2421 $this->completeCriticalSection( __METHOD__, $cs );
2422 }
2423
2425 final public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2426 if (
2427 $flush !== self::FLUSHING_INTERNAL &&
2428 $flush !== self::FLUSHING_ALL_PEERS &&
2429 $this->flagsHolder->hasImplicitTrxFlag()
2430 ) {
2431 throw new DBUnexpectedError(
2432 $this,
2433 "$fname: Expected mass rollback of all peer transactions (DBO_TRX set)"
2434 );
2435 }
2436
2437 if ( !$this->trxLevel() ) {
2438 $this->transactionManager->setTrxStatusToNone();
2439 $this->transactionManager->clearPreEndCallbacks();
2440 if ( $this->transactionManager->trxLevel() === TransactionManager::STATUS_TRX_ERROR ) {
2441 $this->logger->info(
2442 "$fname: acknowledged server-side transaction loss on {db_server}",
2443 $this->getLogContext()
2444 );
2445 }
2446
2447 return;
2448 }
2449
2450 $this->assertHasConnectionHandle();
2451
2452 if ( $this->csmError ) {
2453 // Since the session state is corrupt, we cannot just rollback the transaction
2454 // while preserving the non-transaction session state. The handle will remain
2455 // marked as corrupt until flushSession() is called to reset the connection
2456 // and deal with any remaining callbacks.
2457 $this->logger->info(
2458 "$fname: acknowledged client-side transaction loss on {db_server}",
2459 $this->getLogContext()
2460 );
2461
2462 return;
2463 }
2464
2465 $cs = $this->commenceCriticalSection( __METHOD__ );
2466 if ( $this->trxLevel() ) {
2467 // Disconnects cause rollback anyway, so ignore those errors
2468 $query = new Query(
2469 $this->platform->rollbackSqlText(),
2470 self::QUERY_SILENCE_ERRORS | self::QUERY_CHANGE_TRX,
2471 'ROLLBACK'
2472 );
2473 $this->query( $query, $fname );
2474 }
2475 $this->transactionManager->onRollbackInCriticalSection( $this );
2476 // With FLUSHING_ALL_PEERS, callbacks will run when requested by a dedicated phase
2477 // within LoadBalancer. With FLUSHING_INTERNAL, callbacks will run when requested by
2478 // the Database caller during a safe point. This avoids isolation and recursion issues.
2479 if ( $flush === self::FLUSHING_ONE ) {
2480 $this->runTransactionPostRollbackCallbacks();
2481 }
2482 $this->completeCriticalSection( __METHOD__, $cs );
2483 }
2484
2489 public function setTransactionManager( TransactionManager $transactionManager ) {
2490 $this->transactionManager = $transactionManager;
2491 }
2492
2494 public function flushSession( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2495 if (
2496 $flush !== self::FLUSHING_INTERNAL &&
2497 $flush !== self::FLUSHING_ALL_PEERS &&
2498 $this->flagsHolder->hasImplicitTrxFlag()
2499 ) {
2500 throw new DBUnexpectedError(
2501 $this,
2502 "$fname: Expected mass flush of all peer connections (DBO_TRX set)"
2503 );
2504 }
2505
2506 if ( $this->csmError ) {
2507 // If a critical section error occurred, such as Excimer timeout exceptions raised
2508 // before a query response was marshalled, destroy the connection handle and reset
2509 // the session state tracking variables. The value of trxLevel() is irrelevant here,
2510 // and, in fact, might be 1 due to rollback() deferring critical section recovery.
2511 $this->logger->info(
2512 "$fname: acknowledged client-side session loss on {db_server}",
2513 $this->getLogContext()
2514 );
2515 $this->csmError = null;
2516 $this->csmFname = null;
2517 $this->replaceLostConnection( 2048, __METHOD__ );
2518
2519 return;
2520 }
2521
2522 if ( $this->trxLevel() ) {
2523 // Any existing transaction should have been rolled back already
2524 throw new DBUnexpectedError(
2525 $this,
2526 "$fname: transaction still in progress (not yet rolled back)"
2527 );
2528 }
2529
2530 if ( $this->transactionManager->sessionStatus() === TransactionManager::STATUS_SESS_ERROR ) {
2531 // If the session state was already lost due to either an unacknowledged session
2532 // state loss error (e.g. dropped connection) or an explicit connection close call,
2533 // then there is nothing to do here. Note that in such cases, even temporary tables
2534 // and server-side config variables are lost (invocation of this method is assumed
2535 // to imply that such losses are tolerable).
2536 $this->logger->info(
2537 "$fname: acknowledged server-side session loss on {db_server}",
2538 $this->getLogContext()
2539 );
2540 } elseif ( $this->isOpen() ) {
2541 // Connection handle exists; server-side session state must be flushed
2542 $this->doFlushSession( $fname );
2543 $this->sessionNamedLocks = [];
2544 }
2545
2546 $this->transactionManager->clearSessionError();
2547 }
2548
2557 protected function doFlushSession( $fname ) {
2558 // no-op
2559 }
2560
2562 public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
2563 $this->transactionManager->onFlushSnapshot(
2564 $this,
2565 $fname,
2566 $flush,
2567 $this->getTransactionRoundFname()
2568 );
2569 if (
2570 $this->transactionManager->sessionStatus() === TransactionManager::STATUS_SESS_ERROR ||
2571 $this->transactionManager->trxStatus() === TransactionManager::STATUS_TRX_ERROR
2572 ) {
2573 $this->rollback( $fname, self::FLUSHING_INTERNAL );
2574 } else {
2575 $this->commit( $fname, self::FLUSHING_INTERNAL );
2576 }
2577 }
2578
2581 $oldName,
2582 $newName,
2583 $temporary = false,
2584 $fname = __METHOD__
2585 ) {
2586 throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2587 }
2588
2590 public function listTables( $prefix = null, $fname = __METHOD__ ) {
2591 throw new RuntimeException( __METHOD__ . ' is not implemented in descendant class' );
2592 }
2593
2595 public function affectedRows() {
2596 $this->lastEmulatedAffectedRows ??= $this->lastQueryAffectedRows;
2597
2598 return $this->lastEmulatedAffectedRows;
2599 }
2600
2602 public function insertId() {
2603 if ( $this->lastEmulatedInsertId === null ) {
2604 // Guard against misuse of this method by checking affectedRows(). Note that calls
2605 // to insert() with "IGNORE" and calls to insertSelect() might not add any rows.
2606 if ( $this->affectedRows() ) {
2607 $this->lastEmulatedInsertId = $this->lastInsertId();
2608 } else {
2609 $this->lastEmulatedInsertId = 0;
2610 }
2611 }
2612
2613 return $this->lastEmulatedInsertId;
2614 }
2615
2629 abstract protected function lastInsertId();
2630
2632 public function ping() {
2633 if ( $this->isOpen() ) {
2634 // If the connection was recently used, assume that it is still good
2635 if ( ( microtime( true ) - $this->lastPing ) < self::PING_TTL ) {
2636 return true;
2637 }
2638 // Send a trivial query to test the connection, triggering an automatic
2639 // reconnection attempt if the connection was lost
2640 $query = new Query(
2641 self::PING_QUERY,
2642 self::QUERY_IGNORE_DBO_TRX | self::QUERY_SILENCE_ERRORS | self::QUERY_CHANGE_NONE,
2643 'SELECT'
2644 );
2645 $res = $this->query( $query, __METHOD__ );
2646 $ok = ( $res !== false );
2647 } else {
2648 // Try to re-establish a connection
2649 $ok = $this->replaceLostConnection( null, __METHOD__ );
2650 }
2651
2652 return $ok;
2653 }
2654
2662 protected function replaceLostConnection( $lastErrno, $fname ) {
2663 if ( $this->conn ) {
2664 $this->closeConnection();
2665 $this->conn = null;
2666 $this->handleSessionLossPreconnect();
2667 }
2668
2669 try {
2670 $this->open(
2671 $this->connectionParams[self::CONN_SERVER],
2672 $this->connectionParams[self::CONN_USER],
2673 $this->connectionParams[self::CONN_PASSWORD],
2674 $this->currentDomain->getDatabase(),
2675 $this->currentDomain->getSchema(),
2676 $this->tablePrefix()
2677 );
2678 $this->lastPing = microtime( true );
2679 $ok = true;
2680
2681 $this->logger->warning(
2682 $fname . ': lost connection to {db_server} with error {errno}; reconnected',
2683 $this->getLogContext( [
2684 'exception' => new RuntimeException(),
2685 'db_log_category' => 'connection',
2686 'errno' => $lastErrno
2687 ] )
2688 );
2689 } catch ( DBConnectionError $e ) {
2690 $ok = false;
2691
2692 $this->logger->error(
2693 $fname . ': lost connection to {db_server} with error {errno}; reconnection failed: {connect_msg}',
2694 $this->getLogContext( [
2695 'exception' => new RuntimeException(),
2696 'db_log_category' => 'connection',
2697 'errno' => $lastErrno,
2698 'connect_msg' => $e->getMessage()
2699 ] )
2700 );
2701 }
2702
2703 // Handle callbacks in trxEndCallbacks, e.g. onTransactionResolution().
2704 // If callback suppression is set then the array will remain unhandled.
2705 $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );
2706 // Handle callbacks in trxRecurringCallbacks, e.g. setTransactionListener().
2707 // If callback suppression is set then the array will remain unhandled.
2708 $this->runTransactionListenerCallbacks( self::TRIGGER_ROLLBACK );
2709
2710 return $ok;
2711 }
2712
2735 public static function getCacheSetOptions( ?IReadableDatabase ...$dbs ) {
2736 $res = [ 'lag' => 0, 'since' => INF, 'pending' => false ];
2737
2738 foreach ( $dbs as $db ) {
2739 if ( $db instanceof IReadableDatabase ) {
2740 $status = $db->getSessionLagStatus();
2741
2742 if ( $status['lag'] === false ) {
2743 $res['lag'] = false;
2744 } elseif ( $res['lag'] !== false ) {
2745 $res['lag'] = max( $res['lag'], $status['lag'] );
2746 }
2747 $res['since'] = min( $res['since'], $status['since'] );
2748 }
2749
2750 if ( $db instanceof IDatabaseForOwner ) {
2751 $res['pending'] = $res['pending'] ?: $db->writesPending();
2752 }
2753 }
2754
2755 return $res;
2756 }
2757
2759 public function encodeBlob( $b ) {
2760 return $b;
2761 }
2762
2764 public function decodeBlob( $b ) {
2765 if ( $b instanceof Blob ) {
2766 $b = $b->fetch();
2767 }
2768 return $b;
2769 }
2770
2771 public function setSessionOptions( array $options ) {
2772 }
2773
2775 public function sourceFile(
2776 $filename,
2777 ?callable $lineCallback = null,
2778 ?callable $resultCallback = null,
2779 $fname = false,
2780 ?callable $inputCallback = null
2781 ) {
2782 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
2783 $fp = @fopen( $filename, 'r' );
2784
2785 if ( $fp === false ) {
2786 throw new RuntimeException( "Could not open \"{$filename}\"" );
2787 }
2788
2789 if ( !$fname ) {
2790 $fname = __METHOD__ . "( $filename )";
2791 }
2792
2793 try {
2794 return $this->sourceStream(
2795 $fp,
2796 $lineCallback,
2797 $resultCallback,
2798 $fname,
2799 $inputCallback
2800 );
2801 } finally {
2802 fclose( $fp );
2803 }
2804 }
2805
2807 public function sourceStream(
2808 $fp,
2809 ?callable $lineCallback = null,
2810 ?callable $resultCallback = null,
2811 $fname = __METHOD__,
2812 ?callable $inputCallback = null
2813 ) {
2814 $delimiterReset = new ScopedCallback(
2815 function ( $delimiter ) {
2816 $this->delimiter = $delimiter;
2817 },
2818 [ $this->delimiter ]
2819 );
2820 $cmd = '';
2821
2822 while ( !feof( $fp ) ) {
2823 if ( $lineCallback ) {
2824 $lineCallback();
2825 }
2826
2827 $line = trim( fgets( $fp ) );
2828
2829 if ( $line == '' ) {
2830 continue;
2831 }
2832
2833 if ( $line[0] == '-' && $line[1] == '-' ) {
2834 continue;
2835 }
2836
2837 if ( $cmd != '' ) {
2838 $cmd .= ' ';
2839 }
2840
2841 $done = $this->streamStatementEnd( $cmd, $line );
2842
2843 $cmd .= "$line\n";
2844
2845 if ( $done || feof( $fp ) ) {
2846 $cmd = $this->platform->replaceVars( $cmd );
2847
2848 if ( $inputCallback ) {
2849 $callbackResult = $inputCallback( $cmd );
2850
2851 if ( is_string( $callbackResult ) || !$callbackResult ) {
2852 $cmd = $callbackResult;
2853 }
2854 }
2855
2856 if ( $cmd ) {
2857 $res = $this->query( $cmd, $fname );
2858
2859 if ( $resultCallback ) {
2860 $resultCallback( $res, $this );
2861 }
2862
2863 if ( $res === false ) {
2864 $err = $this->lastError();
2865
2866 return "Query \"{$cmd}\" failed with error code \"$err\".\n";
2867 }
2868 }
2869 $cmd = '';
2870 }
2871 }
2872
2873 ScopedCallback::consume( $delimiterReset );
2874 return true;
2875 }
2876
2884 public function streamStatementEnd( &$sql, &$newLine ) {
2885 if ( $this->delimiter ) {
2886 $prev = $newLine;
2887 $newLine = preg_replace(
2888 '/' . preg_quote( $this->delimiter, '/' ) . '$/',
2889 '',
2890 $newLine
2891 );
2892 if ( $newLine != $prev ) {
2893 return true;
2894 }
2895 }
2896
2897 return false;
2898 }
2899
2903 public function lock( $lockName, $method, $timeout = 5, $flags = 0 ) {
2904 $lockTsUnix = $this->doLock( $lockName, $method, $timeout );
2905 if ( $lockTsUnix !== null ) {
2906 $locked = true;
2907 $this->sessionNamedLocks[$lockName] = [
2908 'ts' => $lockTsUnix,
2909 'trxId' => $this->transactionManager->getTrxId()
2910 ];
2911 } else {
2912 $locked = false;
2913 $this->logger->info(
2914 __METHOD__ . ": failed to acquire lock '{lockname}'",
2915 [
2916 'lockname' => $lockName,
2917 'db_log_category' => 'locking'
2918 ]
2919 );
2920 }
2921
2922 return $this->flagsHolder::contains( $flags, self::LOCK_TIMESTAMP ) ? $lockTsUnix : $locked;
2923 }
2924
2934 protected function doLock( string $lockName, string $method, int $timeout ) {
2935 return microtime( true ); // not implemented
2936 }
2937
2941 public function unlock( $lockName, $method ) {
2942 if ( !isset( $this->sessionNamedLocks[$lockName] ) ) {
2943 $released = false;
2944 $this->logger->warning(
2945 __METHOD__ . ": trying to release unheld lock '$lockName'\n",
2946 [ 'db_log_category' => 'locking' ]
2947 );
2948 } else {
2949 $released = $this->doUnlock( $lockName, $method );
2950 if ( $released ) {
2951 unset( $this->sessionNamedLocks[$lockName] );
2952 } else {
2953 $this->logger->warning(
2954 __METHOD__ . ": failed to release lock '$lockName'\n",
2955 [ 'db_log_category' => 'locking' ]
2956 );
2957 }
2958 }
2959
2960 return $released;
2961 }
2962
2971 protected function doUnlock( string $lockName, string $method ) {
2972 return true; // not implemented
2973 }
2974
2976 #[\NoDiscard]
2977 public function getScopedLockAndFlush( $lockKey, $fname, $timeout ): ?ScopedCallback {
2978 $this->transactionManager->onGetScopedLockAndFlush( $this, $fname );
2979
2980 if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
2981 return null;
2982 }
2983
2984 $unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
2985 // Note that the callback can be reached due to an exception making the calling
2986 // function end early. If the transaction/session is in an error state, avoid log
2987 // spam and confusing replacement of an original DBError with one about unlock().
2988 // Unlock query will fail anyway; avoid possibly triggering errors in rollback()
2989 if (
2990 $this->transactionManager->sessionStatus() === TransactionManager::STATUS_SESS_ERROR ||
2991 $this->transactionManager->trxStatus() === TransactionManager::STATUS_TRX_ERROR
2992 ) {
2993 return;
2994 }
2995 if ( $this->trxLevel() ) {
2996 $this->onTransactionResolution(
2997 function () use ( $lockKey, $fname ) {
2998 $this->unlock( $lockKey, $fname );
2999 },
3000 $fname
3001 );
3002 } else {
3003 $this->unlock( $lockKey, $fname );
3004 }
3005 } );
3006
3007 $this->commit( $fname, self::FLUSHING_INTERNAL );
3008
3009 return $unlocker;
3010 }
3011
3013 public function dropTable( $table, $fname = __METHOD__ ) {
3014 if ( !$this->tableExists( $table, $fname ) ) {
3015 return false;
3016 }
3017
3018 $query = new Query(
3019 $this->platform->dropTableSqlText( $table ),
3020 self::QUERY_CHANGE_SCHEMA,
3021 'DROP',
3022 $table
3023 );
3024 $this->query( $query, $fname );
3025
3026 return true;
3027 }
3028
3030 public function truncateTable( $table, $fname = __METHOD__ ) {
3031 $sql = "TRUNCATE TABLE " . $this->tableName( $table );
3032 $query = new Query( $sql, self::QUERY_CHANGE_SCHEMA, 'TRUNCATE', $table );
3033 $this->query( $query, $fname );
3034 }
3035
3037 public function isReadOnly() {
3038 return ( $this->getReadOnlyReason() !== null );
3039 }
3040
3044 protected function getReadOnlyReason() {
3045 $reason = $this->replicationReporter->getTopologyBasedReadOnlyReason();
3046 if ( $reason ) {
3047 return $reason;
3048 }
3049
3050 $reason = $this->getLBInfo( self::LB_READ_ONLY_REASON );
3051 if ( is_string( $reason ) ) {
3052 return [ $reason, 'lb' ];
3053 }
3054
3055 return null;
3056 }
3057
3069 protected function getBindingHandle() {
3070 if ( !$this->conn ) {
3071 throw new DBUnexpectedError(
3072 $this,
3073 'DB connection was already closed or the connection dropped'
3074 );
3075 }
3076
3077 return $this->conn;
3078 }
3079
3116 protected function commenceCriticalSection( string $fname ) {
3117 if ( $this->csmError ) {
3118 throw new DBUnexpectedError(
3119 $this,
3120 "Cannot execute $fname critical section while session state is out of sync.\n\n" .
3121 $this->csmError->getMessage() . "\n" .
3122 $this->csmError->getTraceAsString()
3123 );
3124 }
3125
3126 if ( $this->csmId ) {
3127 $csm = null; // fold into the outer critical section
3128 } elseif ( $this->csProvider ) {
3129 $csm = $this->csProvider->scopedEnter(
3130 $fname,
3131 null, // emergency limit (default)
3132 null, // emergency callback (default)
3133 function () use ( $fname ) {
3134 // Mark a critical section as having been aborted by an error
3135 $e = new RuntimeException( "A critical section from {$fname} has failed" );
3136 $this->csmError = $e;
3137 $this->csmId = null;
3138 }
3139 );
3140 $this->csmId = $csm->getId();
3141 $this->csmFname = $fname;
3142 } else {
3143 $csm = null; // not supported
3144 }
3145
3146 return $csm;
3147 }
3148
3159 protected function completeCriticalSection(
3160 string $fname,
3161 ?CriticalSectionScope $csm,
3162 ?Throwable $trxError = null
3163 ) {
3164 if ( $csm !== null ) {
3165 if ( $this->csmId === null ) {
3166 throw new LogicException( "$fname critical section is not active" );
3167 } elseif ( $csm->getId() !== $this->csmId ) {
3168 throw new LogicException(
3169 "$fname critical section is not the active ({$this->csmFname}) one"
3170 );
3171 }
3172
3173 $csm->exit();
3174 $this->csmId = null;
3175 }
3176
3177 if ( $trxError ) {
3178 $this->transactionManager->setTransactionError( $trxError );
3179 }
3180 }
3181
3182 public function __toString() {
3183 $id = spl_object_id( $this );
3184
3185 $description = $this->getType() . ' object #' . $id;
3186 // phpcs:ignore MediaWiki.Usage.ForbiddenFunctions.is_resource
3187 if ( is_resource( $this->conn ) ) {
3188 $description .= ' (' . (string)$this->conn . ')'; // "resource id #<ID>"
3189 } elseif ( is_object( $this->conn ) ) {
3190 $handleId = spl_object_id( $this->conn );
3191 $description .= " (handle id #$handleId)";
3192 }
3193
3194 return $description;
3195 }
3196
3201 public function __clone() {
3202 $this->logger->warning(
3203 "Cloning " . static::class . " is not recommended; forking connection",
3204 [
3205 'exception' => new RuntimeException(),
3206 'db_log_category' => 'connection'
3207 ]
3208 );
3209
3210 if ( $this->isOpen() ) {
3211 // Open a new connection resource without messing with the old one
3212 $this->conn = null;
3213 $this->transactionManager->clearEndCallbacks();
3214 $this->handleSessionLossPreconnect(); // no trx or locks anymore
3215 $this->open(
3216 $this->connectionParams[self::CONN_SERVER],
3217 $this->connectionParams[self::CONN_USER],
3218 $this->connectionParams[self::CONN_PASSWORD],
3219 $this->currentDomain->getDatabase(),
3220 $this->currentDomain->getSchema(),
3221 $this->tablePrefix()
3222 );
3223 $this->lastPing = microtime( true );
3224 }
3225 }
3226
3233 public function __sleep(): never {
3234 throw new RuntimeException( 'Database serialization may cause problems, since ' .
3235 'the connection is not restored on wakeup' );
3236 }
3237
3241 public function __destruct() {
3242 if ( $this->transactionManager ) {
3243 // Tests mock this class and disable constructor.
3244 $this->transactionManager->onDestruct();
3245 }
3246
3247 $danglingWriters = $this->pendingWriteAndCallbackCallers();
3248 if ( $danglingWriters ) {
3249 $fnames = implode( ', ', $danglingWriters );
3250 trigger_error( "DB transaction writes or callbacks still pending ($fnames)" );
3251 }
3252
3253 if ( $this->conn ) {
3254 // Avoid connection leaks. Normally, resources close at script completion.
3255 // The connection might already be closed in PHP by now, so suppress warnings.
3256 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
3257 @$this->closeConnection();
3258 $this->conn = null;
3259 }
3260 }
3261
3262 /* Start of methods delegated to DatabaseFlags. Avoid using them outside of rdbms library */
3263
3265 public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
3266 $this->flagsHolder->setFlag( $flag, $remember );
3267 }
3268
3270 public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
3271 $this->flagsHolder->clearFlag( $flag, $remember );
3272 }
3273
3275 public function restoreFlags( $state = self::RESTORE_PRIOR ) {
3276 $this->flagsHolder->restoreFlags( $state );
3277 }
3278
3280 public function getFlag( $flag ) {
3281 return $this->flagsHolder->getFlag( $flag );
3282 }
3283
3284 /* End of methods delegated to DatabaseFlags. */
3285
3286 /* Start of methods delegated to TransactionManager. Avoid using them outside of rdbms library */
3287
3289 final public function trxLevel() {
3290 // FIXME: A lot of tests disable constructor leading to trx manager being
3291 // null and breaking, this is unacceptable but hopefully this should
3292 // happen less by moving these functions to the transaction manager class.
3293 if ( !$this->transactionManager ) {
3294 $this->transactionManager = new TransactionManager( new NullLogger() );
3295 }
3296 return $this->transactionManager->trxLevel();
3297 }
3298
3300 public function trxTimestamp() {
3301 return $this->transactionManager->trxTimestamp();
3302 }
3303
3305 public function trxStatus() {
3306 return $this->transactionManager->trxStatus();
3307 }
3308
3310 public function writesPending() {
3311 return $this->transactionManager->writesPending();
3312 }
3313
3315 public function writesOrCallbacksPending() {
3316 return $this->transactionManager->writesOrCallbacksPending();
3317 }
3318
3320 public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
3321 return $this->transactionManager->pendingWriteQueryDuration( $type );
3322 }
3323
3325 public function pendingWriteCallers() {
3326 if ( !$this->transactionManager ) {
3327 return [];
3328 }
3329 return $this->transactionManager->pendingWriteCallers();
3330 }
3331
3334 if ( !$this->transactionManager ) {
3335 return [];
3336 }
3337 return $this->transactionManager->pendingWriteAndCallbackCallers();
3338 }
3339
3342 return $this->transactionManager->runOnTransactionPreCommitCallbacks();
3343 }
3344
3346 public function explicitTrxActive() {
3347 return $this->transactionManager->explicitTrxActive();
3348 }
3349
3350 /* End of methods delegated to TransactionManager. */
3351
3352 /* Start of methods delegated to SQLPlatform. Avoid using them outside of rdbms library */
3353
3355 public function implicitOrderby() {
3356 return $this->platform->implicitOrderby();
3357 }
3358
3360 public function selectSQLText(
3361 $tables, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
3362 ) {
3363 return $this->platform->selectSQLText( $tables, $vars, $conds, $fname, $options, $join_conds );
3364 }
3365
3367 public function buildComparison( string $op, array $conds ): string {
3368 return $this->platform->buildComparison( $op, $conds );
3369 }
3370
3372 public function makeList( array $a, $mode = self::LIST_COMMA ) {
3373 return $this->platform->makeList( $a, $mode );
3374 }
3375
3377 public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
3378 return $this->platform->makeWhereFrom2d( $data, $baseKey, $subKey );
3379 }
3380
3382 public function factorConds( $condsArray ) {
3383 return $this->platform->factorConds( $condsArray );
3384 }
3385
3387 public function bitNot( $field ) {
3388 return $this->platform->bitNot( $field );
3389 }
3390
3392 public function bitAnd( $fieldLeft, $fieldRight ) {
3393 return $this->platform->bitAnd( $fieldLeft, $fieldRight );
3394 }
3395
3397 public function bitOr( $fieldLeft, $fieldRight ) {
3398 return $this->platform->bitOr( $fieldLeft, $fieldRight );
3399 }
3400
3402 public function buildConcat( $stringList ) {
3403 return $this->platform->buildConcat( $stringList );
3404 }
3405
3407 public function buildGroupConcat( $field, $delim ): string {
3408 return $this->platform->buildGroupConcat( $field, $delim );
3409 }
3410
3412 public function buildGreatest( $fields, $values ) {
3413 return $this->platform->buildGreatest( $fields, $values );
3414 }
3415
3417 public function buildLeast( $fields, $values ) {
3418 return $this->platform->buildLeast( $fields, $values );
3419 }
3420
3422 public function buildSubstring( $input, $startPosition, $length = null ) {
3423 return $this->platform->buildSubstring( $input, $startPosition, $length );
3424 }
3425
3427 public function buildStringCast( $field ) {
3428 return $this->platform->buildStringCast( $field );
3429 }
3430
3432 public function buildIntegerCast( $field ) {
3433 return $this->platform->buildIntegerCast( $field );
3434 }
3435
3437 public function tableName( string $name, $format = 'quoted' ) {
3438 return $this->platform->tableName( $name, $format );
3439 }
3440
3442 public function tableNamesN( ...$tables ) {
3443 return $this->platform->tableNamesN( ...$tables );
3444 }
3445
3447 public function addIdentifierQuotes( $s ) {
3448 return $this->platform->addIdentifierQuotes( $s );
3449 }
3450
3452 public function isQuotedIdentifier( $name ) {
3453 return $this->platform->isQuotedIdentifier( $name );
3454 }
3455
3457 public function buildLike( $param, ...$params ) {
3458 return $this->platform->buildLike( $param, ...$params );
3459 }
3460
3462 public function anyChar() {
3463 return $this->platform->anyChar();
3464 }
3465
3467 public function anyString() {
3468 return $this->platform->anyString();
3469 }
3470
3472 public function limitResult( $sql, $limit, $offset = false ) {
3473 return $this->platform->limitResult( $sql, $limit, $offset );
3474 }
3475
3477 public function unionSupportsOrderAndLimit() {
3478 return $this->platform->unionSupportsOrderAndLimit();
3479 }
3480
3482 public function unionQueries( $sqls, $all, $options = [] ) {
3483 return $this->platform->unionQueries( $sqls, $all, $options );
3484 }
3485
3487 public function conditional( $cond, $caseTrueExpression, $caseFalseExpression ) {
3488 return $this->platform->conditional( $cond, $caseTrueExpression, $caseFalseExpression );
3489 }
3490
3492 public function strreplace( $orig, $old, $new ) {
3493 return $this->platform->strreplace( $orig, $old, $new );
3494 }
3495
3497 public function timestamp( $ts = 0 ) {
3498 return $this->platform->timestamp( $ts );
3499 }
3500
3502 public function timestampOrNull( $ts = null ) {
3503 return $this->platform->timestampOrNull( $ts );
3504 }
3505
3507 public function getInfinity() {
3508 return $this->platform->getInfinity();
3509 }
3510
3512 public function encodeExpiry( $expiry ) {
3513 return $this->platform->encodeExpiry( $expiry );
3514 }
3515
3517 public function decodeExpiry( $expiry, $format = TS::MW ) {
3518 return $this->platform->decodeExpiry( $expiry, $format );
3519 }
3520
3522 public function setTableAliases( array $aliases ) {
3523 $this->platform->setTableAliases( $aliases );
3524 }
3525
3527 public function getTableAliases() {
3528 return $this->platform->getTableAliases();
3529 }
3530
3532 public function buildGroupConcatField(
3533 $delim, $tables, $field, $conds = '', $join_conds = []
3534 ) {
3535 return $this->platform->buildGroupConcatField( $delim, $tables, $field, $conds, $join_conds );
3536 }
3537
3539 public function buildSelectSubquery(
3540 $tables, $vars, $conds = '', $fname = __METHOD__,
3541 $options = [], $join_conds = []
3542 ) {
3543 return $this->platform->buildSelectSubquery( $tables, $vars, $conds, $fname, $options, $join_conds );
3544 }
3545
3547 public function buildExcludedValue( $column ) {
3548 return $this->platform->buildExcludedValue( $column );
3549 }
3550
3552 public function setSchemaVars( $vars ) {
3553 $this->platform->setSchemaVars( $vars );
3554 }
3555
3556 /* End of methods delegated to SQLPlatform. */
3557
3558 /* Start of methods delegated to ReplicationReporter. */
3559
3561 public function primaryPosWait( DBPrimaryPos $pos, $timeout ) {
3562 return $this->replicationReporter->primaryPosWait( $this, $pos, $timeout );
3563 }
3564
3566 public function getPrimaryPos() {
3567 return $this->replicationReporter->getPrimaryPos( $this );
3568 }
3569
3571 public function getLag() {
3572 return $this->replicationReporter->getLag( $this );
3573 }
3574
3576 public function getSessionLagStatus() {
3577 return $this->replicationReporter->getSessionLagStatus( $this );
3578 }
3579
3580 /* End of methods delegated to ReplicationReporter. */
3581}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
Representing a group of expressions chained via AND.
Class used for token representing identifiers for atomic sections from IDatabase instances.
static recordQuery(DatabaseDomain $domain, Query $query)
When tracking is enabled and a query alters tables, record the list of tables that are altered.
Database error base class.
Definition DBError.php:22
Exception class for attempted DB write access to a DBConnRef with the DB_REPLICA role.
Class to handle database/schema/prefix specifications for IDatabase.
A single concrete connection to a relational database.
Definition Database.php:38
getPrimaryKeyColumns( $table, $fname=__METHOD__)
Get the primary key columns of a table.to be used by updater onlystring[] query}
bool $cliMode
Whether this PHP instance is for a CLI script.
Definition Database.php:66
getServerInfo()
Get a human-readable string describing the current software version.Use getServerVersion() to get mac...
Definition Database.php: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, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 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'=>[], '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',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], '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, 'PHPSessionHandling'=> 'warn', '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',],],], '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, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, 'createwithcontentmodel' => 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' => [ ], '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, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'BotPasswordsLimit' => 100, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, '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, 'CachePrefix' => 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: ], '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, ], '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' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'GenerateReqIDFormat' => 'rand24', 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => true, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], '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', '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', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', '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', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'BotPasswordsLimit' => 'integer', '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', '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', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'GenerateReqIDFormat' => 'string', 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'file' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], ], ], '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