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