Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
20.26% covered (danger)
20.26%
46 / 227
5.23% covered (danger)
5.23%
8 / 153
CRAP
0.00% covered (danger)
0.00%
0 / 1
DBConnRef
20.26% covered (danger)
20.26%
46 / 227
5.23% covered (danger)
5.23%
8 / 153
15700.09
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
5
 __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
 getTopologyBasedServerId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTopologyRole
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
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 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
 nextSequenceValue
0.00% covered (danger)
0.00%
0 / 2
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
 wasDeadlock
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 wasReadOnlyError
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
 getReplicaPos
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
 onAtomicSectionCancel
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
 namedLocksEnqueue
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 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
 truncate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 listViews
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 textFieldSize
0.00% covered (danger)
0.00%
0 / 1
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;
6
7/**
8 * Helper class used for automatically re-using IDatabase connections and lazily
9 * establishing the actual network connection to a database host.
10 *
11 * It does this by deferring to ILoadBalancer::getConnectionInternal, which in
12 * turn ensures we share and re-use a single connection for a given database
13 * wherever possible.
14 *
15 * This class previously used an RAII-style pattern where connections would be
16 * claimed from a pool, and then added back to the pool for re-use only after
17 * the calling code's variable for this object went out of scope (a __destruct
18 * got called when the calling function returns or throws). This is no longer
19 * needed today as LoadBalancer now permits re-use internally even for
20 * overlapping callers, where two pieces of code may both obtain their own
21 * DBConnRef object and where both are used alternatingly, and yet still share
22 * the same connection.
23 *
24 * @par Example:
25 * @code
26 *     function getRowData() {
27 *         $conn = $this->lb->getConnection( DB_REPLICA );
28 *         $row = $conn->select( ... );
29 *         return $row ? (array)$row : false;
30 *     }
31 * @endcode
32 *
33 * @ingroup Database
34 * @since 1.22
35 */
36class DBConnRef implements IMaintainableDatabase {
37    /** @var ILoadBalancer */
38    private $lb;
39    /** @var Database|null Live connection handle */
40    private $conn;
41    /**
42     * @var array Map of (DBConnRef::FLD_* constant => connection parameter)
43     * @phan-var array{0:int,1:array|string|false,2:DatabaseDomain,3:int}
44     */
45    private $params;
46    /** @var int One of DB_PRIMARY/DB_REPLICA */
47    private $role;
48
49    /**
50     * @var int Reference to the $modcount passed to the constructor.
51     *      $conn is valid if $modCountRef and $modCountFix are the same.
52     */
53    private $modCountRef;
54
55    /**
56     * @var int Last known good value of $modCountRef
57     *      $conn is valid if $modCountRef and $modCountFix are the same.
58     */
59    private $modCountFix;
60
61    private const FLD_INDEX = 0;
62    private const FLD_GROUPS = 1;
63    private const FLD_DOMAIN = 2;
64    private const FLD_FLAGS = 3;
65
66    /**
67     * @internal May not be used outside Rdbms LoadBalancer
68     * @param ILoadBalancer $lb Connection manager for $conn
69     * @param array $params [server index, query groups, domain, flags]
70     * @param int $role The type of connection asked for; one of DB_PRIMARY/DB_REPLICA
71     * @param null|int &$modcount Reference to a modification counter. This is for
72     *  LoadBalancer::reconfigure to indicate that a new connection should be acquired.
73     */
74    public function __construct( ILoadBalancer $lb, $params, $role, &$modcount = 0 ) {
75        if ( !is_array( $params ) || count( $params ) < 4 || $params[self::FLD_DOMAIN] === false ) {
76            throw new InvalidArgumentException( "Missing lazy connection arguments." );
77        }
78
79        $params[self::FLD_DOMAIN] = DatabaseDomain::newFromId( $params[self::FLD_DOMAIN] );
80
81        $this->lb = $lb;
82        $this->params = $params;
83        $this->role = $role;
84
85        // $this->conn is valid as long as $this->modCountRef and $this->modCountFix are the same.
86        $this->modCountRef = &$modcount; // remember reference
87        $this->modCountFix = $modcount;  // remember current value
88    }
89
90    /**
91     * Connect to the database if we are not already connected.
92     */
93    public function ensureConnection() {
94        if ( $this->modCountFix !== $this->modCountRef ) {
95            // Discard existing connection, unless we are in an ongoing transaction.
96            // This is triggered by LoadBalancer::reconfigure(), to allow changed settings
97            // to take effect. The primary use case are replica servers being taken out of
98            // rotation, or the primary database changing.
99            if ( !$this->conn->trxLevel() ) {
100                $this->conn->close();
101                $this->conn = null;
102            }
103        }
104
105        if ( $this->conn === null ) {
106            $this->conn = $this->lb->getConnectionInternal(
107                $this->params[self::FLD_INDEX],
108                $this->params[self::FLD_GROUPS],
109                $this->params[self::FLD_DOMAIN]->getId(),
110                $this->params[self::FLD_FLAGS]
111            );
112            $this->modCountFix = $this->modCountRef;
113        }
114
115        if ( !$this->params[self::FLD_DOMAIN]->equals( $this->conn->getDomainID() ) ) {
116            // The underlying connection handle is likely being shared by other DBConnRef
117            // instances in a load balancer. Make sure that each one routes queries by their
118            // owner function to the domain that the owner expects.
119            $this->conn->selectDomain( $this->params[self::FLD_DOMAIN] );
120        }
121    }
122
123    public function __call( $name, array $arguments ) {
124        $this->ensureConnection();
125
126        return $this->conn->$name( ...$arguments );
127    }
128
129    /**
130     * @return int DB_PRIMARY when this *requires* the primary DB, otherwise DB_REPLICA
131     * @since 1.33
132     */
133    public function getReferenceRole() {
134        return $this->role;
135    }
136
137    public function getServerInfo() {
138        return $this->__call( __FUNCTION__, func_get_args() );
139    }
140
141    public function getTopologyBasedServerId() {
142        return $this->__call( __FUNCTION__, func_get_args() );
143    }
144
145    public function getTopologyRole() {
146        return $this->__call( __FUNCTION__, func_get_args() );
147    }
148
149    public function trxLevel() {
150        return $this->__call( __FUNCTION__, func_get_args() );
151    }
152
153    public function trxTimestamp() {
154        return $this->__call( __FUNCTION__, func_get_args() );
155    }
156
157    public function explicitTrxActive() {
158        return $this->__call( __FUNCTION__, func_get_args() );
159    }
160
161    public function tablePrefix( $prefix = null ) {
162        if ( $prefix !== null ) {
163            // Disallow things that might confuse the LoadBalancer tracking
164            throw $this->getDomainChangeException();
165        }
166
167        if ( $this->conn === null ) {
168            // Avoid triggering a database connection
169            $prefix = $this->params[self::FLD_DOMAIN]->getTablePrefix();
170        } else {
171            // This will just return the prefix
172            $prefix = $this->__call( __FUNCTION__, func_get_args() );
173        }
174
175        return $prefix;
176    }
177
178    public function dbSchema( $schema = null ) {
179        if ( $schema !== null ) {
180            // Disallow things that might confuse the LoadBalancer tracking
181            throw $this->getDomainChangeException();
182        }
183
184        if ( $this->conn === null ) {
185            // Avoid triggering a database connection
186            $schema = (string)( $this->params[self::FLD_DOMAIN]->getSchema() );
187        } else {
188            // This will just return the schema
189            $schema = $this->__call( __FUNCTION__, func_get_args() );
190        }
191
192        return $schema;
193    }
194
195    public function getLBInfo( $name = null ) {
196        return $this->__call( __FUNCTION__, func_get_args() );
197    }
198
199    public function setLBInfo( $nameOrArray, $value = null ) {
200        // @phan-suppress-previous-line PhanPluginNeverReturnMethod
201        // Disallow things that might confuse the LoadBalancer tracking
202        throw $this->getDomainChangeException();
203    }
204
205    public function implicitOrderby() {
206        return $this->__call( __FUNCTION__, func_get_args() );
207    }
208
209    public function lastDoneWrites() {
210        return $this->__call( __FUNCTION__, func_get_args() );
211    }
212
213    public function writesPending() {
214        return $this->__call( __FUNCTION__, func_get_args() );
215    }
216
217    public function writesOrCallbacksPending() {
218        return $this->__call( __FUNCTION__, func_get_args() );
219    }
220
221    public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
222        return $this->__call( __FUNCTION__, func_get_args() );
223    }
224
225    public function pendingWriteCallers() {
226        return $this->__call( __FUNCTION__, func_get_args() );
227    }
228
229    public function isOpen() {
230        return $this->__call( __FUNCTION__, func_get_args() );
231    }
232
233    public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
234        return $this->__call( __FUNCTION__, func_get_args() );
235    }
236
237    public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
238        return $this->__call( __FUNCTION__, func_get_args() );
239    }
240
241    public function restoreFlags( $state = self::RESTORE_PRIOR ) {
242        return $this->__call( __FUNCTION__, func_get_args() );
243    }
244
245    public function getFlag( $flag ) {
246        return $this->__call( __FUNCTION__, func_get_args() );
247    }
248
249    public function getProperty( $name ) {
250        return $this->__call( __FUNCTION__, func_get_args() );
251    }
252
253    public function getDomainID() {
254        if ( $this->conn === null ) {
255            // Avoid triggering a database connection
256            return $this->params[self::FLD_DOMAIN]->getId();
257        }
258
259        return $this->__call( __FUNCTION__, func_get_args() );
260    }
261
262    public function getType() {
263        if ( $this->conn === null ) {
264            // Avoid triggering a database connection
265            $index = $this->normalizeServerIndex( $this->params[self::FLD_INDEX] );
266            if ( $index >= 0 ) {
267                // In theory, if $index is DB_REPLICA, the type could vary
268                return $this->lb->getServerType( $index );
269            }
270        }
271
272        return $this->__call( __FUNCTION__, func_get_args() );
273    }
274
275    public function insertId() {
276        return $this->__call( __FUNCTION__, func_get_args() );
277    }
278
279    public function lastErrno() {
280        return $this->__call( __FUNCTION__, func_get_args() );
281    }
282
283    public function lastError() {
284        return $this->__call( __FUNCTION__, func_get_args() );
285    }
286
287    public function affectedRows() {
288        return $this->__call( __FUNCTION__, func_get_args() );
289    }
290
291    public function getSoftwareLink() {
292        return $this->__call( __FUNCTION__, func_get_args() );
293    }
294
295    public function getServerVersion() {
296        return $this->__call( __FUNCTION__, func_get_args() );
297    }
298
299    public function close( $fname = __METHOD__ ) {
300        // @phan-suppress-previous-line PhanPluginNeverReturnMethod
301        throw new DBUnexpectedError( $this->conn, 'Cannot close shared connection.' );
302    }
303
304    public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
305        if ( $this->role !== ILoadBalancer::DB_PRIMARY ) {
306            $flags |= IDatabase::QUERY_REPLICA_ROLE;
307        }
308
309        return $this->__call( __FUNCTION__, [ $sql, $fname, $flags ] );
310    }
311
312    public function newSelectQueryBuilder(): SelectQueryBuilder {
313        // Use $this not $this->conn so that the domain is preserved (T326377)
314        return new SelectQueryBuilder( $this );
315    }
316
317    public function newUnionQueryBuilder(): UnionQueryBuilder {
318        // Use $this not $this->conn so that the domain is preserved (T326377)
319        return new UnionQueryBuilder( $this );
320    }
321
322    public function newUpdateQueryBuilder(): UpdateQueryBuilder {
323        // Use $this not $this->conn so that the domain is preserved (T326377)
324        return new UpdateQueryBuilder( $this );
325    }
326
327    public function newDeleteQueryBuilder(): DeleteQueryBuilder {
328        // Use $this not $this->conn so that the domain is preserved (T326377)
329        return new DeleteQueryBuilder( $this );
330    }
331
332    public function newInsertQueryBuilder(): InsertQueryBuilder {
333        // Use $this not $this->conn so that the domain is preserved (T326377)
334        return new InsertQueryBuilder( $this );
335    }
336
337    public function newReplaceQueryBuilder(): ReplaceQueryBuilder {
338        // Use $this not $this->conn so that the domain is preserved (T326377)
339        return new ReplaceQueryBuilder( $this );
340    }
341
342    public function selectField(
343        $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
344    ) {
345        return $this->__call( __FUNCTION__, func_get_args() );
346    }
347
348    public function selectFieldValues(
349        $table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
350    ): array {
351        return $this->__call( __FUNCTION__, func_get_args() );
352    }
353
354    public function select(
355        $table, $vars, $conds = '', $fname = __METHOD__,
356        $options = [], $join_conds = []
357    ) {
358        return $this->__call( __FUNCTION__, func_get_args() );
359    }
360
361    public function selectSQLText(
362        $table, $vars, $conds = '', $fname = __METHOD__,
363        $options = [], $join_conds = []
364    ) {
365        return $this->__call( __FUNCTION__, func_get_args() );
366    }
367
368    public function limitResult( $sql, $limit, $offset = false ) {
369        return $this->__call( __FUNCTION__, func_get_args() );
370    }
371
372    public function selectRow(
373        $table, $vars, $conds, $fname = __METHOD__,
374        $options = [], $join_conds = []
375    ) {
376        return $this->__call( __FUNCTION__, func_get_args() );
377    }
378
379    public function estimateRowCount(
380        $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
381    ) {
382        return $this->__call( __FUNCTION__, func_get_args() );
383    }
384
385    public function selectRowCount(
386        $tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
387    ) {
388        return $this->__call( __FUNCTION__, func_get_args() );
389    }
390
391    public function lockForUpdate(
392        $table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
393    ) {
394        $this->assertRoleAllowsWrites();
395
396        return $this->__call( __FUNCTION__, func_get_args() );
397    }
398
399    public function fieldExists( $table, $field, $fname = __METHOD__ ) {
400        return $this->__call( __FUNCTION__, func_get_args() );
401    }
402
403    public function indexExists( $table, $index, $fname = __METHOD__ ) {
404        return $this->__call( __FUNCTION__, func_get_args() );
405    }
406
407    public function tableExists( $table, $fname = __METHOD__ ) {
408        return $this->__call( __FUNCTION__, func_get_args() );
409    }
410
411    public function insert( $table, $rows, $fname = __METHOD__, $options = [] ) {
412        $this->assertRoleAllowsWrites();
413
414        return $this->__call( __FUNCTION__, func_get_args() );
415    }
416
417    public function update( $table, $set, $conds, $fname = __METHOD__, $options = [] ) {
418        $this->assertRoleAllowsWrites();
419
420        return $this->__call( __FUNCTION__, func_get_args() );
421    }
422
423    public function buildComparison( string $op, array $conds ): string {
424        return $this->__call( __FUNCTION__, func_get_args() );
425    }
426
427    public function makeList( array $a, $mode = self::LIST_COMMA ) {
428        return $this->__call( __FUNCTION__, func_get_args() );
429    }
430
431    public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
432        return $this->__call( __FUNCTION__, func_get_args() );
433    }
434
435    public function factorConds( $condsArray ) {
436        return $this->__call( __FUNCTION__, func_get_args() );
437    }
438
439    public function bitNot( $field ) {
440        return $this->__call( __FUNCTION__, func_get_args() );
441    }
442
443    public function bitAnd( $fieldLeft, $fieldRight ) {
444        return $this->__call( __FUNCTION__, func_get_args() );
445    }
446
447    public function bitOr( $fieldLeft, $fieldRight ) {
448        return $this->__call( __FUNCTION__, func_get_args() );
449    }
450
451    public function buildConcat( $stringList ) {
452        return $this->__call( __FUNCTION__, func_get_args() );
453    }
454
455    public function buildGroupConcatField(
456        $delim, $table, $field, $conds = '', $join_conds = []
457    ) {
458        return $this->__call( __FUNCTION__, func_get_args() );
459    }
460
461    public function buildGreatest( $fields, $values ) {
462        return $this->__call( __FUNCTION__, func_get_args() );
463    }
464
465    public function buildLeast( $fields, $values ) {
466        return $this->__call( __FUNCTION__, func_get_args() );
467    }
468
469    public function buildSubstring( $input, $startPosition, $length = null ) {
470        return $this->__call( __FUNCTION__, func_get_args() );
471    }
472
473    public function buildStringCast( $field ) {
474        return $this->__call( __FUNCTION__, func_get_args() );
475    }
476
477    public function buildIntegerCast( $field ) {
478        return $this->__call( __FUNCTION__, func_get_args() );
479    }
480
481    public function buildExcludedValue( $column ) {
482        return $this->__call( __FUNCTION__, func_get_args() );
483    }
484
485    public function buildSelectSubquery(
486        $table, $vars, $conds = '', $fname = __METHOD__,
487        $options = [], $join_conds = []
488    ) {
489        return $this->__call( __FUNCTION__, func_get_args() );
490    }
491
492    public function databasesAreIndependent() {
493        return $this->__call( __FUNCTION__, func_get_args() );
494    }
495
496    public function selectDomain( $domain ) {
497        // @phan-suppress-previous-line PhanPluginNeverReturnMethod
498        // Disallow things that might confuse the LoadBalancer tracking
499        throw $this->getDomainChangeException();
500    }
501
502    public function getDBname() {
503        if ( $this->conn === null ) {
504            // Avoid triggering a database connection
505            return $this->params[self::FLD_DOMAIN]->getDatabase();
506        }
507
508        return $this->__call( __FUNCTION__, func_get_args() );
509    }
510
511    public function getServer() {
512        return $this->__call( __FUNCTION__, func_get_args() );
513    }
514
515    public function getServerName() {
516        if ( $this->conn === null ) {
517            // Avoid triggering a database connection
518            $index = $this->normalizeServerIndex( $this->params[self::FLD_INDEX] );
519            if ( $index >= 0 ) {
520                // If $index is DB_REPLICA, the server name could vary
521                return $this->lb->getServerName( $index );
522            }
523        }
524
525        return $this->__call( __FUNCTION__, func_get_args() );
526    }
527
528    public function addQuotes( $s ) {
529        return $this->__call( __FUNCTION__, func_get_args() );
530    }
531
532    public function expr( string $field, string $op, $value ): Expression {
533        return new Expression( $field, $op, $value );
534    }
535
536    public function addIdentifierQuotes( $s ) {
537        return $this->__call( __FUNCTION__, func_get_args() );
538    }
539
540    public function buildLike( $param, ...$params ) {
541        return $this->__call( __FUNCTION__, func_get_args() );
542    }
543
544    public function anyChar() {
545        return $this->__call( __FUNCTION__, func_get_args() );
546    }
547
548    public function anyString() {
549        return $this->__call( __FUNCTION__, func_get_args() );
550    }
551
552    public function nextSequenceValue( $seqName ) {
553        $this->assertRoleAllowsWrites();
554
555        return $this->__call( __FUNCTION__, func_get_args() );
556    }
557
558    public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
559        $this->assertRoleAllowsWrites();
560
561        return $this->__call( __FUNCTION__, func_get_args() );
562    }
563
564    public function upsert(
565        $table, array $rows, $uniqueKeys, array $set, $fname = __METHOD__
566    ) {
567        $this->assertRoleAllowsWrites();
568
569        return $this->__call( __FUNCTION__, func_get_args() );
570    }
571
572    public function deleteJoin(
573        $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
574    ) {
575        $this->assertRoleAllowsWrites();
576
577        $this->__call( __FUNCTION__, func_get_args() );
578    }
579
580    public function delete( $table, $conds, $fname = __METHOD__ ) {
581        $this->assertRoleAllowsWrites();
582
583        return $this->__call( __FUNCTION__, func_get_args() );
584    }
585
586    public function insertSelect(
587        $destTable, $srcTable, $varMap, $conds,
588        $fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
589    ) {
590        $this->assertRoleAllowsWrites();
591
592        return $this->__call( __FUNCTION__, func_get_args() );
593    }
594
595    public function unionSupportsOrderAndLimit() {
596        return $this->__call( __FUNCTION__, func_get_args() );
597    }
598
599    public function unionQueries( $sqls, $all, $options = [] ) {
600        return $this->__call( __FUNCTION__, func_get_args() );
601    }
602
603    public function conditional( $cond, $caseTrueExpression, $caseFalseExpression ) {
604        return $this->__call( __FUNCTION__, func_get_args() );
605    }
606
607    public function strreplace( $orig, $old, $new ) {
608        return $this->__call( __FUNCTION__, func_get_args() );
609    }
610
611    public function wasDeadlock() {
612        return $this->__call( __FUNCTION__, func_get_args() );
613    }
614
615    public function wasReadOnlyError() {
616        return $this->__call( __FUNCTION__, func_get_args() );
617    }
618
619    public function primaryPosWait( DBPrimaryPos $pos, $timeout ) {
620        return $this->__call( __FUNCTION__, func_get_args() );
621    }
622
623    public function getReplicaPos() {
624        return $this->__call( __FUNCTION__, func_get_args() );
625    }
626
627    public function getPrimaryPos() {
628        return $this->__call( __FUNCTION__, func_get_args() );
629    }
630
631    public function serverIsReadOnly() {
632        return $this->__call( __FUNCTION__, func_get_args() );
633    }
634
635    public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
636        // DB_REPLICA role: caller might want to refresh cache after a REPEATABLE-READ snapshot
637        return $this->__call( __FUNCTION__, func_get_args() );
638    }
639
640    public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
641        // DB_REPLICA role: caller might want to refresh cache after a REPEATABLE-READ snapshot
642        return $this->__call( __FUNCTION__, func_get_args() );
643    }
644
645    public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
646        // DB_REPLICA role: caller might want to refresh cache after a cache mutex is released
647        return $this->__call( __FUNCTION__, func_get_args() );
648    }
649
650    public function onAtomicSectionCancel( callable $callback, $fname = __METHOD__ ) {
651        return $this->__call( __FUNCTION__, func_get_args() );
652    }
653
654    public function setTransactionListener( $name, callable $callback = null ) {
655        return $this->__call( __FUNCTION__, func_get_args() );
656    }
657
658    public function startAtomic(
659        $fname = __METHOD__, $cancelable = IDatabase::ATOMIC_NOT_CANCELABLE
660    ) {
661        // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
662        return $this->__call( __FUNCTION__, func_get_args() );
663    }
664
665    public function endAtomic( $fname = __METHOD__ ) {
666        // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
667        return $this->__call( __FUNCTION__, func_get_args() );
668    }
669
670    public function cancelAtomic( $fname = __METHOD__, AtomicSectionIdentifier $sectionId = null ) {
671        // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
672        return $this->__call( __FUNCTION__, func_get_args() );
673    }
674
675    public function doAtomicSection(
676        $fname, callable $callback, $cancelable = self::ATOMIC_NOT_CANCELABLE
677    ) {
678        // Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
679        return $this->__call( __FUNCTION__, func_get_args() );
680    }
681
682    public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
683        return $this->__call( __FUNCTION__, func_get_args() );
684    }
685
686    public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
687        return $this->__call( __FUNCTION__, func_get_args() );
688    }
689
690    public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
691        return $this->__call( __FUNCTION__, func_get_args() );
692    }
693
694    public function flushSession( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
695        return $this->__call( __FUNCTION__, func_get_args() );
696    }
697
698    public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
699        return $this->__call( __FUNCTION__, func_get_args() );
700    }
701
702    public function timestamp( $ts = 0 ) {
703        return $this->__call( __FUNCTION__, func_get_args() );
704    }
705
706    public function timestampOrNull( $ts = null ) {
707        return $this->__call( __FUNCTION__, func_get_args() );
708    }
709
710    public function ping( &$rtt = null ) {
711        return func_num_args()
712            ? $this->__call( __FUNCTION__, [ &$rtt ] )
713            : $this->__call( __FUNCTION__, [] ); // method cares about null vs missing
714    }
715
716    public function getLag() {
717        return $this->__call( __FUNCTION__, func_get_args() );
718    }
719
720    public function getSessionLagStatus() {
721        return $this->__call( __FUNCTION__, func_get_args() );
722    }
723
724    public function encodeBlob( $b ) {
725        return $this->__call( __FUNCTION__, func_get_args() );
726    }
727
728    public function decodeBlob( $b ) {
729        return $this->__call( __FUNCTION__, func_get_args() );
730    }
731
732    public function setSessionOptions( array $options ) {
733        $this->__call( __FUNCTION__, func_get_args() );
734    }
735
736    public function setSchemaVars( $vars ) {
737        return $this->__call( __FUNCTION__, func_get_args() );
738    }
739
740    public function lockIsFree( $lockName, $method ) {
741        $this->assertRoleAllowsWrites();
742
743        return $this->__call( __FUNCTION__, func_get_args() );
744    }
745
746    public function lock( $lockName, $method, $timeout = 5, $flags = 0 ) {
747        $this->assertRoleAllowsWrites();
748
749        return $this->__call( __FUNCTION__, func_get_args() );
750    }
751
752    public function unlock( $lockName, $method ) {
753        $this->assertRoleAllowsWrites();
754
755        return $this->__call( __FUNCTION__, func_get_args() );
756    }
757
758    public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
759        $this->assertRoleAllowsWrites();
760
761        return $this->__call( __FUNCTION__, func_get_args() );
762    }
763
764    public function namedLocksEnqueue() {
765        return $this->__call( __FUNCTION__, func_get_args() );
766    }
767
768    public function getInfinity() {
769        return $this->__call( __FUNCTION__, func_get_args() );
770    }
771
772    public function encodeExpiry( $expiry ) {
773        return $this->__call( __FUNCTION__, func_get_args() );
774    }
775
776    public function decodeExpiry( $expiry, $format = TS_MW ) {
777        return $this->__call( __FUNCTION__, func_get_args() );
778    }
779
780    public function isReadOnly() {
781        return $this->__call( __FUNCTION__, func_get_args() );
782    }
783
784    public function setTableAliases( array $aliases ) {
785        return $this->__call( __FUNCTION__, func_get_args() );
786    }
787
788    public function getTableAliases() {
789        return $this->__call( __FUNCTION__, func_get_args() );
790    }
791
792    public function setIndexAliases( array $aliases ) {
793        return $this->__call( __FUNCTION__, func_get_args() );
794    }
795
796    public function tableName( $name, $format = 'quoted' ) {
797        return $this->__call( __FUNCTION__, func_get_args() );
798    }
799
800    public function tableNames( ...$tables ) {
801        return $this->__call( __FUNCTION__, func_get_args() );
802    }
803
804    public function tableNamesN( ...$tables ) {
805        return $this->__call( __FUNCTION__, func_get_args() );
806    }
807
808    public function sourceFile(
809        $filename,
810        callable $lineCallback = null,
811        callable $resultCallback = null,
812        $fname = false,
813        callable $inputCallback = null
814    ) {
815        $this->assertRoleAllowsWrites();
816
817        return $this->__call( __FUNCTION__, func_get_args() );
818    }
819
820    public function sourceStream(
821        $fp,
822        callable $lineCallback = null,
823        callable $resultCallback = null,
824        $fname = __METHOD__,
825        callable $inputCallback = null
826    ) {
827        $this->assertRoleAllowsWrites();
828
829        return $this->__call( __FUNCTION__, func_get_args() );
830    }
831
832    public function dropTable( $table, $fname = __METHOD__ ) {
833        $this->assertRoleAllowsWrites();
834
835        return $this->__call( __FUNCTION__, func_get_args() );
836    }
837
838    public function truncateTable( $table, $fname = __METHOD__ ) {
839        $this->assertRoleAllowsWrites();
840
841        return $this->__call( __FUNCTION__, func_get_args() );
842    }
843
844    public function truncate( $tables, $fname = __METHOD__ ) {
845        $this->assertRoleAllowsWrites();
846
847        return $this->__call( __FUNCTION__, func_get_args() );
848    }
849
850    public function listViews( $prefix = null, $fname = __METHOD__ ) {
851        return $this->__call( __FUNCTION__, func_get_args() );
852    }
853
854    public function textFieldSize( $table, $field ) {
855        return $this->__call( __FUNCTION__, func_get_args() );
856    }
857
858    public function streamStatementEnd( &$sql, &$newLine ) {
859        return $this->__call( __FUNCTION__, [ &$sql, &$newLine ] );
860    }
861
862    public function duplicateTableStructure(
863        $oldName, $newName, $temporary = false, $fname = __METHOD__
864    ) {
865        $this->assertRoleAllowsWrites();
866
867        return $this->__call( __FUNCTION__, func_get_args() );
868    }
869
870    public function indexUnique( $table, $index, $fname = __METHOD__ ) {
871        return $this->__call( __FUNCTION__, func_get_args() );
872    }
873
874    public function listTables( $prefix = null, $fname = __METHOD__ ) {
875        return $this->__call( __FUNCTION__, func_get_args() );
876    }
877
878    public function fieldInfo( $table, $field ) {
879        return $this->__call( __FUNCTION__, func_get_args() );
880    }
881
882    public function __toString() {
883        if ( $this->conn === null ) {
884            return $this->getType() . ' object #' . spl_object_id( $this );
885        }
886
887        return $this->__call( __FUNCTION__, func_get_args() );
888    }
889
890    /**
891     * Error out if the role is not DB_PRIMARY
892     *
893     * Note that the underlying connection may or may not itself be read-only.
894     * It could even be to a writable primary (both server-side and to the application).
895     * This error is meant for the case when a DB_REPLICA handle was requested but a
896     * a write was attempted on that handle regardless.
897     *
898     * In configurations where the primary DB has some generic read load or is the only server,
899     * DB_PRIMARY/DB_REPLICA will sometimes (or always) use the same connection to the primary DB.
900     * This does not effect the role of DBConnRef instances.
901     * @throws DBReadOnlyRoleError
902     */
903    protected function assertRoleAllowsWrites() {
904        // DB_PRIMARY is "prima facie" writable
905        if ( $this->role !== ILoadBalancer::DB_PRIMARY ) {
906            throw new DBReadOnlyRoleError( $this->conn, "Cannot write with role DB_REPLICA" );
907        }
908    }
909
910    /**
911     * @return DBUnexpectedError
912     */
913    protected function getDomainChangeException() {
914        return new DBUnexpectedError(
915            $this,
916            "Cannot directly change the selected DB domain; any underlying connection handle " .
917            "is owned by a LoadBalancer instance and possibly shared with other callers. " .
918            "LoadBalancer automatically manages DB domain re-selection of unused handles."
919        );
920    }
921
922    /**
923     * @param int $i Specific or virtual (DB_PRIMARY/DB_REPLICA) server index
924     * @return int|mixed
925     */
926    protected function normalizeServerIndex( $i ) {
927        return ( $i === ILoadBalancer::DB_PRIMARY ) ? $this->lb->getWriterIndex() : $i;
928    }
929}