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