Completed
Push — master ( 617702...1dd00f )
by mw
36:38 queued 19:59
created

Database::disableTransactions()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 6
ccs 5
cts 5
cp 1
crap 3
rs 9.4285
1
<?php
2
3
namespace SMW\MediaWiki;
4
5
use DBError;
6
use ResultWrapper;
7
use RuntimeException;
8
use SMW\DBConnectionProvider;
9
use UnexpectedValueException;
10
11
/**
12
 * This adapter class covers MW DB specific operations. Changes to the
13
 * interface are likely therefore this class should not be used other than by
14
 * SMW itself.
15
 *
16
 * @license GNU GPL v2+
17
 * @since 1.9
18
 *
19
 * @author mwjames
20
 */
21
class Database {
22
23
	/**
24
	 * @var DBConnectionProvider
25
	 */
26
	private $readConnectionProvider = null;
27
28
	/**
29
	 * @var DBConnectionProvider
30
	 */
31
	private $writeConnectionProvider = null;
32
33
	/**
34
	 * @var array
35
	 */
36
	private $transactionQueue = array();
37
38
	/**
39
	 * @var string
40
	 */
41
	private $dbPrefix = '';
42
43
	/**
44
	 * @var string
45
	 */
46
	private $disabledTransactions = false;
47
48
	/**
49
	 * @since 1.9
50
	 *
51
	 * @param DBConnectionProvider $readConnectionProvider
52
	 * @param DBConnectionProvider|null $writeConnectionProvider
53
	 */
54 242
	public function __construct( DBConnectionProvider $readConnectionProvider, DBConnectionProvider $writeConnectionProvider = null ) {
55 242
		$this->readConnectionProvider = $readConnectionProvider;
56 242
		$this->writeConnectionProvider = $writeConnectionProvider;
57 242
	}
58
59
	/**
60
	 * @see DatabaseBase::getType
61
	 *
62
	 * @since 1.9.1
63
	 *
64
	 * @return string
65
	 */
66 247
	public function getType() {
67 247
		return $this->readConnection()->getType();
68
	}
69
70
	/**
71
	 * @since 2.1
72
	 *
73
	 * @param string $dbPrefix
74
	 */
75 227
	public function setDBPrefix( $dbPrefix ) {
76 227
		$this->dbPrefix = $dbPrefix;
77 227
	}
78
79
	/**
80
	 * @see DatabaseBase::tableName
81
	 *
82
	 * @since 1.9.0.2
83
	 *
84
	 * @param string $tableName
85
	 *
86
	 * @return string
87
	 */
88 238
	public function tableName( $tableName ) {
89
90 238
		if ( $this->getType() === 'sqlite' ) {
91 1
			return $this->dbPrefix . $tableName;
92
		}
93
94 237
		return $this->readConnection()->tableName( $tableName );
95
	}
96
97
	/**
98
	 * @see DatabaseBase::addQuotes
99
	 *
100
	 * @since 1.9.0.2
101
	 *
102
	 * @param string $tableName
0 ignored issues
show
Bug introduced by
There is no parameter named $tableName. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
103
	 *
104
	 * @return string
105
	 */
106 232
	public function addQuotes( $value ) {
107 232
		return $this->readConnection()->addQuotes( $value );
108
	}
109
110
	/**
111
	 * @see DatabaseBase::fetchObject
112
	 *
113
	 * @since 1.9.1
114
	 *
115
	 * @param ResultWrapper $res
116
	 *
117
	 * @return string
118
	 */
119 136
	public function fetchObject( $res ) {
120 136
		return $this->readConnection()->fetchObject( $res );
121
	}
122
123
	/**
124
	 * @see DatabaseBase::numRows
125
	 *
126
	 * @since 1.9.0.2
127
	 *
128
	 * @param mixed $results
129
	 *
130
	 * @return integer
131
	 */
132 16
	public function numRows( $results ) {
133 16
		return $this->readConnection()->numRows( $results );
134
	}
135
136
	/**
137
	 * @see DatabaseBase::freeResult
138
	 *
139
	 * @since 1.9.0.2
140
	 *
141
	 * @param ResultWrapper $res
142
	 */
143 231
	public function freeResult( $res ) {
144 231
		$this->readConnection()->freeResult( $res );
145 231
	}
146
147
	/**
148
	 * @see DatabaseBase::select
149
	 *
150
	 * @since 1.9.0.2
151
	 *
152
	 * @param string $tableName
153
	 * @param $fields
154
	 * @param $conditions
155
	 * @param array $options
156
	 * @param array $joinConditions
157
	 *
158
	 * @return ResultWrapper
159
	 * @throws UnexpectedValueException
160
	 */
161 236
	public function select( $tableName, $fields, $conditions = '', $fname, array $options = array(), $joinConditions = array() ) {
162
163 236
		$tablePrefix = null;
164
165
		// MW's SQLite implementation adds an auto prefix to the tableName but
166
		// not to the conditions and since ::tableName will handle prefixing
167
		// consistently ensure that the select doesn't add an extra prefix
168 236
		if ( $this->getType() === 'sqlite' ) {
169
			$tablePrefix = $this->readConnection()->tablePrefix( '' );
170
171
			if ( isset( $options['ORDER BY'] ) ) {
172
				$options['ORDER BY'] = str_replace( 'RAND', 'RANDOM', $options['ORDER BY'] );
173
			}
174
		}
175
176
		try {
177 236
			$results = $this->readConnection()->select(
178
				$tableName,
179
				$fields,
180
				$conditions,
181
				$fname,
182
				$options,
183
				$joinConditions
184
			);
185
		} catch  ( DBError $e ) {
0 ignored issues
show
Bug introduced by
The class DBError does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
186
			throw new RuntimeException (
187
				$e->getMessage() . "\n" .
188
				$e->getTraceAsString()
189
			);
190
		}
191
192 236
		if ( $tablePrefix !== null ) {
193
			$this->readConnection()->tablePrefix( $tablePrefix );
194
		}
195
196 236
		if ( $results instanceof ResultWrapper ) {
0 ignored issues
show
Bug introduced by
The class ResultWrapper does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
197 235
			return $results;
198
		}
199
200 1
		throw new UnexpectedValueException (
201
			'Expected a ResultWrapper for ' . "\n" .
202 1
			$tableName . "\n" .
203 1
			$fields . "\n" .
204 1
			$conditions
205
		);
206
	}
207
208
	/**
209
	 * @see DatabaseBase::query
210
	 *
211
	 * @since 1.9.1
212
	 *
213
	 * @param string $sql
214
	 * @param $fname
215
	 * @param $ignoreException
216
	 *
217
	 * @return ResultWrapper
218
	 * @throws RuntimeException
219
	 */
220 52
	public function query( $sql, $fname = __METHOD__, $ignoreException = false ) {
221
222 52
		if ( $this->getType() !== 'postgres' ) {
223 52
			$sql = str_replace( '@INT', '', $sql );
224
		}
225
226 52
		if ( $this->getType() == 'postgres' ) {
227
			$sql = str_replace( '@INT', '::integer', $sql );
228
			$sql = str_replace( 'IGNORE', '', $sql );
229
			$sql = str_replace( 'DROP TEMPORARY TABLE', 'DROP TABLE IF EXISTS', $sql );
230
			$sql = str_replace( 'RAND()', ( strpos( $sql, 'DISTINCT' ) !== false ? '' : 'RANDOM()' ), $sql );
231
		}
232
233 52
		if ( $this->getType() == 'sqlite' ) {
234 4
			$sql = str_replace( 'IGNORE', '', $sql );
235 4
			$sql = str_replace( 'TEMPORARY', 'TEMP', $sql );
236 4
			$sql = str_replace( 'ENGINE=MEMORY', '', $sql );
237 4
			$sql = str_replace( 'DROP TEMP', 'DROP', $sql );
238 4
			$sql = str_replace( 'TRUNCATE TABLE', 'DELETE FROM', $sql );
239 4
			$sql = str_replace( 'RAND', 'RANDOM', $sql );
240
		}
241
242
		try {
243 52
			$results = $this->writeConnection()->query(
244
				$sql,
245
				$fname,
246
				$ignoreException
247
			);
248 1
		} catch ( DBError $e ) {
0 ignored issues
show
Bug introduced by
The class DBError does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
249
			throw new RuntimeException (
250
				$e->getMessage() . "\n" .
251
				$e->getTraceAsString()
252
			);
253
		}
254
255 51
		return $results;
256
	}
257
258
	/**
259
	 * @see DatabaseBase::selectRow
260
	 *
261
	 * @since 1.9.1
262
	 */
263 236
	public function selectRow( $table, $vars, $conds, $fname = __METHOD__,
264
		$options = array(), $joinConditions = array() ) {
265
266 236
		return $this->readConnection()->selectRow(
267
			$table,
268
			$vars,
269
			$conds,
270
			$fname,
271
			$options,
272
			$joinConditions
273
		);
274
	}
275
276
	/**
277
	 * @see DatabaseBase::affectedRows
278
	 *
279
	 * @since 1.9.1
280
	 *
281
	 * @return int
282
	 */
283 11
	function affectedRows() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
284 11
		return $this->readConnection()->affectedRows();
285
	}
