1<?php
2
3namespace Wikimedia\Rdbms;
4
5use InvalidArgumentException;
6
7/**
8 * Helper class used for automatically marking an IDatabase connection as reusable (once it no
9 * longer matters which DB domain is selected) and for deferring the actual network connection
10 *
11 * This uses an RAII-style pattern where calling code is expected to keep the returned reference
12 * handle as a function variable that falls out of scope when no longer needed. This avoids the
13 * need for matching reuseConnection() calls for every "return" statement as well as the tedious
14 * use of try/finally.
15 *
16 * @par Example:
17 * @code
18 *     function getRowData() {
19 *         $conn = $this->lb->getConnectedRef( DB_REPLICA );
20 *         $row = $conn->select( ... );
21 *         return $row ? (array)$row : false;
22 *         // $conn falls out of scope and $this->lb->reuseConnection() gets called
23 *     }
24 * @endcode
25 *
26 * @ingroup Database
27 * @since 1.22
28 */
29class DBConnRef implements IDatabase {
30	/** @var ILoadBalancer */
31	private $lb;
32	/** @var Database|null Live connection handle */
33	private $conn;
34	/** @var array N-tuple of (server index, group, DatabaseDomain|string) */
35	private $params;
36	/** @var int One of DB_PRIMARY/DB_REPLICA */
37	private $role;
38
39	private const FLD_INDEX = 0;
40	private const FLD_GROUP = 1;
41	private const FLD_DOMAIN = 2;
42	private const FLD_FLAGS = 3;
43
44	/**
45	 * @param ILoadBalancer $lb Connection manager for $conn
46	 * @param IDatabase|array $conn Database or (server index, query groups, domain, flags)
47	 * @param int $role The type of connection asked for; one of DB_PRIMARY/DB_REPLICA
48	 * @internal This method should not be called outside of LoadBalancer
49	 */
50	public function __construct( ILoadBalancer $lb, $conn, $role ) {
51		$this->lb = $lb;
52		$this->role = $role;
53		if ( $conn instanceof IDatabase && !( $conn instanceof DBConnRef ) ) {
54			$this->conn = $conn; // live handle
55		} elseif ( is_array( $conn ) && count( $conn ) >= 4 && $conn[self::FLD_DOMAIN] !== false ) {
56			$this->params = $conn;
57		} else {
58			throw new InvalidArgumentException( "Missing lazy connection arguments." );
59		}
60	}
61
62	public function __call( $name, array $arguments ) {
63		if ( $this->conn === null ) {
64			list( $index, $groups, $wiki, $flags ) = $this->params;
65			$this->conn = $this->lb->getConnection( $index, $groups, $wiki, $flags );
66		}
67
68		return $this->conn->$name( ...$arguments );
69	}
70
71	/**
72	 * @return int DB_PRIMARY when this *requires* the primary DB, otherwise DB_REPLICA
73	 * @since 1.33
74	 */
75	public function getReferenceRole() {
76		return $this->role;
77	}
78
79	public function getServerInfo() {
80		return $this->__call( __FUNCTION__, func_get_args() );
81	}
82
83	public function getTopologyBasedServerId() {
84		return $this->__call( __FUNCTION__, func_get_args() );
85	}
86
87	public function getTopologyRole() {
88		return $this->__call( __FUNCTION__, func_get_args() );
89	}
90
91	public function getTopologyRootPrimary() {
92		return $this->__call( __FUNCTION__, func_get_args() );
93	}
94
95	public function getTopologyRootMaster() {
96		wfDeprecated( __METHOD__, '1.37' );
97		return $this->__call( __FUNCTION__, func_get_args() );
98	}
99
100	public function trxLevel() {
101		return $this->__call( __FUNCTION__, func_get_args() );
102	}
103
104	public function trxTimestamp() {
105		return $this->__call( __FUNCTION__, func_get_args() );
106	}
107
108	public function explicitTrxActive() {
109		return $this->__call( __FUNCTION__, func_get_args() );
110	}
111
112	public function assertNoOpenTransactions() {
113		return $this->__call( __FUNCTION__, func_get_args() );
114	}
115
116	public function tablePrefix( $prefix = null ) {
117		if ( $this->conn === null && $prefix === null ) {
118			$domain = DatabaseDomain::newFromId( $this->params[self::FLD_DOMAIN] );
119			// Avoid triggering a database connection
120			return $domain->getTablePrefix();
121		} elseif ( $this->conn !== null && $prefix === null ) {
122			// This will just return the prefix
123			return $this->__call( __FUNCTION__, func_get_args() );
124		}
125		// Disallow things that might confuse the LoadBalancer tracking
126		throw $this->getDomainChangeException();
127	}
128
129	public function dbSchema( $schema = null ) {
130		if ( $this->conn === null && $schema === null ) {
131			$domain = DatabaseDomain::newFromId( $this->params[self::FLD_DOMAIN] );
132			// Avoid triggering a database connection
133			return $domain->getSchema();
134		} elseif ( $this->conn !== null && $schema === null ) {
135			// This will just return the schema
136			return $this->__call( __FUNCTION__, func_get_args() );
137		}
138		// Disallow things that might confuse the LoadBalancer tracking
139		throw $this->getDomainChangeException();
140	}
141
142	public function getLBInfo( $name = null ) {
143		return $this->__call( __FUNCTION__, func_get_args() );
144	}
145
146	public function setLBInfo( $nameOrArray, $value = null ) {
147		// @phan-suppress-previous-line PhanPluginNeverReturnMethod
148		// Disallow things that might confuse the LoadBalancer tracking
149		throw $this->getDomainChangeException();
150	}
151
152	public function implicitOrderby() {
153		return $this->__call( __FUNCTION__, func_get_args() );
154	}
155
156	public function lastQuery() {
157		return $this->__call( __FUNCTION__, func_get_args() );
158	}
159
160	public function lastDoneWrites() {
161		return $this->__call( __FUNCTION__, func_get_args() );
162	}
163
164	public function writesPending() {
165		return $this->__call( __FUNCTION__, func_get_args() );
166	}
167
168	public function preCommitCallbacksPending() {
169		return $this->__call( __FUNCTION__, func_get_args() );
170	}
171
172	public function writesOrCallbacksPending() {
173		return $this->__call( __FUNCTION__, func_get_args() );
174	}
175
176	public function pendingWriteQueryDuration( $type = self::ESTIMATE_TOTAL ) {
177		return $this->__call( __FUNCTION__, func_get_args() );
178	}
179
180	public function pendingWriteCallers() {
181		return $this->__call( __FUNCTION__, func_get_args() );
182	}
183
184	public function pendingWriteRowsAffected() {
185		return $this->__call( __FUNCTION__, func_get_args() );
186	}
187
188	public function isOpen() {
189		return $this->__call( __FUNCTION__, func_get_args() );
190	}
191
192	public function setFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
193		return $this->__call( __FUNCTION__, func_get_args() );
194	}
195
196	public function clearFlag( $flag, $remember = self::REMEMBER_NOTHING ) {
197		return $this->__call( __FUNCTION__, func_get_args() );
198	}
199
200	public function restoreFlags( $state = self::RESTORE_PRIOR ) {
201		return $this->__call( __FUNCTION__, func_get_args() );
202	}
203
204	public function getFlag( $flag ) {
205		return $this->__call( __FUNCTION__, func_get_args() );
206	}
207
208	public function getProperty( $name ) {
209		return $this->__call( __FUNCTION__, func_get_args() );
210	}
211
212	public function getDomainID() {
213		if ( $this->conn === null ) {
214			$domain = $this->params[self::FLD_DOMAIN];
215			// Avoid triggering a database connection
216			return $domain instanceof DatabaseDomain ? $domain->getId() : $domain;
217		}
218
219		return $this->__call( __FUNCTION__, func_get_args() );
220	}
221
222	public function getType() {
223		if ( $this->conn === null ) {
224			// Avoid triggering a database connection
225			if ( $this->params[self::FLD_INDEX] === ILoadBalancer::DB_PRIMARY ) {
226				$index = $this->lb->getWriterIndex();
227			} else {
228				$index = $this->params[self::FLD_INDEX];
229			}
230			if ( $index >= 0 ) {
231				// In theory, if $index is DB_REPLICA, the type could vary
232				return $this->lb->getServerType( $index );
233			}
234		}
235
236		return $this->__call( __FUNCTION__, func_get_args() );
237	}
238
239	public function fetchObject( $res ) {
240		return $this->__call( __FUNCTION__, func_get_args() );
241	}
242
243	public function fetchRow( $res ) {
244		return $this->__call( __FUNCTION__, func_get_args() );
245	}
246
247	public function numRows( $res ) {
248		return $this->__call( __FUNCTION__, func_get_args() );
249	}
250
251	public function numFields( $res ) {
252		return $this->__call( __FUNCTION__, func_get_args() );
253	}
254
255	public function fieldName( $res, $n ) {
256		return $this->__call( __FUNCTION__, func_get_args() );
257	}
258
259	public function insertId() {
260		return $this->__call( __FUNCTION__, func_get_args() );
261	}
262
263	public function dataSeek( $res, $row ) {
264		return $this->__call( __FUNCTION__, func_get_args() );
265	}
266
267	public function lastErrno() {
268		return $this->__call( __FUNCTION__, func_get_args() );
269	}
270
271	public function lastError() {
272		return $this->__call( __FUNCTION__, func_get_args() );
273	}
274
275	public function affectedRows() {
276		return $this->__call( __FUNCTION__, func_get_args() );
277	}
278
279	public function getSoftwareLink() {
280		return $this->__call( __FUNCTION__, func_get_args() );
281	}
282
283	public function getServerVersion() {
284		return $this->__call( __FUNCTION__, func_get_args() );
285	}
286
287	public function close( $fname = __METHOD__, $owner = null ) {
288		// @phan-suppress-previous-line PhanPluginNeverReturnMethod
289		throw new DBUnexpectedError( $this->conn, 'Cannot close shared connection.' );
290	}
291
292	public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
293		if ( $this->role !== ILoadBalancer::DB_PRIMARY ) {
294			$flags |= IDatabase::QUERY_REPLICA_ROLE;
295		}
296
297		return $this->__call( __FUNCTION__, [ $sql, $fname, $flags ] );
298	}
299
300	public function freeResult( $res ) {
301		return $this->__call( __FUNCTION__, func_get_args() );
302	}
303
304	public function newSelectQueryBuilder() {
305		return $this->__call( __FUNCTION__, func_get_args() );
306	}
307
308	public function selectField(
309		$table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
310	) {
311		return $this->__call( __FUNCTION__, func_get_args() );
312	}
313
314	public function selectFieldValues(
315		$table, $var, $cond = '', $fname = __METHOD__, $options = [], $join_conds = []
316	): array {
317		return $this->__call( __FUNCTION__, func_get_args() );
318	}
319
320	public function select(
321		$table, $vars, $conds = '', $fname = __METHOD__,
322		$options = [], $join_conds = []
323	) {
324		return $this->__call( __FUNCTION__, func_get_args() );
325	}
326
327	public function selectSQLText(
328		$table, $vars, $conds = '', $fname = __METHOD__,
329		$options = [], $join_conds = []
330	) {
331		return $this->__call( __FUNCTION__, func_get_args() );
332	}
333
334	public function limitResult( $sql, $limit, $offset = false ) {
335		return $this->__call( __FUNCTION__, func_get_args() );
336	}
337
338	public function selectRow(
339		$table, $vars, $conds, $fname = __METHOD__,
340		$options = [], $join_conds = []
341	) {
342		return $this->__call( __FUNCTION__, func_get_args() );
343	}
344
345	public function estimateRowCount(
346		$tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
347	) {
348		return $this->__call( __FUNCTION__, func_get_args() );
349	}
350
351	public function selectRowCount(
352		$tables, $vars = '*', $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
353	) {
354		return $this->__call( __FUNCTION__, func_get_args() );
355	}
356
357	public function lockForUpdate(
358		$table, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
359	) {
360		$this->assertRoleAllowsWrites();
361
362		return $this->__call( __FUNCTION__, func_get_args() );
363	}
364
365	public function fieldExists( $table, $field, $fname = __METHOD__ ) {
366		return $this->__call( __FUNCTION__, func_get_args() );
367	}
368
369	public function indexExists( $table, $index, $fname = __METHOD__ ) {
370		return $this->__call( __FUNCTION__, func_get_args() );
371	}
372
373	public function tableExists( $table, $fname = __METHOD__ ) {
374		return $this->__call( __FUNCTION__, func_get_args() );
375	}
376
377	public function insert( $table, $rows, $fname = __METHOD__, $options = [] ) {
378		$this->assertRoleAllowsWrites();
379
380		return $this->__call( __FUNCTION__, func_get_args() );
381	}
382
383	public function update( $table, $set, $conds, $fname = __METHOD__, $options = [] ) {
384		$this->assertRoleAllowsWrites();
385
386		return $this->__call( __FUNCTION__, func_get_args() );
387	}
388
389	public function makeList( array $a, $mode = self::LIST_COMMA ) {
390		return $this->__call( __FUNCTION__, func_get_args() );
391	}
392
393	public function makeWhereFrom2d( $data, $baseKey, $subKey ) {
394		return $this->__call( __FUNCTION__, func_get_args() );
395	}
396
397	public function aggregateValue( $valuedata, $valuename = 'value' ) {
398		return $this->__call( __FUNCTION__, func_get_args() );
399	}
400
401	public function bitNot( $field ) {
402		return $this->__call( __FUNCTION__, func_get_args() );
403	}
404
405	public function bitAnd( $fieldLeft, $fieldRight ) {
406		return $this->__call( __FUNCTION__, func_get_args() );
407	}
408
409	public function bitOr( $fieldLeft, $fieldRight ) {
410		return $this->__call( __FUNCTION__, func_get_args() );
411	}
412
413	public function buildConcat( $stringList ) {
414		return $this->__call( __FUNCTION__, func_get_args() );
415	}
416
417	public function buildGroupConcatField(
418		$delim, $table, $field, $conds = '', $join_conds = []
419	) {
420		return $this->__call( __FUNCTION__, func_get_args() );
421	}
422
423	public function buildGreatest( $fields, $values ) {
424		return $this->__call( __FUNCTION__, func_get_args() );
425	}
426
427	public function buildLeast( $fields, $values ) {
428		return $this->__call( __FUNCTION__, func_get_args() );
429	}
430
431	public function buildSubstring( $input, $startPosition, $length = null ) {
432		return $this->__call( __FUNCTION__, func_get_args() );
433	}
434
435	public function buildStringCast( $field ) {
436		return $this->__call( __FUNCTION__, func_get_args() );
437	}
438
439	public function buildIntegerCast( $field ) {
440		return $this->__call( __FUNCTION__, func_get_args() );
441	}
442
443	public function buildSelectSubquery(
444		$table, $vars, $conds = '', $fname = __METHOD__,
445		$options = [], $join_conds = []
446	) {
447		return $this->__call( __FUNCTION__, func_get_args() );
448	}
449
450	public function databasesAreIndependent() {
451		return $this->__call( __FUNCTION__, func_get_args() );
452	}
453
454	public function selectDB( $db ) {
455		// @phan-suppress-previous-line PhanPluginNeverReturnMethod
456		// Disallow things that might confuse the LoadBalancer tracking
457		throw $this->getDomainChangeException();
458	}
459
460	public function selectDomain( $domain ) {
461		// @phan-suppress-previous-line PhanPluginNeverReturnMethod
462		// Disallow things that might confuse the LoadBalancer tracking
463		throw $this->getDomainChangeException();
464	}
465
466	public function getDBname() {
467		if ( $this->conn === null ) {
468			$domain = DatabaseDomain::newFromId( $this->params[self::FLD_DOMAIN] );
469			// Avoid triggering a database connection
470			return $domain->getDatabase();
471		}
472
473		return $this->__call( __FUNCTION__, func_get_args() );
474	}
475
476	public function getServer() {
477		return $this->__call( __FUNCTION__, func_get_args() );
478	}
479
480	public function getServerName() {
481		return $this->__call( __FUNCTION__, func_get_args() );
482	}
483
484	public function addQuotes( $s ) {
485		return $this->__call( __FUNCTION__, func_get_args() );
486	}
487
488	public function addIdentifierQuotes( $s ) {
489		return $this->__call( __FUNCTION__, func_get_args() );
490	}
491
492	public function buildLike( $param, ...$params ) {
493		return $this->__call( __FUNCTION__, func_get_args() );
494	}
495
496	public function anyChar() {
497		return $this->__call( __FUNCTION__, func_get_args() );
498	}
499
500	public function anyString() {
501		return $this->__call( __FUNCTION__, func_get_args() );
502	}
503
504	public function nextSequenceValue( $seqName ) {
505		$this->assertRoleAllowsWrites();
506
507		return $this->__call( __FUNCTION__, func_get_args() );
508	}
509
510	public function replace( $table, $uniqueKeys, $rows, $fname = __METHOD__ ) {
511		$this->assertRoleAllowsWrites();
512
513		return $this->__call( __FUNCTION__, func_get_args() );
514	}
515
516	public function upsert(
517		$table, array $rows, $uniqueKeys, array $set, $fname = __METHOD__
518	) {
519		$this->assertRoleAllowsWrites();
520
521		return $this->__call( __FUNCTION__, func_get_args() );
522	}
523
524	public function deleteJoin(
525		$delTable, $joinTable, $delVar, $joinVar, $conds, $fname = __METHOD__
526	) {
527		$this->assertRoleAllowsWrites();
528
529		return $this->__call( __FUNCTION__, func_get_args() );
530	}
531
532	public function delete( $table, $conds, $fname = __METHOD__ ) {
533		$this->assertRoleAllowsWrites();
534
535		return $this->__call( __FUNCTION__, func_get_args() );
536	}
537
538	public function insertSelect(
539		$destTable, $srcTable, $varMap, $conds,
540		$fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
541	) {
542		$this->assertRoleAllowsWrites();
543
544		return $this->__call( __FUNCTION__, func_get_args() );
545	}
546
547	public function unionSupportsOrderAndLimit() {
548		return $this->__call( __FUNCTION__, func_get_args() );
549	}
550
551	public function unionQueries( $sqls, $all ) {
552		return $this->__call( __FUNCTION__, func_get_args() );
553	}
554
555	public function unionConditionPermutations(
556		$table, $vars, array $permute_conds, $extra_conds = '', $fname = __METHOD__,
557		$options = [], $join_conds = []
558	) {
559		return $this->__call( __FUNCTION__, func_get_args() );
560	}
561
562	public function conditional( $cond, $caseTrueExpression, $caseFalseExpression ) {
563		return $this->__call( __FUNCTION__, func_get_args() );
564	}
565
566	public function strreplace( $orig, $old, $new ) {
567		return $this->__call( __FUNCTION__, func_get_args() );
568	}
569
570	public function getServerUptime() {
571		return $this->__call( __FUNCTION__, func_get_args() );
572	}
573
574	public function wasDeadlock() {
575		return $this->__call( __FUNCTION__, func_get_args() );
576	}
577
578	public function wasLockTimeout() {
579		return $this->__call( __FUNCTION__, func_get_args() );
580	}
581
582	public function wasConnectionLoss() {
583		return $this->__call( __FUNCTION__, func_get_args() );
584	}
585
586	public function wasReadOnlyError() {
587		return $this->__call( __FUNCTION__, func_get_args() );
588	}
589
590	public function wasErrorReissuable() {
591		return $this->__call( __FUNCTION__, func_get_args() );
592	}
593
594	public function primaryPosWait( DBPrimaryPos $pos, $timeout ) {
595		return $this->__call( __FUNCTION__, func_get_args() );
596	}
597
598	public function masterPosWait( DBPrimaryPos $pos, $timeout ) {
599		wfDeprecated( __METHOD__, '1.37' );
600		return $this->__call( __FUNCTION__, func_get_args() );
601	}
602
603	public function getReplicaPos() {
604		return $this->__call( __FUNCTION__, func_get_args() );
605	}
606
607	public function getPrimaryPos() {
608		return $this->__call( __FUNCTION__, func_get_args() );
609	}
610
611	public function getMasterPos() {
612		wfDeprecated( __METHOD__, '1.37' );
613		return $this->getPrimaryPos();
614	}
615
616	public function serverIsReadOnly() {
617		return $this->__call( __FUNCTION__, func_get_args() );
618	}
619
620	public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
621		// DB_REPLICA role: caller might want to refresh cache after a REPEATABLE-READ snapshot
622		return $this->__call( __FUNCTION__, func_get_args() );
623	}
624
625	public function onTransactionCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
626		// DB_REPLICA role: caller might want to refresh cache after a REPEATABLE-READ snapshot
627		return $this->__call( __FUNCTION__, func_get_args() );
628	}
629
630	public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
631		return $this->onTransactionCommitOrIdle( $callback, $fname );
632	}
633
634	public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
635		// DB_REPLICA role: caller might want to refresh cache after a cache mutex is released
636		return $this->__call( __FUNCTION__, func_get_args() );
637	}
638
639	public function onAtomicSectionCancel( callable $callback, $fname = __METHOD__ ) {
640		return $this->__call( __FUNCTION__, func_get_args() );
641	}
642
643	public function setTransactionListener( $name, callable $callback = null ) {
644		return $this->__call( __FUNCTION__, func_get_args() );
645	}
646
647	public function startAtomic(
648		$fname = __METHOD__, $cancelable = IDatabase::ATOMIC_NOT_CANCELABLE
649	) {
650		// Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
651		return $this->__call( __FUNCTION__, func_get_args() );
652	}
653
654	public function endAtomic( $fname = __METHOD__ ) {
655		// Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
656		return $this->__call( __FUNCTION__, func_get_args() );
657	}
658
659	public function cancelAtomic( $fname = __METHOD__, AtomicSectionIdentifier $sectionId = null ) {
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 doAtomicSection(
665		$fname, callable $callback, $cancelable = self::ATOMIC_NOT_CANCELABLE
666	) {
667		// Don't call assertRoleAllowsWrites(); caller might want a REPEATABLE-READ snapshot
668		return $this->__call( __FUNCTION__, func_get_args() );
669	}
670
671	public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
672		return $this->__call( __FUNCTION__, func_get_args() );
673	}
674
675	public function commit( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
676		return $this->__call( __FUNCTION__, func_get_args() );
677	}
678
679	public function rollback( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
680		return $this->__call( __FUNCTION__, func_get_args() );
681	}
682
683	public function flushSnapshot( $fname = __METHOD__, $flush = self::FLUSHING_ONE ) {
684		return $this->__call( __FUNCTION__, func_get_args() );
685	}
686
687	public function timestamp( $ts = 0 ) {
688		return $this->__call( __FUNCTION__, func_get_args() );
689	}
690
691	public function timestampOrNull( $ts = null ) {
692		return $this->__call( __FUNCTION__, func_get_args() );
693	}
694
695	public function ping( &$rtt = null ) {
696		return func_num_args()
697			? $this->__call( __FUNCTION__, [ &$rtt ] )
698			: $this->__call( __FUNCTION__, [] ); // method cares about null vs missing
699	}
700
701	public function getLag() {
702		return $this->__call( __FUNCTION__, func_get_args() );
703	}
704
705	public function getSessionLagStatus() {
706		return $this->__call( __FUNCTION__, func_get_args() );
707	}
708
709	public function maxListLen() {
710		return $this->__call( __FUNCTION__, func_get_args() );
711	}
712
713	public function encodeBlob( $b ) {
714		return $this->__call( __FUNCTION__, func_get_args() );
715	}
716
717	public function decodeBlob( $b ) {
718		return $this->__call( __FUNCTION__, func_get_args() );
719	}
720
721	public function setSessionOptions( array $options ) {
722		$this->__call( __FUNCTION__, func_get_args() );
723	}
724
725	public function setSchemaVars( $vars ) {
726		return $this->__call( __FUNCTION__, func_get_args() );
727	}
728
729	public function lockIsFree( $lockName, $method ) {
730		$this->assertRoleAllowsWrites();
731
732		return $this->__call( __FUNCTION__, func_get_args() );
733	}
734
735	public function lock( $lockName, $method, $timeout = 5, $flags = 0 ) {
736		$this->assertRoleAllowsWrites();
737
738		return $this->__call( __FUNCTION__, func_get_args() );
739	}
740
741	public function unlock( $lockName, $method ) {
742		$this->assertRoleAllowsWrites();
743
744		return $this->__call( __FUNCTION__, func_get_args() );
745	}
746
747	public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
748		$this->assertRoleAllowsWrites();
749
750		return $this->__call( __FUNCTION__, func_get_args() );
751	}
752
753	public function namedLocksEnqueue() {
754		return $this->__call( __FUNCTION__, func_get_args() );
755	}
756
757	public function getInfinity() {
758		return $this->__call( __FUNCTION__, func_get_args() );
759	}
760
761	public function encodeExpiry( $expiry ) {
762		return $this->__call( __FUNCTION__, func_get_args() );
763	}
764
765	public function decodeExpiry( $expiry, $format = TS_MW ) {
766		return $this->__call( __FUNCTION__, func_get_args() );
767	}
768
769	public function setBigSelects( $value = true ) {
770		return $this->__call( __FUNCTION__, func_get_args() );
771	}
772
773	public function isReadOnly() {
774		return $this->__call( __FUNCTION__, func_get_args() );
775	}
776
777	public function setTableAliases( array $aliases ) {
778		return $this->__call( __FUNCTION__, func_get_args() );
779	}
780
781	public function setIndexAliases( array $aliases ) {
782		return $this->__call( __FUNCTION__, func_get_args() );
783	}
784
785	public function __toString() {
786		if ( $this->conn === null ) {
787			return $this->getType() . ' object #' . spl_object_id( $this );
788		}
789
790		return $this->__call( __FUNCTION__, func_get_args() );
791	}
792
793	/**
794	 * Error out if the role is not DB_PRIMARY
795	 *
796	 * Note that the underlying connection may or may not itself be read-only.
797	 * It could even be to a writable primary (both server-side and to the application).
798	 * This error is meant for the case when a DB_REPLICA handle was requested but a
799	 * a write was attempted on that handle regardless.
800	 *
801	 * In configurations where the primary DB has some generic read load or is the only server,
802	 * DB_PRIMARY/DB_REPLICA will sometimes (or always) use the same connection to the primary DB.
803	 * This does not effect the role of DBConnRef instances.
804	 * @throws DBReadOnlyRoleError
805	 */
806	protected function assertRoleAllowsWrites() {
807		// DB_PRIMARY is "prima facie" writable
808		if ( $this->role !== ILoadBalancer::DB_PRIMARY ) {
809			throw new DBReadOnlyRoleError( $this->conn, "Cannot write with role DB_REPLICA" );
810		}
811	}
812
813	/**
814	 * @return DBUnexpectedError
815	 */
816	protected function getDomainChangeException() {
817		return new DBUnexpectedError(
818			$this,
819			"Cannot directly change the selected DB domain; any underlying connection handle " .
820			"is owned by a LoadBalancer instance and possibly shared with other callers. " .
821			"LoadBalancer automatically manages DB domain re-selection of unused handles."
822		);
823	}
824
825	/**
826	 * Clean up the connection when out of scope
827	 */
828	public function __destruct() {
829		if ( $this->conn ) {
830			$this->lb->reuseConnection( $this->conn );
831		}
832	}
833}
834
835/**
836 * @since 1.22
837 * @deprecated since 1.29
838 */
839class_alias( DBConnRef::class, 'DBConnRef' );
840