Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
22.69% covered (danger)
22.69%
49 / 216
7.64% covered (danger)
7.64%
11 / 144
CRAP
0.00% covered (danger)
0.00%
0 / 1
DBConnRef
22.69% covered (danger)
22.69%
49 / 216
7.64% covered (danger)
7.64%
11 / 144
13056.06
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 ensureConnection
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 __call
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getReferenceRole
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getServerInfo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 trxLevel
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 trxTimestamp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 explicitTrxActive
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 tablePrefix
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 dbSchema
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getLBInfo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setLBInfo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 implicitOrderby
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 lastDoneWrites
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 writesPending
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 writesOrCallbacksPending
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 pendingWriteQueryDuration
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 pendingWriteCallers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isOpen
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setFlag
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 clearFlag
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 restoreFlags
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFlag
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getProperty
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDomainID
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getType
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 insertId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 lastErrno
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 lastError
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 affectedRows
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSoftwareLink
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getServerVersion
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 close
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 query
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 newSelectQueryBuilder
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 newUnionQueryBuilder
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 newUpdateQueryBuilder
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 newDeleteQueryBuilder
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 newInsertQueryBuilder
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 newReplaceQueryBuilder
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectFieldValues
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 select
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 selectSQLText
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 limitResult
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectRow
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 estimateRowCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectRowCount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 lockForUpdate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 fieldExists
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 indexExists
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 tableExists
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 insert
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
1.12
 update
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
1.12
 buildComparison
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 makeList
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 makeWhereFrom2d
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 factorConds
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 bitNot
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 bitAnd
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 bitOr
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildConcat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildGroupConcatField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildGreatest
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildLeast
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildSubstring
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildStringCast
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildIntegerCast
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildExcludedValue
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildSelectSubquery
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 databasesAreIndependent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 selectDomain
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDBname
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getServer
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getServerName
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 addQuotes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 expr
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 andExpr
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 orExpr
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addIdentifierQuotes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildLike
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 anyChar
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 anyString
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 replace
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
1.12
 upsert
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
1.12
 deleteJoin
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 delete
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
1.12
 insertSelect
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 unionSupportsOrderAndLimit
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 unionQueries
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 conditional
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 strreplace
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 primaryPosWait
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPrimaryPos
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 serverIsReadOnly
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onTransactionResolution
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onTransactionCommitOrIdle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onTransactionPreCommitOrIdle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setTransactionListener
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 startAtomic
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 endAtomic
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 cancelAtomic
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 doAtomicSection
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 begin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 commit
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 rollback
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 flushSession
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 flushSnapshot
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 timestamp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 timestampOrNull
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 ping
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getLag
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSessionLagStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 encodeBlob
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 decodeBlob
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setSessionOptions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setSchemaVars
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 lockIsFree
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 lock
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
1.12
 unlock
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
1.12
 getScopedLockAndFlush
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
1.12
 getInfinity
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 encodeExpiry
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 decodeExpiry
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isReadOnly
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setTableAliases
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTableAliases
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setIndexAliases
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 tableName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 tableNames
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 tableNamesN
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 sourceFile
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 sourceStream
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 dropTable
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 truncateTable
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 streamStatementEnd
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 duplicateTableStructure
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 indexUnique
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 listTables
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 fieldInfo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __toString
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 assertRoleAllowsWrites
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getDomainChangeException
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 normalizeServerIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace Wikimedia\Rdbms;
4
5use InvalidArgumentException;
6use Stringable;
7
8/**
9 * Helper class used for automatically re-using IDatabase connections and lazily
10 * establishing the actual network connection to a database host.
11 *
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
14 * wherever possible.
15 *
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.
24 *
25 * @par Example:
26 * @code
27 *     function getRowData() {
28 *         $conn = $this->lb->getConnection( DB_REPLICA );
29 *         $row = $conn->select( ... );
30 *         return $row ? (array)$row : false;
31 *     }
32 * @endcode
33 *
34 * @ingroup Database
35 * @since 1.22
36 */
37class DBConnRef implements Stringable, IMaintainableDatabase, IDatabaseForOwner {
38    /** @var ILoadBalancer */
39    private $lb;
40    /** @var Database|null Live connection handle */
41    private $conn;
42    /**
43     * @var array Map of (DBConnRef::FLD_* constant => connection parameter)
44     * @phan-var array{0:int,1:array|string|false,2:DatabaseDomain,3:int}
45     */
46    private $params;
47    /** @var int One of DB_PRIMARY/DB_REPLICA */
48    private $role;
49
50    /**
51     * @var int Reference to the $modcount passed to the constructor.
52     *      $conn is valid if $modCountRef and $modCountFix are the same.
53     */
54    private $modCountRef;
55
56    /**
57     * @var int Last known good value of $modCountRef
58     *      $conn is valid if $modCountRef and $modCountFix are the same.
59     */
60    private $modCountFix;
61
62    private const FLD_INDEX = 0;
63    private const FLD_GROUPS = 1;
64    private const FLD_DOMAIN = 2;
65    private const FLD_FLAGS = 3;
66
67    /**
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.
74     */
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." );
78        }
79
80        $params[self::FLD_DOMAIN] = DatabaseDomain::newFromId( $params[self::FLD_DOMAIN] );
81
82        $this->lb = $lb;
83        $this->params = $params;
84        $this->role = $role;
85
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
89    }
90
91    /**
92     * Connect to the database if we are not already connected.
93     */
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();
102                $this->conn = null;
103            }
104        }
105
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]
112            );
113            $this->modCountFix = $this->modCountRef;
114        }
115
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] );
121        }
122    }
123
124    public function __call( $name, array $arguments ) {
125        $this->ensureConnection();
126
127        return $this->conn->$name( ...$arguments );
128    }
129
130    /**
131     * @return int DB_PRIMARY when this *requires* the primary DB, otherwise DB_REPLICA
132     * @since 1.33
133     */
134    public function getReferenceRole() {
135        return $this->role;
136    }
137
138    public function getServerInfo() {
139        return $this->__call( __FUNCTION__, func_get_args() );
140    }
141
142    public function trxLevel() {
143        return $this->__call( __FUNCTION__, func_get_args() );
144    }
145
146    public function trxTimestamp() {
147        return $this->__call( __FUNCTION__, func_get_args() );
148    }
149
150    public function explicitTrxActive() {
151        return $this->__call( __FUNCTION__, func_get_args() );
152    }
153
154    public function tablePrefix( $prefix = null ) {
155        if ( $prefix !== null ) {
156            // Disallow things that might confuse the LoadBalancer tracking
157            throw $this->getDomainChangeException();
158        }
159
160        if ( $this->conn === null ) {
161            // Avoid triggering a database connection
162            $prefix = $this->params[self::FLD_DOMAIN]->getTablePrefix();
163        } else {
164            // This will just return the prefix
165            $prefix = $this->__call( __FUNCTION__, func_get_args() );
166        }
167
168        return $prefix;
169    }
170
171    public function dbSchema( $schema = null ) {
172        if ( $schema !== null ) {
173            // Disallow things that might confuse the LoadBalancer tracking
174            throw $this->getDomainChangeException();
175        }
176
177        if ( $this->conn === null ) {
178            // Avoid triggering a database connection
179            $schema = (string)( $this->params[self::FLD_DOMAIN]->getSchema() );
180        } else {
181            // This will just return the schema
182            $schema = $this->__call( __FUNCTION__, func_get_args() );
183        }
184
185        return $schema;
186    }
187
188    public function getLBInfo( $name = null ) {
189        return $this->__call( __FUNCTION__, func_get_args() );
190    }
191
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();
196    }
197
198    public function implicitOrderby() {
199        return $this->__call( __FUNCTION__, func_get_args() );
200    }
201
202    public function lastDoneWrites() {
203        return $this->__call( __FUNCTION__, func_get_args() );
204    }
205
206    public function writesPending() {
207        return $this->__call( __FUNCTION__, func_get_args() );
208    }
209
210    public function writesOrCallbacksPending() {
211        return $this->__call( __FUNCTION__, func_get_args() );
212    }
213
214    public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
215        return $this->__call( __FUNCTION__, func_get_args() );
216    }
217
218    public function pendingWriteCallers() {
219        return $this->__call( __FUNCTION__, func_get_args() );
220    }
221
222    public function isOpen() {
223        return $this->__call( __FUNCTION__, func_get_args() );
224    }
225
226    public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
227        return $this->__call( __FUNCTION__, func_get_args() );
228    }
229
230    public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
231        return $this->__call( __FUNCTION__, func_get_args() );
232    }
233
234    public function restoreFlags( $state = self::RESTORE_PRIOR ) {
235        return $this->__call( __FUNCTION__, func_get_args() );
236    }
237
238    public function getFlag( $flag ) {
239        return $this->__call( __FUNCTION__, func_get_args() );
240    }
241
242    public function getProperty( $name ) {
243        return $this->__call( __FUNCTION__, func_get_args() );
244    }
245
246    public function getDomainID() {
247        if ( $this->conn === null ) {
248            // Avoid triggering a database connection
249            return $this->params[self::FLD_DOMAIN]->getId();
250        }
251
252        return $this->__call( __FUNCTION__, func_get_args() );
253    }
254
255    public function getType() {
256        if ( $this->conn === null ) {
257            // Avoid triggering a database connection
258            $index = $this->normalizeServerIndex( $this->params[self::FLD_INDEX] );
259            if ( $index >= 0 ) {
260                // In theory, if $index is DB_REPLICA, the type could vary
261                return $this->lb->getServerType( $index );
262            }
263        }
264
265        return $this->__call( __FUNCTION__, func_get_args() );
266    }
267
268    public function insertId() {
269        return $this->__call( __FUNCTION__, func_get_args() );
270    }
271
272    public function lastErrno() {
273        return $this->__call( __FUNCTION__, func_get_args() );
274    }
275
276    public function lastError() {
277        return $this->__call( __FUNCTION__, func_get_args() );
278    }
279
280    public function affectedRows() {
281        return $this->__call( __FUNCTION__, func_get_args() );
282    }
283
284    public function getSoftwareLink() {
285        return $this->__call( __FUNCTION__, func_get_args() );
286    }
287
288    public function getServerVersion() {
289        return $this->__call( __FUNCTION__, func_get_args() );
290    }
291
292    public function close( $fname = __METHOD__ ) {
293        // @phan-suppress-previous-line PhanPluginNeverReturnMethod
294        throw new DBUnexpectedError( $this->conn, 'Cannot close shared connection.' );
295    }
296
297    public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
298        if ( $this->role !== ILoadBalancer::DB_PRIMARY ) {
299            $flags |= IDatabase::QUERY_REPLICA_ROLE;
300        }
301
302        return $this->__call( __FUNCTION__, [ $sql, $fname, $flags ] );
303    }
304
305    public function newSelectQueryBuilder(): SelectQueryBuilder {
306        // Use $this not $this->conn so that the domain is preserved (T326377)
307        return new SelectQueryBuilder( $this );
308    }
309
310    public function newUnionQueryBuilder(): UnionQueryBuilder {
311        // Use $this not $this->conn so that the domain is preserved (T326377)
312        return new UnionQueryBuilder( $this );
313    }
314
315    public function newUpdateQueryBuilder(): UpdateQueryBuilder {
316        // Use $this not $this->conn so that the domain is preserved (T326377)
317        return new UpdateQueryBuilder( $this );
318    }
319
320    public function newDeleteQueryBuilder(): DeleteQueryBuilder {
321        // Use $this not $this->conn so that the domain is preserved (T326377)
322        return new DeleteQueryBuilder( $this );
323    }
324
325    public function newInsertQueryBuilder(): InsertQueryBuilder {
326        // Use $this not $this->conn so that the domain is preserved (T326377)
327        return new InsertQueryBuilder( $this );
328    }
329
330    public function newReplaceQueryBuilder(): ReplaceQueryBuilder {
331        // Use $this not $this->conn so that the domain is preserved (T326377)
332        return new ReplaceQueryBuilder( $this );
333    }
334
335    public function selectField(
336        $tables, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
337    ) {
338        return $this->__call( __FUNCTION__, func_get_args() );
339    }
340
341    public function selectFieldValues(
342        $tables, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
343    ): array {
344        return $this->__call( __FUNCTION__, func_get_args() );
345    }
346
347    public function select(
348        $tables, $vars, $conds = '', $fname = __METHOD__,
349        $options = [], $join_conds = []
350    ) {
351        return $this->__call( __FUNCTION__, func_get_args() );
352    }
353
354    public function selectSQLText(
355        $tables, $vars, $conds = '', $fname = __METHOD__,
356        $options = [], $join_conds = []
357    ) {
358        return $this->__call( __FUNCTION__, func_get_args() );
359    }
360
361    public function limitResult( $sql, $limit, $offset = false ) {
362        return $this->__call( __FUNCTION__, func_get_args() );
363    }
364
365    public function selectRow(
366        $tables, $vars, $conds, $fname = __METHOD__,
367        $options = [], $join_conds = []
368    ) {
369        return $this->__call( __FUNCTION__, func_get_args() );
370    }
371
372    public function estimateRowCount(
373        $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
374    ): int {
375        return $this->__call( __FUNCTION__, func_get_args() );
376    }
377
378    public function selectRowCount(
379        $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
380    ): int {
381        return $this->__call( __FUNCTION__, func_get_args() );
382    }
383
384    public function lockForUpdate(
385        $table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
386    ) {
387        $this->assertRoleAllowsWrites();
388
389        return $this->__call( __FUNCTION__, func_get_args() );
390    }
391
392    public function fieldExists( $table, $field, $fname = __METHOD__ ) {
393        return $this->__call( __FUNCTION__, func_get_args() );
394    }
395
396    public function indexExists( $table, $index, $fname = __METHOD__ ) {
397        return $this->__call( __FUNCTION__, func_get_args() );
398    }
399
400    public function tableExists( $table, $fname = __METHOD__ ) {
401        return $this->__call( __FUNCTION__, func_get_args() );
402    }
403
404    public function insert( $table, $rows, $fname = __METHOD__, $options = [] ) {
405        $this->assertRoleAllowsWrites();
406
407        return $this->__call( __FUNCTION__, func_get_args() );
408    }
409
410    public function update( $table, $set, $conds, $fname = __METHOD__, $options = [] ) {
411        $this->assertRoleAllowsWrites();
412
413        return $this->__call( __FUNCTION__, func_get_args() );
414    }
415
416    public function buildComparison( string $op, array $conds ): string {
417        return $this->__call( __FUNCTION__, func_get_args() );
418    }
419
420    public function makeList( array $a, $mode = self::LIST_COMMA ) {
421        return $this->__call( __FUNCTION__, func_get_args() );
422    }
423
424    public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
425        return $this->__call( __FUNCTION__, func_get_args() );
426    }
427
428    public function factorConds( $condsArray ) {
429        return $this->__call( __FUNCTION__, func_get_args() );
430    }
431
432    public function bitNot( $field ) {
433        return $this->__call( __FUNCTION__, func_get_args() );
434    }
435
436    public function bitAnd( $fieldLeft, $fieldRight ) {
437        return $this->__call( __FUNCTION__, func_get_args() );
438    }
439
440    public function bitOr( $fieldLeft, $fieldRight ) {
441        return $this->__call( __FUNCTION__, func_get_args() );
442    }
443
444    public function buildConcat( $stringList ) {
445        return $this->__call( __FUNCTION__, func_get_args() );
446    }
447
448    public function buildGroupConcatField(
449        $delim, $tables, $field, $conds = '', $join_conds = []
450    ) {
451        return $this->__call( __FUNCTION__, func_get_args() );
452    }
453
454    public function buildGreatest( $fields, $values ) {
455        return $this->__call( __FUNCTION__, func_get_args() );
456    }
457
458    public function buildLeast( $fields, $values ) {
459        return $this->__call( __FUNCTION__, func_get_args() );
460    }
461
462    public function buildSubstring( $input, $startPosition, $length = null ) {
463        return $this->__call( __FUNCTION__, func_get_args() );
464    }
465
466    public function buildStringCast( $field ) {
467        return $this->__call( __FUNCTION__, func_get_args() );
468    }
469
470    public function buildIntegerCast( $field ) {
471        return $this->__call( __FUNCTION__, func_get_args() );
472    }
473
474    public function buildExcludedValue( $column ) {
475        return $this->__call( __FUNCTION__, func_get_args() );
476    }
477
478    public function buildSelectSubquery(
479        $tables, $vars, $conds = '', $fname = __METHOD__,
480        $options = [], $join_conds = []
481    ) {
482        return $this->__call( __FUNCTION__, func_get_args() );
483    }
484
485    public function databasesAreIndependent() {
486        return $this->__call( __FUNCTION__, func_get_args() );
487    }
488
489    public function selectDomain( $domain ) {
490        // @phan-suppress-previous-line PhanPluginNeverReturnMethod
491        // Disallow things that might confuse the LoadBalancer tracking
492        throw $this->getDomainChangeException();
493    }
494
495    public function getDBname() {
496        if ( $this->conn === null ) {
497            // Avoid triggering a database connection
498            return $this->params[self::FLD_DOMAIN]->getDatabase();
499        }
500
501        return $this->__call( __FUNCTION__, func_get_args() );
502    }
503
504    public function getServer() {
505        return $this->__call( __FUNCTION__, func_get_args() );
506    }
507
508    public function getServerName() {
509        if ( $this->conn === null ) {
510            // Avoid triggering a database connection
511            $index = $this->normalizeServerIndex( $this->params[self::FLD_INDEX] );
512            if ( $index >= 0 ) {
513                // If $index is DB_REPLICA, the server name could vary
514                return $this->lb->getServerName( $index );
515            }
516        }
517
518        return $this->__call( __FUNCTION__, func_get_args() );
519    }
520
521    public function addQuotes( $s ) {
522        return $this->__call( __FUNCTION__, func_get_args() );
523    }
524
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 );
528    }
529
530    public function andExpr( array $conds ): AndExpressionGroup {
531        // Does not use __call here to delay creating the db connection
532        return AndExpressionGroup::newFromArray( $conds );
533    }
534
535    public function orExpr( array $conds ): OrExpressionGroup {
536        // Does not use __call here to delay creating the db connection
537        return OrExpressionGroup::newFromArray( $conds );
538    }
539
540    public function addIdentifierQuotes( $s ) {
541        return $this->__call( __FUNCTION__, func_get_args() );
542    }
543
544    public function buildLike( $param, ...$params ) {
545        return $this->__call( __FUNCTION__, func_get_args() );
546    }
547
548    public function anyChar() {
549        return $this->__call( __FUNCTION__, func_get_args() );
550    }
551
552    public function anyString() {
553        return $this->__call( __FUNCTION__, func_get_args() );
554    }
555
556    public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
557        $this->assertRoleAllowsWrites();
558
559        return $this->__call( __FUNCTION__, func_get_args() );
560    }
561
562    public function upsert(
563        $table, array $rows, $uniqueKeys, array $set, $fname = __METHOD__
564    ) {
565        $this->assertRoleAllowsWrites();
566
567        return $this->__call( __FUNCTION__, func_get_args() );
568    }
569
570    public function deleteJoin(
571        $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
572    ) {
573        $this->assertRoleAllowsWrites();
574
575        $this->__call( __FUNCTION__, func_get_args() );
576    }
577
578    public function delete( $table, $conds, $fname = __METHOD__ ) {
579        $this->assertRoleAllowsWrites();
580
581        return $this->__call( __FUNCTION__, func_get_args() );
582    }
583
584    public function insertSelect(
585        $destTable, $srcTable, $varMap, $conds,
586        $fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
587    ) {
588        $this->assertRoleAllowsWrites();
589
590        return $this->__call( __FUNCTION__, func_get_args() );
591    }
592
593    public function unionSupportsOrderAndLimit() {
594        return $this->__call( __FUNCTION__, func_get_args() );
595    }
596
597    public function unionQueries( $sqls, $all, $options = [] ) {
598        return $this->__call( __FUNCTION__, func_get_args() );
599    }
600
601    public function conditional( $cond, $caseTrueExpression, $caseFalseExpression ) {
602        return $this->__call( __FUNCTION__, func_get_args() );
603    }
604
605    public function strreplace( $orig, $old, $new ) {
606        return $this->__call( __FUNCTION__, func_get_args() );
607    }
608
609    public function primaryPosWait( DBPrimaryPos $pos, $timeout ) {
610        return $this->__call( __FUNCTION__, func_get_args() );
611    }
612
613    public function getPrimaryPos() {
614        return $this->__call( __FUNCTION__, func_get_args() );
615    }
616
617    public function serverIsReadOnly() {
618        return $this->__call( __FUNCTION__, func_get_args() );
619    }
620
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() );
624    }
625
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() );
629    }
630
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() );
634    }
635
636    public function setTransactionListener( $name, ?callable $callback = null ) {
637        return $this->__call( __FUNCTION__, func_get_args() );
638    }
639
640    public function startAtomic(
641        $fname = __METHOD__, $cancelable = IDatabase::ATOMIC_NOT_CANCELABLE
642    ) {
643        // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
644        return $this->__call( __FUNCTION__, func_get_args() );
645    }
646
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() );
650    }
651
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() );
655    }
656
657    public function doAtomicSection(
658        $fname, callable $callback, $cancelable = self::ATOMIC_NOT_CANCELABLE
659    ) {
660        // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
661        return $this->__call( __FUNCTION__, func_get_args() );
662    }
663
664    public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
665        return $this->__call( __FUNCTION__, func_get_args() );
666    }
667
668    public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
669        return $this->__call( __FUNCTION__, func_get_args() );
670    }
671
672    public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
673        return $this->__call( __FUNCTION__, func_get_args() );
674    }
675
676    public function flushSession( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
677        return $this->__call( __FUNCTION__, func_get_args() );
678    }
679
680    public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
681        return $this->__call( __FUNCTION__, func_get_args() );
682    }
683
684    public function timestamp( $ts = 0 ) {
685        return $this->__call( __FUNCTION__, func_get_args() );
686    }
687
688    public function timestampOrNull( $ts = null ) {
689        return $this->__call( __FUNCTION__, func_get_args() );
690    }
691
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
696    }
697
698    public function getLag() {
699        return $this->__call( __FUNCTION__, func_get_args() );
700    }
701
702    public function getSessionLagStatus() {
703        return $this->__call( __FUNCTION__, func_get_args() );
704    }
705
706    public function encodeBlob( $b ) {
707        return $this->__call( __FUNCTION__, func_get_args() );
708    }
709
710    public function decodeBlob( $b ) {
711        return $this->__call( __FUNCTION__, func_get_args() );
712    }
713
714    public function setSessionOptions( array $options ) {
715        $this->__call( __FUNCTION__, func_get_args() );
716    }
717
718    public function setSchemaVars( $vars ) {
719        return $this->__call( __FUNCTION__, func_get_args() );
720    }
721
722    public function lockIsFree( $lockName, $method ) {
723        $this->assertRoleAllowsWrites();
724
725        return $this->__call( __FUNCTION__, func_get_args() );
726    }
727
728    public function lock( $lockName, $method, $timeout = 5, $flags = 0 ) {
729        $this->assertRoleAllowsWrites();
730
731        return $this->__call( __FUNCTION__, func_get_args() );
732    }
733
734    public function unlock( $lockName, $method ) {
735        $this->assertRoleAllowsWrites();
736
737        return $this->__call( __FUNCTION__, func_get_args() );
738    }
739
740    public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
741        $this->assertRoleAllowsWrites();
742
743        return $this->__call( __FUNCTION__, func_get_args() );
744    }
745
746    public function getInfinity() {
747        return $this->__call( __FUNCTION__, func_get_args() );
748    }
749
750    public function encodeExpiry( $expiry ) {
751        return $this->__call( __FUNCTION__, func_get_args() );
752    }
753
754    public function decodeExpiry( $expiry, $format = TS_MW ) {
755        return $this->__call( __FUNCTION__, func_get_args() );
756    }
757
758    public function isReadOnly() {
759        return $this->__call( __FUNCTION__, func_get_args() );
760    }
761
762    public function setTableAliases( array $aliases ) {
763        return $this->__call( __FUNCTION__, func_get_args() );
764    }
765
766    public function getTableAliases() {
767        return $this->__call( __FUNCTION__, func_get_args() );
768    }
769
770    public function setIndexAliases( array $aliases ) {
771        return $this->__call( __FUNCTION__, func_get_args() );
772    }
773
774    public function tableName( $name, $format = 'quoted' ) {
775        return $this->__call( __FUNCTION__, func_get_args() );
776    }
777
778    public function tableNames( ...$tables ) {
779        return $this->__call( __FUNCTION__, func_get_args() );
780    }
781
782    public function tableNamesN( ...$tables ) {
783        return $this->__call( __FUNCTION__, func_get_args() );
784    }
785
786    public function sourceFile(
787        $filename,
788        ?callable $lineCallback = null,
789        ?callable $resultCallback = null,
790        $fname = false,
791        ?callable $inputCallback = null
792    ) {
793        $this->assertRoleAllowsWrites();
794
795        return $this->__call( __FUNCTION__, func_get_args() );
796    }
797
798    public function sourceStream(
799        $fp,
800        ?callable $lineCallback = null,
801        ?callable $resultCallback = null,
802        $fname = __METHOD__,
803        ?callable $inputCallback = null
804    ) {
805        $this->assertRoleAllowsWrites();
806
807        return $this->__call( __FUNCTION__, func_get_args() );
808    }
809
810    public function dropTable( $table, $fname = __METHOD__ ) {
811        $this->assertRoleAllowsWrites();
812
813        return $this->__call( __FUNCTION__, func_get_args() );
814    }
815
816    public function truncateTable( $table, $fname = __METHOD__ ) {
817        $this->assertRoleAllowsWrites();
818
819        return $this->__call( __FUNCTION__, func_get_args() );
820    }
821
822    public function streamStatementEnd( &$sql, &$newLine ) {
823        return $this->__call( __FUNCTION__, [ &$sql, &$newLine ] );
824    }
825
826    public function duplicateTableStructure(
827        $oldName, $newName, $temporary = false, $fname = __METHOD__
828    ) {
829        $this->assertRoleAllowsWrites();
830
831        return $this->__call( __FUNCTION__, func_get_args() );
832    }
833
834    public function indexUnique( $table, $index, $fname = __METHOD__ ) {
835        return $this->__call( __FUNCTION__, func_get_args() );
836    }
837
838    public function listTables( $prefix = null, $fname = __METHOD__ ) {
839        return $this->__call( __FUNCTION__, func_get_args() );
840    }
841
842    public function fieldInfo( $table, $field ) {
843        return $this->__call( __FUNCTION__, func_get_args() );
844    }
845
846    public function __toString() {
847        if ( $this->conn === null ) {
848            return $this->getType() . ' object #' . spl_object_id( $this );
849        }
850
851        return $this->__call( __FUNCTION__, func_get_args() );
852    }
853
854    /**
855     * Error out if the role is not DB_PRIMARY
856     *
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.
861     *
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
866     */
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" );
871        }
872    }
873
874    /**
875     * @return DBUnexpectedError
876     */
877    protected function getDomainChangeException() {
878        return new DBUnexpectedError(
879            $this,
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."
883        );
884    }
885
886    /**
887     * @param int $i Specific or virtual (DB_PRIMARY/DB_REPLICA) server index
888     * @return int|mixed
889     */
890    protected function normalizeServerIndex( $i ) {
891        return ( $i === ILoadBalancer::DB_PRIMARY ) ? ServerInfo::WRITER_INDEX : $i;
892    }
893}