Merge "Exclude redirects from Special:Fewestrevisions"
[lhc/web/wiklou.git] / tests / phpunit / includes / libs / rdbms / database / DatabaseTest.php
1 <?php
2
3 use Wikimedia\Rdbms\Database;
4 use Wikimedia\Rdbms\IDatabase;
5 use Wikimedia\Rdbms\DatabaseDomain;
6 use Wikimedia\Rdbms\DatabaseMysqli;
7 use Wikimedia\Rdbms\LBFactorySingle;
8 use Wikimedia\Rdbms\TransactionProfiler;
9 use Wikimedia\TestingAccessWrapper;
10 use Wikimedia\Rdbms\DatabaseSqlite;
11 use Wikimedia\Rdbms\DatabasePostgres;
12 use Wikimedia\Rdbms\DatabaseMssql;
13 use Wikimedia\Rdbms\DBUnexpectedError;
14
15 class DatabaseTest extends PHPUnit\Framework\TestCase {
16 /** @var DatabaseTestHelper */
17 private $db;
18
19 use MediaWikiCoversValidator;
20
21 protected function setUp() {
22 $this->db = new DatabaseTestHelper( __CLASS__ . '::' . $this->getName() );
23 }
24
25 /**
26 * @dataProvider provideAddQuotes
27 * @covers Wikimedia\Rdbms\Database::factory
28 */
29 public function testFactory() {
30 $m = Database::NEW_UNCONNECTED; // no-connect mode
31 $p = [ 'host' => 'localhost', 'user' => 'me', 'password' => 'myself', 'dbname' => 'i' ];
32
33 $this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'mysqli', $p, $m ) );
34 $this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySqli', $p, $m ) );
35 $this->assertInstanceOf( DatabaseMysqli::class, Database::factory( 'MySQLi', $p, $m ) );
36 $this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'postgres', $p, $m ) );
37 $this->assertInstanceOf( DatabasePostgres::class, Database::factory( 'Postgres', $p, $m ) );
38
39 $x = $p + [ 'port' => 10000, 'UseWindowsAuth' => false ];
40 $this->assertInstanceOf( DatabaseMssql::class, Database::factory( 'mssql', $x, $m ) );
41
42 $x = $p + [ 'dbFilePath' => 'some/file.sqlite' ];
43 $this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
44 $x = $p + [ 'dbDirectory' => 'some/file' ];
45 $this->assertInstanceOf( DatabaseSqlite::class, Database::factory( 'sqlite', $x, $m ) );
46 }
47
48 public static function provideAddQuotes() {
49 return [
50 [ null, 'NULL' ],
51 [ 1234, "'1234'" ],
52 [ 1234.5678, "'1234.5678'" ],
53 [ 'string', "'string'" ],
54 [ 'string\'s cause trouble', "'string\'s cause trouble'" ],
55 ];
56 }
57
58 /**
59 * @dataProvider provideAddQuotes
60 * @covers Wikimedia\Rdbms\Database::addQuotes
61 */
62 public function testAddQuotes( $input, $expected ) {
63 $this->assertEquals( $expected, $this->db->addQuotes( $input ) );
64 }
65
66 public static function provideTableName() {
67 // Formatting is mostly ignored since addIdentifierQuotes is abstract.
68 // For testing of addIdentifierQuotes, see actual Database subclas tests.
69 return [
70 'local' => [
71 'tablename',
72 'tablename',
73 'quoted',
74 ],
75 'local-raw' => [
76 'tablename',
77 'tablename',
78 'raw',
79 ],
80 'shared' => [
81 'sharedb.tablename',
82 'tablename',
83 'quoted',
84 [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ],
85 ],
86 'shared-raw' => [
87 'sharedb.tablename',
88 'tablename',
89 'raw',
90 [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ],
91 ],
92 'shared-prefix' => [
93 'sharedb.sh_tablename',
94 'tablename',
95 'quoted',
96 [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ],
97 ],
98 'shared-prefix-raw' => [
99 'sharedb.sh_tablename',
100 'tablename',
101 'raw',
102 [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ],
103 ],
104 'foreign' => [
105 'databasename.tablename',
106 'databasename.tablename',
107 'quoted',
108 ],
109 'foreign-raw' => [
110 'databasename.tablename',
111 'databasename.tablename',
112 'raw',
113 ],
114 ];
115 }
116
117 /**
118 * @dataProvider provideTableName
119 * @covers Wikimedia\Rdbms\Database::tableName
120 */
121 public function testTableName( $expected, $table, $format, array $alias = null ) {
122 if ( $alias ) {
123 $this->db->setTableAliases( [ $table => $alias ] );
124 }
125 $this->assertEquals(
126 $expected,
127 $this->db->tableName( $table, $format ?: 'quoted' )
128 );
129 }
130
131 public function provideTableNamesWithIndexClauseOrJOIN() {
132 return [
133 'one-element array' => [
134 [ 'table' ], [], 'table '
135 ],
136 'comma join' => [
137 [ 'table1', 'table2' ], [], 'table1,table2 '
138 ],
139 'real join' => [
140 [ 'table1', 'table2' ],
141 [ 'table2' => [ 'LEFT JOIN', 't1_id = t2_id' ] ],
142 'table1 LEFT JOIN table2 ON ((t1_id = t2_id))'
143 ],
144 'real join with multiple conditionals' => [
145 [ 'table1', 'table2' ],
146 [ 'table2' => [ 'LEFT JOIN', [ 't1_id = t2_id', 't2_x = \'X\'' ] ] ],
147 'table1 LEFT JOIN table2 ON ((t1_id = t2_id) AND (t2_x = \'X\'))'
148 ],
149 'join with parenthesized group' => [
150 [ 'table1', 'n' => [ 'table2', 'table3' ] ],
151 [
152 'table3' => [ 'JOIN', 't2_id = t3_id' ],
153 'n' => [ 'LEFT JOIN', 't1_id = t2_id' ],
154 ],
155 'table1 LEFT JOIN (table2 JOIN table3 ON ((t2_id = t3_id))) ON ((t1_id = t2_id))'
156 ],
157 'join with degenerate parenthesized group' => [
158 [ 'table1', 'n' => [ 't2' => 'table2' ] ],
159 [
160 'n' => [ 'LEFT JOIN', 't1_id = t2_id' ],
161 ],
162 'table1 LEFT JOIN table2 t2 ON ((t1_id = t2_id))'
163 ],
164 ];
165 }
166
167 /**
168 * @dataProvider provideTableNamesWithIndexClauseOrJOIN
169 * @covers Wikimedia\Rdbms\Database::tableNamesWithIndexClauseOrJOIN
170 */
171 public function testTableNamesWithIndexClauseOrJOIN( $tables, $join_conds, $expect ) {
172 $clause = TestingAccessWrapper::newFromObject( $this->db )
173 ->tableNamesWithIndexClauseOrJOIN( $tables, [], [], $join_conds );
174 $this->assertSame( $expect, $clause );
175 }
176
177 /**
178 * @covers Wikimedia\Rdbms\Database::onTransactionCommitOrIdle
179 * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
180 */
181 public function testTransactionIdle() {
182 $db = $this->db;
183
184 $db->clearFlag( DBO_TRX );
185 $called = false;
186 $flagSet = null;
187 $callback = function ( $trigger, IDatabase $db ) use ( &$flagSet, &$called ) {
188 $called = true;
189 $flagSet = $db->getFlag( DBO_TRX );
190 };
191
192 $db->onTransactionCommitOrIdle( $callback, __METHOD__ );
193 $this->assertTrue( $called, 'Callback reached' );
194 $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
195 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX still default' );
196
197 $flagSet = null;
198 $called = false;
199 $db->startAtomic( __METHOD__ );
200 $db->onTransactionCommitOrIdle( $callback, __METHOD__ );
201 $this->assertFalse( $called, 'Callback not reached during TRX' );
202 $db->endAtomic( __METHOD__ );
203
204 $this->assertTrue( $called, 'Callback reached after COMMIT' );
205 $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
206 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
207
208 $db->clearFlag( DBO_TRX );
209 $db->onTransactionCommitOrIdle(
210 function ( $trigger, IDatabase $db ) {
211 $db->setFlag( DBO_TRX );
212 },
213 __METHOD__
214 );
215 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
216 }
217
218 /**
219 * @covers Wikimedia\Rdbms\Database::onTransactionCommitOrIdle
220 * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
221 */
222 public function testTransactionIdle_TRX() {
223 $db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] );
224 $db->method( 'isOpen' )->willReturn( true );
225 $db->method( 'ping' )->willReturn( true );
226 $db->method( 'getDBname' )->willReturn( '' );
227 $db->setFlag( DBO_TRX );
228
229 $lbFactory = LBFactorySingle::newFromConnection( $db );
230 // Ask for the connection so that LB sets internal state
231 // about this connection being the master connection
232 $lb = $lbFactory->getMainLB();
233 $conn = $lb->openConnection( $lb->getWriterIndex() );
234 $this->assertSame( $db, $conn, 'Same DB instance' );
235 $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' );
236
237 $called = false;
238 $flagSet = null;
239 $callback = function () use ( $db, &$flagSet, &$called ) {
240 $called = true;
241 $flagSet = $db->getFlag( DBO_TRX );
242 };
243
244 $db->onTransactionCommitOrIdle( $callback, __METHOD__ );
245 $this->assertTrue( $called, 'Called when idle if DBO_TRX is set' );
246 $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
247 $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX still default' );
248
249 $called = false;
250 $lbFactory->beginMasterChanges( __METHOD__ );
251 $db->onTransactionCommitOrIdle( $callback, __METHOD__ );
252 $this->assertFalse( $called, 'Not called when lb-transaction is active' );
253
254 $lbFactory->commitMasterChanges( __METHOD__ );
255 $this->assertTrue( $called, 'Called when lb-transaction is committed' );
256
257 $called = false;
258 $lbFactory->beginMasterChanges( __METHOD__ );
259 $db->onTransactionCommitOrIdle( $callback, __METHOD__ );
260 $this->assertFalse( $called, 'Not called when lb-transaction is active' );
261
262 $lbFactory->rollbackMasterChanges( __METHOD__ );
263 $this->assertFalse( $called, 'Not called when lb-transaction is rolled back' );
264
265 $lbFactory->commitMasterChanges( __METHOD__ );
266 $this->assertFalse( $called, 'Not called in next round commit' );
267
268 $db->setFlag( DBO_TRX );
269 try {
270 $db->onTransactionCommitOrIdle( function () {
271 throw new RuntimeException( 'test' );
272 } );
273 $this->fail( "Exception not thrown" );
274 } catch ( RuntimeException $e ) {
275 $this->assertTrue( $db->getFlag( DBO_TRX ) );
276 }
277 }
278
279 /**
280 * @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle
281 * @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
282 */
283 public function testTransactionPreCommitOrIdle() {
284 $db = $this->getMockDB( [ 'isOpen' ] );
285 $db->method( 'isOpen' )->willReturn( true );
286 $db->clearFlag( DBO_TRX );
287
288 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX is not set' );
289
290 $called = false;
291 $db->onTransactionPreCommitOrIdle(
292 function ( IDatabase $db ) use ( &$called ) {
293 $called = true;
294 },
295 __METHOD__
296 );
297 $this->assertTrue( $called, 'Called when idle' );
298
299 $db->begin( __METHOD__ );
300 $called = false;
301 $db->onTransactionPreCommitOrIdle(
302 function ( IDatabase $db ) use ( &$called ) {
303 $called = true;
304 },
305 __METHOD__
306 );
307 $this->assertFalse( $called, 'Not called when transaction is active' );
308 $db->commit( __METHOD__ );
309 $this->assertTrue( $called, 'Called when transaction is committed' );
310 }
311
312 /**
313 * @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle
314 * @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
315 */
316 public function testTransactionPreCommitOrIdle_TRX() {
317 $db = $this->getMockDB( [ 'isOpen', 'ping', 'getDBname' ] );
318 $db->method( 'isOpen' )->willReturn( true );
319 $db->method( 'ping' )->willReturn( true );
320 $db->method( 'getDBname' )->willReturn( 'unittest' );
321 $db->setFlag( DBO_TRX );
322
323 $lbFactory = LBFactorySingle::newFromConnection( $db );
324 // Ask for the connection so that LB sets internal state
325 // about this connection being the master connection
326 $lb = $lbFactory->getMainLB();
327 $conn = $lb->openConnection( $lb->getWriterIndex() );
328 $this->assertSame( $db, $conn, 'Same DB instance' );
329
330 $this->assertFalse( $lb->hasMasterChanges() );
331 $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' );
332 $called = false;
333 $callback = function ( IDatabase $db ) use ( &$called ) {
334 $called = true;
335 };
336 $db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
337 $this->assertTrue( $called, 'Called when idle if DBO_TRX is set' );
338 $called = false;
339 $lbFactory->commitMasterChanges();
340 $this->assertFalse( $called );
341
342 $called = false;
343 $lbFactory->beginMasterChanges( __METHOD__ );
344 $db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
345 $this->assertFalse( $called, 'Not called when lb-transaction is active' );
346 $lbFactory->commitMasterChanges( __METHOD__ );
347 $this->assertTrue( $called, 'Called when lb-transaction is committed' );
348
349 $called = false;
350 $lbFactory->beginMasterChanges( __METHOD__ );
351 $db->onTransactionPreCommitOrIdle( $callback, __METHOD__ );
352 $this->assertFalse( $called, 'Not called when lb-transaction is active' );
353
354 $lbFactory->rollbackMasterChanges( __METHOD__ );
355 $this->assertFalse( $called, 'Not called when lb-transaction is rolled back' );
356
357 $lbFactory->commitMasterChanges( __METHOD__ );
358 $this->assertFalse( $called, 'Not called in next round commit' );
359 }
360
361 /**
362 * @covers Wikimedia\Rdbms\Database::onTransactionResolution
363 * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
364 */
365 public function testTransactionResolution() {
366 $db = $this->db;
367
368 $db->clearFlag( DBO_TRX );
369 $db->begin( __METHOD__ );
370 $called = false;
371 $db->onTransactionResolution( function ( $trigger, IDatabase $db ) use ( &$called ) {
372 $called = true;
373 $db->setFlag( DBO_TRX );
374 } );
375 $db->commit( __METHOD__ );
376 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
377 $this->assertTrue( $called, 'Callback reached' );
378
379 $db->clearFlag( DBO_TRX );
380 $db->begin( __METHOD__ );
381 $called = false;
382 $db->onTransactionResolution( function ( $trigger, IDatabase $db ) use ( &$called ) {
383 $called = true;
384 $db->setFlag( DBO_TRX );
385 } );
386 $db->rollback( __METHOD__ );
387 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
388 $this->assertTrue( $called, 'Callback reached' );
389 }
390
391 /**
392 * @covers Wikimedia\Rdbms\Database::setTransactionListener
393 */
394 public function testTransactionListener() {
395 $db = $this->db;
396
397 $db->setTransactionListener( 'ping', function () use ( $db, &$called ) {
398 $called = true;
399 } );
400
401 $called = false;
402 $db->begin( __METHOD__ );
403 $db->commit( __METHOD__ );
404 $this->assertTrue( $called, 'Callback reached' );
405
406 $called = false;
407 $db->begin( __METHOD__ );
408 $db->commit( __METHOD__ );
409 $this->assertTrue( $called, 'Callback still reached' );
410
411 $called = false;
412 $db->begin( __METHOD__ );
413 $db->rollback( __METHOD__ );
414 $this->assertTrue( $called, 'Callback reached' );
415
416 $db->setTransactionListener( 'ping', null );
417 $called = false;
418 $db->begin( __METHOD__ );
419 $db->commit( __METHOD__ );
420 $this->assertFalse( $called, 'Callback not reached' );
421 }
422
423 /**
424 * Use this mock instead of DatabaseTestHelper for cases where
425 * DatabaseTestHelper is too inflexibile due to mocking too much
426 * or being too restrictive about fname matching (e.g. for tests
427 * that assert behaviour when the name is a mismatch, we need to
428 * catch the error here instead of there).
429 *
430 * @return Database
431 */
432 private function getMockDB( $methods = [] ) {
433 static $abstractMethods = [
434 'fetchAffectedRowCount',
435 'closeConnection',
436 'dataSeek',
437 'doQuery',
438 'fetchObject', 'fetchRow',
439 'fieldInfo', 'fieldName',
440 'getSoftwareLink', 'getServerVersion',
441 'getType',
442 'indexInfo',
443 'insertId',
444 'lastError', 'lastErrno',
445 'numFields', 'numRows',
446 'open',
447 'strencode',
448 'tableExists'
449 ];
450 $db = $this->getMockBuilder( Database::class )
451 ->disableOriginalConstructor()
452 ->setMethods( array_values( array_unique( array_merge(
453 $abstractMethods,
454 $methods
455 ) ) ) )
456 ->getMock();
457 $wdb = TestingAccessWrapper::newFromObject( $db );
458 $wdb->trxProfiler = new TransactionProfiler();
459 $wdb->connLogger = new \Psr\Log\NullLogger();
460 $wdb->queryLogger = new \Psr\Log\NullLogger();
461 $wdb->currentDomain = DatabaseDomain::newUnspecified();
462 return $db;
463 }
464
465 /**
466 * @covers Wikimedia\Rdbms\Database::flushSnapshot
467 */
468 public function testFlushSnapshot() {
469 $db = $this->getMockDB( [ 'isOpen' ] );
470 $db->method( 'isOpen' )->willReturn( true );
471
472 $db->flushSnapshot( __METHOD__ ); // ok
473 $db->flushSnapshot( __METHOD__ ); // ok
474
475 $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
476 $db->query( 'SELECT 1', __METHOD__ );
477 $this->assertTrue( (bool)$db->trxLevel(), "Transaction started." );
478 $db->flushSnapshot( __METHOD__ ); // ok
479 $db->restoreFlags( $db::RESTORE_PRIOR );
480
481 $this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." );
482 }
483
484 /**
485 * @covers Wikimedia\Rdbms\Database::getScopedLockAndFlush
486 * @covers Wikimedia\Rdbms\Database::lock
487 * @covers Wikimedia\Rdbms\Database::unlock
488 * @covers Wikimedia\Rdbms\Database::lockIsFree
489 */
490 public function testGetScopedLock() {
491 $db = $this->getMockDB( [ 'isOpen', 'getDBname' ] );
492 $db->method( 'isOpen' )->willReturn( true );
493 $db->method( 'getDBname' )->willReturn( 'unittest' );
494
495 $this->assertEquals( 0, $db->trxLevel() );
496 $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
497 $this->assertEquals( true, $db->lock( 'x', __METHOD__ ) );
498 $this->assertEquals( false, $db->lockIsFree( 'x', __METHOD__ ) );
499 $this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) );
500 $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
501 $this->assertEquals( 0, $db->trxLevel() );
502
503 $db->setFlag( DBO_TRX );
504 $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
505 $this->assertEquals( true, $db->lock( 'x', __METHOD__ ) );
506 $this->assertEquals( false, $db->lockIsFree( 'x', __METHOD__ ) );
507 $this->assertEquals( true, $db->unlock( 'x', __METHOD__ ) );
508 $this->assertEquals( true, $db->lockIsFree( 'x', __METHOD__ ) );
509 $db->clearFlag( DBO_TRX );
510
511 // Pending writes with DBO_TRX
512 $this->assertEquals( 0, $db->trxLevel() );
513 $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
514 $db->setFlag( DBO_TRX );
515 $db->query( "DELETE FROM test WHERE t = 1" ); // trigger DBO_TRX transaction before lock
516 try {
517 $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
518 $this->fail( "Exception not reached" );
519 } catch ( DBUnexpectedError $e ) {
520 $this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." );
521 $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ), 'Lock not acquired' );
522 }
523 $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
524 // Pending writes without DBO_TRX
525 $db->clearFlag( DBO_TRX );
526 $this->assertEquals( 0, $db->trxLevel() );
527 $this->assertTrue( $db->lockIsFree( 'meow2', __METHOD__ ) );
528 $db->begin( __METHOD__ );
529 $db->query( "DELETE FROM test WHERE t = 1" ); // trigger DBO_TRX transaction before lock
530 try {
531 $lock = $db->getScopedLockAndFlush( 'meow2', __METHOD__, 1 );
532 $this->fail( "Exception not reached" );
533 } catch ( DBUnexpectedError $e ) {
534 $this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." );
535 $this->assertTrue( $db->lockIsFree( 'meow2', __METHOD__ ), 'Lock not acquired' );
536 }
537 $db->rollback( __METHOD__ );
538 // No pending writes, with DBO_TRX
539 $db->setFlag( DBO_TRX );
540 $this->assertEquals( 0, $db->trxLevel() );
541 $this->assertTrue( $db->lockIsFree( 'wuff', __METHOD__ ) );
542 $db->query( "SELECT 1", __METHOD__ );
543 $this->assertEquals( 1, $db->trxLevel() );
544 $lock = $db->getScopedLockAndFlush( 'wuff', __METHOD__, 1 );
545 $this->assertEquals( 0, $db->trxLevel() );
546 $this->assertFalse( $db->lockIsFree( 'wuff', __METHOD__ ), 'Lock already acquired' );
547 $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
548 // No pending writes, without DBO_TRX
549 $db->clearFlag( DBO_TRX );
550 $this->assertEquals( 0, $db->trxLevel() );
551 $this->assertTrue( $db->lockIsFree( 'wuff2', __METHOD__ ) );
552 $db->begin( __METHOD__ );
553 try {
554 $lock = $db->getScopedLockAndFlush( 'wuff2', __METHOD__, 1 );
555 $this->fail( "Exception not reached" );
556 } catch ( DBUnexpectedError $e ) {
557 $this->assertEquals( 1, $db->trxLevel(), "Transaction not committed." );
558 $this->assertFalse( $db->lockIsFree( 'wuff2', __METHOD__ ), 'Lock not acquired' );
559 }
560 $db->rollback( __METHOD__ );
561 }
562
563 /**
564 * @covers Wikimedia\Rdbms\Database::getFlag
565 * @covers Wikimedia\Rdbms\Database::setFlag
566 * @covers Wikimedia\Rdbms\Database::restoreFlags
567 */
568 public function testFlagSetting() {
569 $db = $this->db;
570 $origTrx = $db->getFlag( DBO_TRX );
571 $origSsl = $db->getFlag( DBO_SSL );
572
573 $origTrx
574 ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
575 : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
576 $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
577
578 $origSsl
579 ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
580 : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
581 $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) );
582
583 $db->restoreFlags( $db::RESTORE_INITIAL );
584 $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
585 $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
586
587 $origTrx
588 ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
589 : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
590 $origSsl
591 ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
592 : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
593
594 $db->restoreFlags();
595 $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
596 $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
597
598 $db->restoreFlags();
599 $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
600 $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
601 }
602
603 /**
604 * @expectedException UnexpectedValueException
605 * @covers Wikimedia\Rdbms\Database::setFlag
606 */
607 public function testDBOIgnoreSet() {
608 $db = $this->getMockBuilder( DatabaseMysqli::class )
609 ->disableOriginalConstructor()
610 ->setMethods( null )
611 ->getMock();
612
613 $db->setFlag( Database::DBO_IGNORE );
614 }
615
616 /**
617 * @expectedException UnexpectedValueException
618 * @covers Wikimedia\Rdbms\Database::clearFlag
619 */
620 public function testDBOIgnoreClear() {
621 $db = $this->getMockBuilder( DatabaseMysqli::class )
622 ->disableOriginalConstructor()
623 ->setMethods( null )
624 ->getMock();
625
626 $db->clearFlag( Database::DBO_IGNORE );
627 }
628
629 /**
630 * @covers Wikimedia\Rdbms\Database::tablePrefix
631 * @covers Wikimedia\Rdbms\Database::dbSchema
632 */
633 public function testSchemaAndPrefixMutators() {
634 $ud = DatabaseDomain::newUnspecified();
635
636 $this->assertEquals( $ud->getId(), $this->db->getDomainID() );
637
638 $old = $this->db->tablePrefix();
639 $oldDomain = $this->db->getDomainId();
640 $this->assertInternalType( 'string', $old, 'Prefix is string' );
641 $this->assertSame( $old, $this->db->tablePrefix(), "Prefix unchanged" );
642 $this->assertSame( $old, $this->db->tablePrefix( 'xxx_' ) );
643 $this->assertSame( 'xxx_', $this->db->tablePrefix(), "Prefix set" );
644 $this->db->tablePrefix( $old );
645 $this->assertNotEquals( 'xxx_', $this->db->tablePrefix() );
646 $this->assertSame( $oldDomain, $this->db->getDomainId() );
647
648 $old = $this->db->dbSchema();
649 $oldDomain = $this->db->getDomainId();
650 $this->assertInternalType( 'string', $old, 'Schema is string' );
651 $this->assertSame( $old, $this->db->dbSchema(), "Schema unchanged" );
652
653 $this->db->selectDB( 'y' );
654 $this->assertSame( $old, $this->db->dbSchema( 'xxx' ) );
655 $this->assertSame( 'xxx', $this->db->dbSchema(), "Schema set" );
656 $this->db->dbSchema( $old );
657 $this->assertNotEquals( 'xxx', $this->db->dbSchema() );
658 $this->assertSame( "y", $this->db->getDomainId() );
659 }
660
661 /**
662 * @covers Wikimedia\Rdbms\Database::tablePrefix
663 * @covers Wikimedia\Rdbms\Database::dbSchema
664 * @expectedException DBUnexpectedError
665 */
666 public function testSchemaWithNoDB() {
667 $ud = DatabaseDomain::newUnspecified();
668
669 $this->assertEquals( $ud->getId(), $this->db->getDomainID() );
670 $this->assertSame( '', $this->db->dbSchema() );
671
672 $this->db->dbSchema( 'xxx' );
673 }
674
675 /**
676 * @covers Wikimedia\Rdbms\Database::selectDomain
677 */
678 public function testSelectDomain() {
679 $oldDomain = $this->db->getDomainId();
680 $oldDatabase = $this->db->getDBname();
681 $oldSchema = $this->db->dbSchema();
682 $oldPrefix = $this->db->tablePrefix();
683
684 $this->db->selectDomain( 'testselectdb-xxx_' );
685 $this->assertSame( 'testselectdb', $this->db->getDBname() );
686 $this->assertSame( '', $this->db->dbSchema() );
687 $this->assertSame( 'xxx_', $this->db->tablePrefix() );
688
689 $this->db->selectDomain( $oldDomain );
690 $this->assertSame( $oldDatabase, $this->db->getDBname() );
691 $this->assertSame( $oldSchema, $this->db->dbSchema() );
692 $this->assertSame( $oldPrefix, $this->db->tablePrefix() );
693 $this->assertSame( $oldDomain, $this->db->getDomainId() );
694
695 $this->db->selectDomain( 'testselectdb-schema-xxx_' );
696 $this->assertSame( 'testselectdb', $this->db->getDBname() );
697 $this->assertSame( 'schema', $this->db->dbSchema() );
698 $this->assertSame( 'xxx_', $this->db->tablePrefix() );
699
700 $this->db->selectDomain( $oldDomain );
701 $this->assertSame( $oldDatabase, $this->db->getDBname() );
702 $this->assertSame( $oldSchema, $this->db->dbSchema() );
703 $this->assertSame( $oldPrefix, $this->db->tablePrefix() );
704 $this->assertSame( $oldDomain, $this->db->getDomainId() );
705 }
706
707 /**
708 * @covers Wikimedia\Rdbms\Database::getLBInfo
709 * @covers Wikimedia\Rdbms\Database::setLBInfo
710 */
711 public function testGetSetLBInfo() {
712 $db = $this->getMockDB();
713
714 $this->assertEquals( [], $db->getLBInfo() );
715 $this->assertNull( $db->getLBInfo( 'pringles' ) );
716
717 $db->setLBInfo( 'soda', 'water' );
718 $this->assertEquals( [ 'soda' => 'water' ], $db->getLBInfo() );
719 $this->assertNull( $db->getLBInfo( 'pringles' ) );
720 $this->assertEquals( 'water', $db->getLBInfo( 'soda' ) );
721
722 $db->setLBInfo( 'basketball', 'Lebron' );
723 $this->assertEquals( [ 'soda' => 'water', 'basketball' => 'Lebron' ], $db->getLBInfo() );
724 $this->assertEquals( 'water', $db->getLBInfo( 'soda' ) );
725 $this->assertEquals( 'Lebron', $db->getLBInfo( 'basketball' ) );
726
727 $db->setLBInfo( 'soda', null );
728 $this->assertEquals( [ 'basketball' => 'Lebron' ], $db->getLBInfo() );
729
730 $db->setLBInfo( [ 'King' => 'James' ] );
731 $this->assertNull( $db->getLBInfo( 'basketball' ) );
732 $this->assertEquals( [ 'King' => 'James' ], $db->getLBInfo() );
733 }
734 }