Merge "rdbms: In the query log, show the server name in the message"
[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 $origNoBuffer = $db->getFlag( DBO_NOBUFFER );
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 $origNoBuffer
579 ? $db->clearFlag( DBO_NOBUFFER, $db::REMEMBER_PRIOR )
580 : $db->setFlag( DBO_NOBUFFER, $db::REMEMBER_PRIOR );
581 $this->assertEquals( !$origNoBuffer, $db->getFlag( DBO_NOBUFFER ) );
582
583 $db->restoreFlags( $db::RESTORE_INITIAL );
584 $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
585 $this->assertEquals( $origNoBuffer, $db->getFlag( DBO_NOBUFFER ) );
586
587 $origTrx
588 ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
589 : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
590 $origNoBuffer
591 ? $db->clearFlag( DBO_NOBUFFER, $db::REMEMBER_PRIOR )
592 : $db->setFlag( DBO_NOBUFFER, $db::REMEMBER_PRIOR );
593
594 $db->restoreFlags();
595 $this->assertEquals( $origNoBuffer, $db->getFlag( DBO_NOBUFFER ) );
596 $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
597
598 $db->restoreFlags();
599 $this->assertEquals( $origNoBuffer, $db->getFlag( DBO_NOBUFFER ) );
600 $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
601 }
602
603 public function provideImmutableDBOFlags() {
604 return [
605 [ Database::DBO_IGNORE ],
606 [ Database::DBO_DEFAULT ],
607 [ Database::DBO_PERSISTENT ]
608 ];
609 }
610
611 /**
612 * @expectedException DBUnexpectedError
613 * @covers Wikimedia\Rdbms\Database::setFlag
614 * @dataProvider provideImmutableDBOFlags
615 * @param int $flag
616 */
617 public function testDBOCannotSet( $flag ) {
618 $db = $this->getMockBuilder( DatabaseMysqli::class )
619 ->disableOriginalConstructor()
620 ->setMethods( null )
621 ->getMock();
622
623 $db->setFlag( $flag );
624 }
625
626 /**
627 * @expectedException DBUnexpectedError
628 * @covers Wikimedia\Rdbms\Database::clearFlag
629 * @dataProvider provideImmutableDBOFlags
630 * @param int $flag
631 */
632 public function testDBOCannotClear( $flag ) {
633 $db = $this->getMockBuilder( DatabaseMysqli::class )
634 ->disableOriginalConstructor()
635 ->setMethods( null )
636 ->getMock();
637
638 $db->clearFlag( $flag );
639 }
640
641 /**
642 * @covers Wikimedia\Rdbms\Database::tablePrefix
643 * @covers Wikimedia\Rdbms\Database::dbSchema
644 */
645 public function testSchemaAndPrefixMutators() {
646 $ud = DatabaseDomain::newUnspecified();
647
648 $this->assertEquals( $ud->getId(), $this->db->getDomainID() );
649
650 $old = $this->db->tablePrefix();
651 $oldDomain = $this->db->getDomainId();
652 $this->assertInternalType( 'string', $old, 'Prefix is string' );
653 $this->assertSame( $old, $this->db->tablePrefix(), "Prefix unchanged" );
654 $this->assertSame( $old, $this->db->tablePrefix( 'xxx_' ) );
655 $this->assertSame( 'xxx_', $this->db->tablePrefix(), "Prefix set" );
656 $this->db->tablePrefix( $old );
657 $this->assertNotEquals( 'xxx_', $this->db->tablePrefix() );
658 $this->assertSame( $oldDomain, $this->db->getDomainId() );
659
660 $old = $this->db->dbSchema();
661 $oldDomain = $this->db->getDomainId();
662 $this->assertInternalType( 'string', $old, 'Schema is string' );
663 $this->assertSame( $old, $this->db->dbSchema(), "Schema unchanged" );
664
665 $this->db->selectDB( 'y' );
666 $this->assertSame( $old, $this->db->dbSchema( 'xxx' ) );
667 $this->assertSame( 'xxx', $this->db->dbSchema(), "Schema set" );
668 $this->db->dbSchema( $old );
669 $this->assertNotEquals( 'xxx', $this->db->dbSchema() );
670 $this->assertSame( "y", $this->db->getDomainId() );
671 }
672
673 /**
674 * @covers Wikimedia\Rdbms\Database::tablePrefix
675 * @covers Wikimedia\Rdbms\Database::dbSchema
676 * @expectedException DBUnexpectedError
677 */
678 public function testSchemaWithNoDB() {
679 $ud = DatabaseDomain::newUnspecified();
680
681 $this->assertEquals( $ud->getId(), $this->db->getDomainID() );
682 $this->assertSame( '', $this->db->dbSchema() );
683
684 $this->db->dbSchema( 'xxx' );
685 }
686
687 /**
688 * @covers Wikimedia\Rdbms\Database::selectDomain
689 */
690 public function testSelectDomain() {
691 $oldDomain = $this->db->getDomainId();
692 $oldDatabase = $this->db->getDBname();
693 $oldSchema = $this->db->dbSchema();
694 $oldPrefix = $this->db->tablePrefix();
695
696 $this->db->selectDomain( 'testselectdb-xxx_' );
697 $this->assertSame( 'testselectdb', $this->db->getDBname() );
698 $this->assertSame( '', $this->db->dbSchema() );
699 $this->assertSame( 'xxx_', $this->db->tablePrefix() );
700
701 $this->db->selectDomain( $oldDomain );
702 $this->assertSame( $oldDatabase, $this->db->getDBname() );
703 $this->assertSame( $oldSchema, $this->db->dbSchema() );
704 $this->assertSame( $oldPrefix, $this->db->tablePrefix() );
705 $this->assertSame( $oldDomain, $this->db->getDomainId() );
706
707 $this->db->selectDomain( 'testselectdb-schema-xxx_' );
708 $this->assertSame( 'testselectdb', $this->db->getDBname() );
709 $this->assertSame( 'schema', $this->db->dbSchema() );
710 $this->assertSame( 'xxx_', $this->db->tablePrefix() );
711
712 $this->db->selectDomain( $oldDomain );
713 $this->assertSame( $oldDatabase, $this->db->getDBname() );
714 $this->assertSame( $oldSchema, $this->db->dbSchema() );
715 $this->assertSame( $oldPrefix, $this->db->tablePrefix() );
716 $this->assertSame( $oldDomain, $this->db->getDomainId() );
717 }
718
719 /**
720 * @covers Wikimedia\Rdbms\Database::getLBInfo
721 * @covers Wikimedia\Rdbms\Database::setLBInfo
722 */
723 public function testGetSetLBInfo() {
724 $db = $this->getMockDB();
725
726 $this->assertEquals( [], $db->getLBInfo() );
727 $this->assertNull( $db->getLBInfo( 'pringles' ) );
728
729 $db->setLBInfo( 'soda', 'water' );
730 $this->assertEquals( [ 'soda' => 'water' ], $db->getLBInfo() );
731 $this->assertNull( $db->getLBInfo( 'pringles' ) );
732 $this->assertEquals( 'water', $db->getLBInfo( 'soda' ) );
733
734 $db->setLBInfo( 'basketball', 'Lebron' );
735 $this->assertEquals( [ 'soda' => 'water', 'basketball' => 'Lebron' ], $db->getLBInfo() );
736 $this->assertEquals( 'water', $db->getLBInfo( 'soda' ) );
737 $this->assertEquals( 'Lebron', $db->getLBInfo( 'basketball' ) );
738
739 $db->setLBInfo( 'soda', null );
740 $this->assertEquals( [ 'basketball' => 'Lebron' ], $db->getLBInfo() );
741
742 $db->setLBInfo( [ 'King' => 'James' ] );
743 $this->assertNull( $db->getLBInfo( 'basketball' ) );
744 $this->assertEquals( [ 'King' => 'James' ], $db->getLBInfo() );
745 }
746 }