3 namespace Wikimedia\Rdbms
;
5 use InvalidArgumentException
;
9 * Helper class used for automatically re-using IDatabase connections and lazily
10 * establishing the actual network connection to a database host.
12 * It does this by deferring to ILoadBalancer::getConnectionInternal, which in
13 * turn ensures we share and re-use a single connection for a given database
16 * This class previously used an RAII-style pattern where connections would be
17 * claimed from a pool, and then added back to the pool for re-use only after
18 * the calling code's variable for this object went out of scope (a __destruct
19 * got called when the calling function returns or throws). This is no longer
20 * needed today as LoadBalancer now permits re-use internally even for
21 * overlapping callers, where two pieces of code may both obtain their own
22 * DBConnRef object and where both are used alternatingly, and yet still share
23 * the same connection.
27 * function getRowData() {
28 * $conn = $this->lb->getConnection( DB_REPLICA );
29 * $row = $conn->select( ... );
30 * return $row ? (array)$row : false;
37 class DBConnRef
implements Stringable
, IMaintainableDatabase
, IDatabaseForOwner
{
38 /** @var ILoadBalancer */
40 /** @var Database|null Live connection handle */
43 * @var array Map of (DBConnRef::FLD_* constant => connection parameter)
44 * @phan-var array{0:int,1:array|string|false,2:DatabaseDomain,3:int}
47 /** @var int One of DB_PRIMARY/DB_REPLICA */
51 * @var int Reference to the $modcount passed to the constructor.
52 * $conn is valid if $modCountRef and $modCountFix are the same.
57 * @var int Last known good value of $modCountRef
58 * $conn is valid if $modCountRef and $modCountFix are the same.
62 private const FLD_INDEX
= 0;
63 private const FLD_GROUPS
= 1;
64 private const FLD_DOMAIN
= 2;
65 private const FLD_FLAGS
= 3;
68 * @internal May not be used outside Rdbms LoadBalancer
69 * @param ILoadBalancer $lb Connection manager for $conn
70 * @param array $params [server index, query groups, domain, flags]
71 * @param int $role The type of connection asked for; one of DB_PRIMARY/DB_REPLICA
72 * @param null|int &$modcount Reference to a modification counter. This is for
73 * LoadBalancer::reconfigure to indicate that a new connection should be acquired.
75 public function __construct( ILoadBalancer
$lb, $params, $role, &$modcount = 0 ) {
76 if ( !is_array( $params ) ||
count( $params ) < 4 ||
$params[self
::FLD_DOMAIN
] === false ) {
77 throw new InvalidArgumentException( "Missing lazy connection arguments." );
80 $params[self
::FLD_DOMAIN
] = DatabaseDomain
::newFromId( $params[self
::FLD_DOMAIN
] );
83 $this->params
= $params;
86 // $this->conn is valid as long as $this->modCountRef and $this->modCountFix are the same.
87 $this->modCountRef
= &$modcount; // remember reference
88 $this->modCountFix
= $modcount; // remember current value
92 * Connect to the database if we are not already connected.
94 public function ensureConnection() {
95 if ( $this->modCountFix
!== $this->modCountRef
) {
96 // Discard existing connection, unless we are in an ongoing transaction.
97 // This is triggered by LoadBalancer::reconfigure(), to allow changed settings
98 // to take effect. The primary use case are replica servers being taken out of
99 // rotation, or the primary database changing.
100 if ( $this->conn
&& !$this->conn
->trxLevel() ) {
101 $this->conn
->close();
106 if ( $this->conn
=== null ) {
107 $this->conn
= $this->lb
->getConnectionInternal(
108 $this->params
[self
::FLD_INDEX
],
109 $this->params
[self
::FLD_GROUPS
],
110 $this->params
[self
::FLD_DOMAIN
]->getId(),
111 $this->params
[self
::FLD_FLAGS
]
113 $this->modCountFix
= $this->modCountRef
;
116 if ( !$this->params
[self
::FLD_DOMAIN
]->equals( $this->conn
->getDomainID() ) ) {
117 // The underlying connection handle is likely being shared by other DBConnRef
118 // instances in a load balancer. Make sure that each one routes queries by their
119 // owner function to the domain that the owner expects.
120 $this->conn
->selectDomain( $this->params
[self
::FLD_DOMAIN
] );
124 public function __call( $name, array $arguments ) {
125 $this->ensureConnection();
127 return $this->conn
->$name( ...$arguments );
131 * @return int DB_PRIMARY when this *requires* the primary DB, otherwise DB_REPLICA
134 public function getReferenceRole() {
138 public function getServerInfo() {
139 return $this->__call( __FUNCTION__
, func_get_args() );
142 public function trxLevel() {
143 return $this->__call( __FUNCTION__
, func_get_args() );
146 public function trxTimestamp() {
147 return $this->__call( __FUNCTION__
, func_get_args() );
150 public function explicitTrxActive() {
151 return $this->__call( __FUNCTION__
, func_get_args() );
154 public function tablePrefix( $prefix = null ) {
155 if ( $prefix !== null ) {
156 // Disallow things that might confuse the LoadBalancer tracking
157 throw $this->getDomainChangeException();
160 if ( $this->conn
=== null ) {
161 // Avoid triggering a database connection
162 $prefix = $this->params
[self
::FLD_DOMAIN
]->getTablePrefix();
164 // This will just return the prefix
165 $prefix = $this->__call( __FUNCTION__
, func_get_args() );
171 public function dbSchema( $schema = null ) {
172 if ( $schema !== null ) {
173 // Disallow things that might confuse the LoadBalancer tracking
174 throw $this->getDomainChangeException();
177 if ( $this->conn
=== null ) {
178 // Avoid triggering a database connection
179 $schema = (string)( $this->params
[self
::FLD_DOMAIN
]->getSchema() );
181 // This will just return the schema
182 $schema = $this->__call( __FUNCTION__
, func_get_args() );
188 public function getLBInfo( $name = null ) {
189 return $this->__call( __FUNCTION__
, func_get_args() );
192 public function setLBInfo( $nameOrArray, $value = null ) {
193 // @phan-suppress-previous-line PhanPluginNeverReturnMethod
194 // Disallow things that might confuse the LoadBalancer tracking
195 throw $this->getDomainChangeException();
198 public function implicitOrderby() {
199 return $this->__call( __FUNCTION__
, func_get_args() );
202 public function lastDoneWrites() {
203 return $this->__call( __FUNCTION__
, func_get_args() );
206 public function writesPending() {
207 return $this->__call( __FUNCTION__
, func_get_args() );
210 public function writesOrCallbacksPending() {
211 return $this->__call( __FUNCTION__
, func_get_args() );
214 public function pendingWriteQueryDuration( $type = self
::ESTIMATE_TOTAL
) {
215 return $this->__call( __FUNCTION__
, func_get_args() );
218 public function pendingWriteCallers() {
219 return $this->__call( __FUNCTION__
, func_get_args() );
222 public function isOpen() {
223 return $this->__call( __FUNCTION__
, func_get_args() );
226 public function setFlag( $flag, $remember = self
::REMEMBER_NOTHING
) {
227 return $this->__call( __FUNCTION__
, func_get_args() );
230 public function clearFlag( $flag, $remember = self
::REMEMBER_NOTHING
) {
231 return $this->__call( __FUNCTION__
, func_get_args() );
234 public function restoreFlags( $state = self
::RESTORE_PRIOR
) {
235 return $this->__call( __FUNCTION__
, func_get_args() );
238 public function getFlag( $flag ) {
239 return $this->__call( __FUNCTION__
, func_get_args() );
242 public function getProperty( $name ) {
243 return $this->__call( __FUNCTION__
, func_get_args() );
246 public function getDomainID() {
247 if ( $this->conn
=== null ) {
248 // Avoid triggering a database connection
249 return $this->params
[self
::FLD_DOMAIN
]->getId();
252 return $this->__call( __FUNCTION__
, func_get_args() );
255 public function getType() {
256 if ( $this->conn
=== null ) {
257 // Avoid triggering a database connection
258 $index = $this->normalizeServerIndex( $this->params
[self
::FLD_INDEX
] );
260 // In theory, if $index is DB_REPLICA, the type could vary
261 return $this->lb
->getServerType( $index );
265 return $this->__call( __FUNCTION__
, func_get_args() );
268 public function insertId() {
269 return $this->__call( __FUNCTION__
, func_get_args() );
272 public function lastErrno() {
273 return $this->__call( __FUNCTION__
, func_get_args() );
276 public function lastError() {
277 return $this->__call( __FUNCTION__
, func_get_args() );
280 public function affectedRows() {
281 return $this->__call( __FUNCTION__
, func_get_args() );
284 public function getSoftwareLink() {
285 return $this->__call( __FUNCTION__
, func_get_args() );
288 public function getServerVersion() {
289 return $this->__call( __FUNCTION__
, func_get_args() );
292 public function close( $fname = __METHOD__
) {
293 // @phan-suppress-previous-line PhanPluginNeverReturnMethod
294 throw new DBUnexpectedError( $this->conn
, 'Cannot close shared connection.' );
297 public function query( $sql, $fname = __METHOD__
, $flags = 0 ) {
298 if ( $this->role
!== ILoadBalancer
::DB_PRIMARY
) {
299 $flags |
= IDatabase
::QUERY_REPLICA_ROLE
;
302 return $this->__call( __FUNCTION__
, [ $sql, $fname, $flags ] );
305 public function newSelectQueryBuilder(): SelectQueryBuilder
{
306 // Use $this not $this->conn so that the domain is preserved (T326377)
307 return new SelectQueryBuilder( $this );
310 public function newUnionQueryBuilder(): UnionQueryBuilder
{
311 // Use $this not $this->conn so that the domain is preserved (T326377)
312 return new UnionQueryBuilder( $this );
315 public function newUpdateQueryBuilder(): UpdateQueryBuilder
{
316 // Use $this not $this->conn so that the domain is preserved (T326377)
317 return new UpdateQueryBuilder( $this );
320 public function newDeleteQueryBuilder(): DeleteQueryBuilder
{
321 // Use $this not $this->conn so that the domain is preserved (T326377)
322 return new DeleteQueryBuilder( $this );
325 public function newInsertQueryBuilder(): InsertQueryBuilder
{
326 // Use $this not $this->conn so that the domain is preserved (T326377)
327 return new InsertQueryBuilder( $this );
330 public function newReplaceQueryBuilder(): ReplaceQueryBuilder
{
331 // Use $this not $this->conn so that the domain is preserved (T326377)
332 return new ReplaceQueryBuilder( $this );
335 public function selectField(
336 $tables, $var, $cond = '', $fname = __METHOD__
, $options = [], $join_conds = []
338 return $this->__call( __FUNCTION__
, func_get_args() );
341 public function selectFieldValues(
342 $tables, $var, $cond = '', $fname = __METHOD__
, $options = [], $join_conds = []
344 return $this->__call( __FUNCTION__
, func_get_args() );
347 public function select(
348 $tables, $vars, $conds = '', $fname = __METHOD__
,
349 $options = [], $join_conds = []
351 return $this->__call( __FUNCTION__
, func_get_args() );
354 public function selectSQLText(
355 $tables, $vars, $conds = '', $fname = __METHOD__
,
356 $options = [], $join_conds = []
358 return $this->__call( __FUNCTION__
, func_get_args() );
361 public function limitResult( $sql, $limit, $offset = false ) {
362 return $this->__call( __FUNCTION__
, func_get_args() );
365 public function selectRow(
366 $tables, $vars, $conds, $fname = __METHOD__
,
367 $options = [], $join_conds = []
369 return $this->__call( __FUNCTION__
, func_get_args() );
372 public function estimateRowCount(
373 $tables, $vars = '*', $conds = '', $fname = __METHOD__
, $options = [], $join_conds = []
375 return $this->__call( __FUNCTION__
, func_get_args() );
378 public function selectRowCount(
379 $tables, $vars = '*', $conds = '', $fname = __METHOD__
, $options = [], $join_conds = []
381 return $this->__call( __FUNCTION__
, func_get_args() );
384 public function lockForUpdate(
385 $table, $conds = '', $fname = __METHOD__
, $options = [], $join_conds = []
387 $this->assertRoleAllowsWrites();
389 return $this->__call( __FUNCTION__
, func_get_args() );
392 public function fieldExists( $table, $field, $fname = __METHOD__
) {
393 return $this->__call( __FUNCTION__
, func_get_args() );
396 public function indexExists( $table, $index, $fname = __METHOD__
) {
397 return $this->__call( __FUNCTION__
, func_get_args() );
400 public function tableExists( $table, $fname = __METHOD__
) {
401 return $this->__call( __FUNCTION__
, func_get_args() );
404 public function insert( $table, $rows, $fname = __METHOD__
, $options = [] ) {
405 $this->assertRoleAllowsWrites();
407 return $this->__call( __FUNCTION__
, func_get_args() );
410 public function update( $table, $set, $conds, $fname = __METHOD__
, $options = [] ) {
411 $this->assertRoleAllowsWrites();
413 return $this->__call( __FUNCTION__
, func_get_args() );
416 public function buildComparison( string $op, array $conds ): string {
417 return $this->__call( __FUNCTION__
, func_get_args() );
420 public function makeList( array $a, $mode = self
::LIST_COMMA
) {
421 return $this->__call( __FUNCTION__
, func_get_args() );
424 public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
425 return $this->__call( __FUNCTION__
, func_get_args() );
428 public function factorConds( $condsArray ) {
429 return $this->__call( __FUNCTION__
, func_get_args() );
432 public function bitNot( $field ) {
433 return $this->__call( __FUNCTION__
, func_get_args() );
436 public function bitAnd( $fieldLeft, $fieldRight ) {
437 return $this->__call( __FUNCTION__
, func_get_args() );
440 public function bitOr( $fieldLeft, $fieldRight ) {
441 return $this->__call( __FUNCTION__
, func_get_args() );
444 public function buildConcat( $stringList ) {
445 return $this->__call( __FUNCTION__
, func_get_args() );
448 public function buildGroupConcatField(
449 $delim, $tables, $field, $conds = '', $join_conds = []
451 return $this->__call( __FUNCTION__
, func_get_args() );
454 public function buildGreatest( $fields, $values ) {
455 return $this->__call( __FUNCTION__
, func_get_args() );
458 public function buildLeast( $fields, $values ) {
459 return $this->__call( __FUNCTION__
, func_get_args() );
462 public function buildSubstring( $input, $startPosition, $length = null ) {
463 return $this->__call( __FUNCTION__
, func_get_args() );
466 public function buildStringCast( $field ) {
467 return $this->__call( __FUNCTION__
, func_get_args() );
470 public function buildIntegerCast( $field ) {
471 return $this->__call( __FUNCTION__
, func_get_args() );
474 public function buildExcludedValue( $column ) {
475 return $this->__call( __FUNCTION__
, func_get_args() );
478 public function buildSelectSubquery(
479 $tables, $vars, $conds = '', $fname = __METHOD__
,
480 $options = [], $join_conds = []
482 return $this->__call( __FUNCTION__
, func_get_args() );
485 public function databasesAreIndependent() {
486 return $this->__call( __FUNCTION__
, func_get_args() );
489 public function selectDomain( $domain ) {
490 // @phan-suppress-previous-line PhanPluginNeverReturnMethod
491 // Disallow things that might confuse the LoadBalancer tracking
492 throw $this->getDomainChangeException();
495 public function getDBname() {
496 if ( $this->conn
=== null ) {
497 // Avoid triggering a database connection
498 return $this->params
[self
::FLD_DOMAIN
]->getDatabase();
501 return $this->__call( __FUNCTION__
, func_get_args() );
504 public function getServer() {
505 return $this->__call( __FUNCTION__
, func_get_args() );
508 public function getServerName() {
509 if ( $this->conn
=== null ) {
510 // Avoid triggering a database connection
511 $index = $this->normalizeServerIndex( $this->params
[self
::FLD_INDEX
] );
513 // If $index is DB_REPLICA, the server name could vary
514 return $this->lb
->getServerName( $index );
518 return $this->__call( __FUNCTION__
, func_get_args() );
521 public function addQuotes( $s ) {
522 return $this->__call( __FUNCTION__
, func_get_args() );
525 public function expr( string $field, string $op, $value ): Expression
{
526 // Does not use __call here to delay creating the db connection
527 return new Expression( $field, $op, $value );
530 public function andExpr( array $conds ): AndExpressionGroup
{
531 // Does not use __call here to delay creating the db connection
532 return AndExpressionGroup
::newFromArray( $conds );
535 public function orExpr( array $conds ): OrExpressionGroup
{
536 // Does not use __call here to delay creating the db connection
537 return OrExpressionGroup
::newFromArray( $conds );
540 public function addIdentifierQuotes( $s ) {
541 return $this->__call( __FUNCTION__
, func_get_args() );
544 public function buildLike( $param, ...$params ) {
545 return $this->__call( __FUNCTION__
, func_get_args() );
548 public function anyChar() {
549 return $this->__call( __FUNCTION__
, func_get_args() );
552 public function anyString() {
553 return $this->__call( __FUNCTION__
, func_get_args() );
556 public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__
) {
557 $this->assertRoleAllowsWrites();
559 return $this->__call( __FUNCTION__
, func_get_args() );
562 public function upsert(
563 $table, array $rows, $uniqueKeys, array $set, $fname = __METHOD__
565 $this->assertRoleAllowsWrites();
567 return $this->__call( __FUNCTION__
, func_get_args() );
570 public function deleteJoin(
571 $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
573 $this->assertRoleAllowsWrites();
575 $this->__call( __FUNCTION__
, func_get_args() );
578 public function delete( $table, $conds, $fname = __METHOD__
) {
579 $this->assertRoleAllowsWrites();
581 return $this->__call( __FUNCTION__
, func_get_args() );
584 public function insertSelect(
585 $destTable, $srcTable, $varMap, $conds,
586 $fname = __METHOD__
, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
588 $this->assertRoleAllowsWrites();
590 return $this->__call( __FUNCTION__
, func_get_args() );
593 public function unionSupportsOrderAndLimit() {
594 return $this->__call( __FUNCTION__
, func_get_args() );
597 public function unionQueries( $sqls, $all, $options = [] ) {
598 return $this->__call( __FUNCTION__
, func_get_args() );
601 public function conditional( $cond, $caseTrueExpression, $caseFalseExpression ) {
602 return $this->__call( __FUNCTION__
, func_get_args() );
605 public function strreplace( $orig, $old, $new ) {
606 return $this->__call( __FUNCTION__
, func_get_args() );
609 public function primaryPosWait( DBPrimaryPos
$pos, $timeout ) {
610 return $this->__call( __FUNCTION__
, func_get_args() );
613 public function getPrimaryPos() {
614 return $this->__call( __FUNCTION__
, func_get_args() );
617 public function serverIsReadOnly() {
618 return $this->__call( __FUNCTION__
, func_get_args() );
621 public function onTransactionResolution( callable
$callback, $fname = __METHOD__
) {
622 // DB_REPLICA role: caller might want to refresh cache after a REPEATABLE-READ snapshot
623 return $this->__call( __FUNCTION__
, func_get_args() );
626 public function onTransactionCommitOrIdle( callable
$callback, $fname = __METHOD__
) {
627 // DB_REPLICA role: caller might want to refresh cache after a REPEATABLE-READ snapshot
628 return $this->__call( __FUNCTION__
, func_get_args() );
631 public function onTransactionPreCommitOrIdle( callable
$callback, $fname = __METHOD__
) {
632 // DB_REPLICA role: caller might want to refresh cache after a cache mutex is released
633 return $this->__call( __FUNCTION__
, func_get_args() );
636 public function setTransactionListener( $name, ?callable
$callback = null ) {
637 return $this->__call( __FUNCTION__
, func_get_args() );
640 public function startAtomic(
641 $fname = __METHOD__
, $cancelable = IDatabase
::ATOMIC_NOT_CANCELABLE
643 // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
644 return $this->__call( __FUNCTION__
, func_get_args() );
647 public function endAtomic( $fname = __METHOD__
) {
648 // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
649 return $this->__call( __FUNCTION__
, func_get_args() );
652 public function cancelAtomic( $fname = __METHOD__
, ?AtomicSectionIdentifier
$sectionId = null ) {
653 // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
654 return $this->__call( __FUNCTION__
, func_get_args() );
657 public function doAtomicSection(
658 $fname, callable
$callback, $cancelable = self
::ATOMIC_NOT_CANCELABLE
660 // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
661 return $this->__call( __FUNCTION__
, func_get_args() );
664 public function begin( $fname = __METHOD__
, $mode = IDatabase
::TRANSACTION_EXPLICIT
) {
665 return $this->__call( __FUNCTION__
, func_get_args() );
668 public function commit( $fname = __METHOD__
, $flush = self
::FLUSHING_ONE
) {
669 return $this->__call( __FUNCTION__
, func_get_args() );
672 public function rollback( $fname = __METHOD__
, $flush = self
::FLUSHING_ONE
) {
673 return $this->__call( __FUNCTION__
, func_get_args() );
676 public function flushSession( $fname = __METHOD__
, $flush = self
::FLUSHING_ONE
) {
677 return $this->__call( __FUNCTION__
, func_get_args() );
680 public function flushSnapshot( $fname = __METHOD__
, $flush = self
::FLUSHING_ONE
) {
681 return $this->__call( __FUNCTION__
, func_get_args() );
684 public function timestamp( $ts = 0 ) {
685 return $this->__call( __FUNCTION__
, func_get_args() );
688 public function timestampOrNull( $ts = null ) {
689 return $this->__call( __FUNCTION__
, func_get_args() );
692 public function ping( &$rtt = null ) {
693 return func_num_args()
694 ?
$this->__call( __FUNCTION__
, [ &$rtt ] )
695 : $this->__call( __FUNCTION__
, [] ); // method cares about null vs missing
698 public function getLag() {
699 return $this->__call( __FUNCTION__
, func_get_args() );
702 public function getSessionLagStatus() {
703 return $this->__call( __FUNCTION__
, func_get_args() );
706 public function encodeBlob( $b ) {
707 return $this->__call( __FUNCTION__
, func_get_args() );
710 public function decodeBlob( $b ) {
711 return $this->__call( __FUNCTION__
, func_get_args() );
714 public function setSessionOptions( array $options ) {
715 $this->__call( __FUNCTION__
, func_get_args() );
718 public function setSchemaVars( $vars ) {
719 return $this->__call( __FUNCTION__
, func_get_args() );
722 public function lockIsFree( $lockName, $method ) {
723 $this->assertRoleAllowsWrites();
725 return $this->__call( __FUNCTION__
, func_get_args() );
728 public function lock( $lockName, $method, $timeout = 5, $flags = 0 ) {
729 $this->assertRoleAllowsWrites();
731 return $this->__call( __FUNCTION__
, func_get_args() );
734 public function unlock( $lockName, $method ) {
735 $this->assertRoleAllowsWrites();
737 return $this->__call( __FUNCTION__
, func_get_args() );
740 public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
741 $this->assertRoleAllowsWrites();
743 return $this->__call( __FUNCTION__
, func_get_args() );
746 public function getInfinity() {
747 return $this->__call( __FUNCTION__
, func_get_args() );
750 public function encodeExpiry( $expiry ) {
751 return $this->__call( __FUNCTION__
, func_get_args() );
754 public function decodeExpiry( $expiry, $format = TS_MW
) {
755 return $this->__call( __FUNCTION__
, func_get_args() );
758 public function isReadOnly() {
759 return $this->__call( __FUNCTION__
, func_get_args() );
762 public function setTableAliases( array $aliases ) {
763 return $this->__call( __FUNCTION__
, func_get_args() );
766 public function getTableAliases() {
767 return $this->__call( __FUNCTION__
, func_get_args() );
770 public function setIndexAliases( array $aliases ) {
771 return $this->__call( __FUNCTION__
, func_get_args() );
774 public function tableName( $name, $format = 'quoted' ) {
775 return $this->__call( __FUNCTION__
, func_get_args() );
778 public function tableNames( ...$tables ) {
779 return $this->__call( __FUNCTION__
, func_get_args() );
782 public function tableNamesN( ...$tables ) {
783 return $this->__call( __FUNCTION__
, func_get_args() );
786 public function sourceFile(
788 ?callable
$lineCallback = null,
789 ?callable
$resultCallback = null,
791 ?callable
$inputCallback = null
793 $this->assertRoleAllowsWrites();
795 return $this->__call( __FUNCTION__
, func_get_args() );
798 public function sourceStream(
800 ?callable
$lineCallback = null,
801 ?callable
$resultCallback = null,
803 ?callable
$inputCallback = null
805 $this->assertRoleAllowsWrites();
807 return $this->__call( __FUNCTION__
, func_get_args() );
810 public function dropTable( $table, $fname = __METHOD__
) {
811 $this->assertRoleAllowsWrites();
813 return $this->__call( __FUNCTION__
, func_get_args() );
816 public function truncateTable( $table, $fname = __METHOD__
) {
817 $this->assertRoleAllowsWrites();
819 return $this->__call( __FUNCTION__
, func_get_args() );
822 public function streamStatementEnd( &$sql, &$newLine ) {
823 return $this->__call( __FUNCTION__
, [ &$sql, &$newLine ] );
826 public function duplicateTableStructure(
827 $oldName, $newName, $temporary = false, $fname = __METHOD__
829 $this->assertRoleAllowsWrites();
831 return $this->__call( __FUNCTION__
, func_get_args() );
834 public function indexUnique( $table, $index, $fname = __METHOD__
) {
835 return $this->__call( __FUNCTION__
, func_get_args() );
838 public function listTables( $prefix = null, $fname = __METHOD__
) {
839 return $this->__call( __FUNCTION__
, func_get_args() );
842 public function fieldInfo( $table, $field ) {
843 return $this->__call( __FUNCTION__
, func_get_args() );
846 public function __toString() {
847 if ( $this->conn
=== null ) {
848 return $this->getType() . ' object #' . spl_object_id( $this );
851 return $this->__call( __FUNCTION__
, func_get_args() );
855 * Error out if the role is not DB_PRIMARY
857 * Note that the underlying connection may or may not itself be read-only.
858 * It could even be to a writable primary (both server-side and to the application).
859 * This error is meant for the case when a DB_REPLICA handle was requested but a
860 * a write was attempted on that handle regardless.
862 * In configurations where the primary DB has some generic read load or is the only server,
863 * DB_PRIMARY/DB_REPLICA will sometimes (or always) use the same connection to the primary DB.
864 * This does not effect the role of DBConnRef instances.
865 * @throws DBReadOnlyRoleError
867 protected function assertRoleAllowsWrites() {
868 // DB_PRIMARY is "prima facie" writable
869 if ( $this->role
!== ILoadBalancer
::DB_PRIMARY
) {
870 throw new DBReadOnlyRoleError( $this->conn
, "Cannot write with role DB_REPLICA" );
875 * @return DBUnexpectedError
877 protected function getDomainChangeException() {
878 return new DBUnexpectedError(
880 "Cannot directly change the selected DB domain; any underlying connection handle " .
881 "is owned by a LoadBalancer instance and possibly shared with other callers. " .
882 "LoadBalancer automatically manages DB domain re-selection of unused handles."
887 * @param int $i Specific or virtual (DB_PRIMARY/DB_REPLICA) server index
890 protected function normalizeServerIndex( $i ) {
891 return ( $i === ILoadBalancer
::DB_PRIMARY
) ? ServerInfo
::WRITER_INDEX
: $i;