286
287
	/**
288
	 * @see DatabaseBase::makeSelectOptions
289
	 *
290
	 * @since 1.9.1
291
	 *
292
	 * @param array $options
293
	 *
294
	 * @return array
295
	 */
296 1
	public function makeSelectOptions( $options ) {
297 1
		return $this->readConnection()->makeSelectOptions( $options );
298
	}
299
300
	/**
301
	 * @see DatabaseBase::nextSequenceValue
302
	 *
303
	 * @since 1.9.1
304
	 *
305
	 * @param string $seqName
306
	 *
307
	 * @return int|null
308
	 */
309 217
	public function nextSequenceValue( $seqName ) {
310 217
		return $this->writeConnection()->nextSequenceValue( $seqName );
311
	}
312
313
	/**
314
	 * @see DatabaseBase::insertId
315
	 *
316
	 * @since 1.9.1
317
	 *
318
	 * @return int
319
	 */
320 217
	function insertId() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
321 217
		return (int)$this->writeConnection()->insertId();
322
	}
323
324
	/**
325
	 * @note Use a blank trx profiler to ignore exceptions
326
	 *
327
	 * @since 2.4
328
	 */
329
	function resetTransactionProfiler() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
330
		// MW 1.27
331
		if ( method_exists( $this->writeConnection(), 'setTransactionProfiler' ) ) {
332
			$this->writeConnection()->setTransactionProfiler( new \TransactionProfiler() );
333
		}
334
	}
335
336
	/**
337
	 * @see DatabaseBase::clearFlag
338
	 *
339
	 * @since 2.4
340
	 */
341
	function clearFlag( $flag ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
342
		$this->writeConnection()->clearFlag( $flag );
343
	}
344
345
	/**
346
	 * @note According to notes in SqlBagOStuff.php#L161
347
	 * "... and PostgreSQL needs to know if we are in transaction or not"
348
	 *
349
	 * @since 2.4
350
	 */
351 135
	public function disableTransactions() {
352 135
		if ( $this->writeConnection()->getType() == 'mysql' && $this->writeConnection()->getFlag( DBO_TRX ) ) {
353 1
			$this->writeConnection()->clearFlag( DBO_TRX );
354 1
			$this->disabledTransactions = true;
0 ignored issues
show
Documentation Bug introduced by
The property $disabledTransactions was declared of type string, but true is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
355
		}
356 135
	}
357
358
	/**
359
	 * Can only be used in cases where Database::disableTransactions was
360
	 * successful
361
	 *
362
	 * @since 2.4
363
	 */
364 135
	public function enableTransactions() {
365 135
		if ( $this->disabledTransactions ) {
366 1
			$this->writeConnection()->setFlag( DBO_TRX );
367 1
			$this->disabledTransactions = false;
0 ignored issues
show
Documentation Bug introduced by
The property $disabledTransactions was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
368
		}
369 135
	}
370
371
	/**
372
	 * @see DatabaseBase::insert
373
	 *
374
	 * @since 1.9.1
375
	 */
376 217
	public function insert( $table, $rows, $fname = __METHOD__, $options = array() ) {
377 217
		return $this->writeConnection()->insert( $table, $rows, $fname, $options );
378
	}
379
380
	/**
381
	 * @see DatabaseBase::update
382
	 *
383
	 * @since 1.9.1
384
	 */
385 210
	function update( $table, $values, $conds, $fname = __METHOD__, $options = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
386 210
		return $this->writeConnection()->update( $table, $values, $conds, $fname, $options );
387
	}
388
389
	/**
390
	 * @see DatabaseBase::delete
391
	 *
392
	 * @since 1.9.1
393
	 */
394 198
	public function delete( $table, $conds, $fname = __METHOD__ ) {
395 198
		return $this->writeConnection()->delete( $table, $conds, $fname );
396
	}
397
398
	/**
399
	 * @see DatabaseBase::makeList
400
	 *
401
	 * @since 1.9.1
402
	 */
403 198
	public function makeList( $data, $mode ) {
404 198
		return $this->writeConnection()->makeList( $data, $mode );
405
	}
406
407
	/**
408
	 * @see DatabaseBase::tableExists
409
	 *
410
	 * @since 1.9.1
411
	 *
412
	 * @param string $table
413
	 * @param string $fname
414
	 *
415
	 * @return bool
416
	 */
417 1
	public function tableExists( $table, $fname = __METHOD__ ) {
418 1
		return $this->writeConnection()->tableExists( $table, $fname );
419
	}
420
421
	/**
422
	 * @see DatabaseBase::selectField
423
	 *
424
	 * @since 1.9.2
425
	 */
426 9
	public function selectField( $table, $fieldName, $conditions = '', $fname = __METHOD__, $options = array() ) {
427 9
		return $this->readConnection()->selectField( $table, $fieldName, $conditions, $fname, $options );
428
	}
429
430
	/**
431
	 * @see DatabaseBase::estimateRowCount
432
	 *
433
	 * @since 2.1
434
	 */
435 1
	public function estimateRowCount( $table, $vars = '*', $conditions = '', $fname = __METHOD__, $options = array() ) {
436 1
		return $this->readConnection()->estimateRowCount(
437
			$table,
438
			$vars,
439
			$conditions,
440
			$fname,
441
			$options
442
		);
443
	}
444
445
	/**
446
	 * @since 2.1
447
	 *
448
	 * @param string $fname
449
	 */
450 2
	public function beginTransaction( $fname = __METHOD__  ) {
451
452
		// If a transaction is being added for an uncommitted
453
		// queue entry then a transaction for the same instance
454
		// and name is being omitted
455 2
		if ( isset( $this->transactionQueue[$fname] ) ) {
456
			return;
457
		}
458
459 2
		$this->transactionQueue[$fname] = true;
460
461
		try {
462 2
			$this->writeConnection()->begin( $fname );
463 2
		} catch ( \Exception $exception ) {
464 2
			unset( $this->transactionQueue[$fname] );
465 2
			wfDebug( __METHOD__ . ' exception caused by ' . $exception->getMessage() );
466
		}
467 2
	}
468
469
	/**
470
	 * @since 2.1
471
	 *
472
	 * @param string $fname
473
	 */
474 1
	public function commitTransaction( $fname = __METHOD__  ) {
475
476 1
		if ( !isset( $this->transactionQueue[$fname] ) ) {
477 1
			return;
478
		}
479
480
		try {
481
			$this->writeConnection()->commit( $fname );
482
		} catch ( \Exception $exception ) {
483
			$this->writeConnection()->rollback( $fname );
484
			wfDebug( __METHOD__ . ' rollback because of ' . $exception->getMessage() );
485
		}
486
487
		unset( $this->transactionQueue[$fname] );
488
	}
489
490
	/**
491
	 * @since 2.3
492
	 *
493
	 * @param string $fname
494
	 */
495 224
	public function beginAtomicTransaction( $fname = __METHOD__ ) {
496
497
		// MW 1.23
498 224
		if ( !method_exists( $this->writeConnection(), 'startAtomic' ) ) {
499
			return null;
500
		}
501
502 224
		$this->writeConnection()->startAtomic( $fname );
503 224
	}
504
505
	/**
506
	 * @since 2.3
507
	 *
508
	 * @param string $fname
509
	 */
510 224
	public function endAtomicTransaction( $fname = __METHOD__ ) {
511
512
		// MW 1.23
513 224
		if ( !method_exists( $this->writeConnection(), 'endAtomic' ) ) {
514
			return null;
515
		}
516
517 224
		$this->writeConnection()->endAtomic( $fname );
518 224
	}
519
520
	/**
521
	 * @since 2.3
522
	 *
523
	 * @param callable $callback
524
	 */
525
	public function onTransactionIdle( $callback ) {
526
527
		// FIXME For 1.19 it is an unknown method hence execute without idle
528
		if ( !method_exists( $this->readConnection(), 'onTransactionIdle' ) ) {
529
			return call_user_func( $callback );
530
		}
531
532
		$this->readConnection()->onTransactionIdle( $callback );
533
	}
534
535 259
	private function readConnection() {
536 259
		return $this->readConnectionProvider->getConnection();
537
	}
538
539 236
	private function writeConnection() {
540
541 236
		if ( $this->writeConnectionProvider instanceof DBConnectionProvider ) {
542 234
			return $this->writeConnectionProvider->getConnection();
543
		}
544
545 2
		throw new RuntimeException( 'Expected a DBConnectionProvider instance' );
546
	}
547
548
}
549