rdbms: Rename "memCache" to "memStash" in LBFactory
[mediawiki.git] / tests / phpunit / includes / libs / rdbms / database / DatabaseTest.php
blob70b6c3603249d17a131c992acfced10d29c5e8fb
1 <?php
3 use Wikimedia\Rdbms\IDatabase;
4 use Wikimedia\Rdbms\LBFactorySingle;
5 use Wikimedia\Rdbms\TransactionProfiler;
6 use Wikimedia\TestingAccessWrapper;
8 class DatabaseTest extends PHPUnit_Framework_TestCase {
10 protected function setUp() {
11 $this->db = new DatabaseTestHelper( __CLASS__ . '::' . $this->getName() );
14 public static function provideAddQuotes() {
15 return [
16 [ null, 'NULL' ],
17 [ 1234, "'1234'" ],
18 [ 1234.5678, "'1234.5678'" ],
19 [ 'string', "'string'" ],
20 [ 'string\'s cause trouble', "'string\'s cause trouble'" ],
24 /**
25 * @dataProvider provideAddQuotes
26 * @covers Wikimedia\Rdbms\Database::addQuotes
28 public function testAddQuotes( $input, $expected ) {
29 $this->assertEquals( $expected, $this->db->addQuotes( $input ) );
32 public static function provideTableName() {
33 // Formatting is mostly ignored since addIdentifierQuotes is abstract.
34 // For testing of addIdentifierQuotes, see actual Database subclas tests.
35 return [
36 'local' => [
37 'tablename',
38 'tablename',
39 'quoted',
41 'local-raw' => [
42 'tablename',
43 'tablename',
44 'raw',
46 'shared' => [
47 'sharedb.tablename',
48 'tablename',
49 'quoted',
50 [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ],
52 'shared-raw' => [
53 'sharedb.tablename',
54 'tablename',
55 'raw',
56 [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ],
58 'shared-prefix' => [
59 'sharedb.sh_tablename',
60 'tablename',
61 'quoted',
62 [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ],
64 'shared-prefix-raw' => [
65 'sharedb.sh_tablename',
66 'tablename',
67 'raw',
68 [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ],
70 'foreign' => [
71 'databasename.tablename',
72 'databasename.tablename',
73 'quoted',
75 'foreign-raw' => [
76 'databasename.tablename',
77 'databasename.tablename',
78 'raw',
83 /**
84 * @dataProvider provideTableName
85 * @covers Wikimedia\Rdbms\Database::tableName
87 public function testTableName( $expected, $table, $format, array $alias = null ) {
88 if ( $alias ) {
89 $this->db->setTableAliases( [ $table => $alias ] );
91 $this->assertEquals(
92 $expected,
93 $this->db->tableName( $table, $format ?: 'quoted' )
97 /**
98 * @covers Wikimedia\Rdbms\Database::onTransactionIdle
99 * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
101 public function testTransactionIdle() {
102 $db = $this->db;
104 $db->setFlag( DBO_TRX );
105 $called = false;
106 $flagSet = null;
107 $db->onTransactionIdle(
108 function () use ( $db, &$flagSet, &$called ) {
109 $called = true;
110 $flagSet = $db->getFlag( DBO_TRX );
112 __METHOD__
114 $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
115 $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
116 $this->assertTrue( $called, 'Callback reached' );
118 $db->clearFlag( DBO_TRX );
119 $flagSet = null;
120 $db->onTransactionIdle(
121 function () use ( $db, &$flagSet ) {
122 $flagSet = $db->getFlag( DBO_TRX );
124 __METHOD__
126 $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
127 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
129 $db->clearFlag( DBO_TRX );
130 $db->onTransactionIdle(
131 function () use ( $db ) {
132 $db->setFlag( DBO_TRX );
134 __METHOD__
136 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
140 * @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle
141 * @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
143 public function testTransactionPreCommitOrIdle() {
144 $db = $this->getMockDB( [ 'isOpen' ] );
145 $db->method( 'isOpen' )->willReturn( true );
146 $db->clearFlag( DBO_TRX );
148 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX is not set' );
150 $called = false;
151 $db->onTransactionPreCommitOrIdle(
152 function () use ( &$called ) {
153 $called = true;
155 __METHOD__
157 $this->assertTrue( $called, 'Called when idle' );
159 $db->begin( __METHOD__ );
160 $called = false;
161 $db->onTransactionPreCommitOrIdle(
162 function () use ( &$called ) {
163 $called = true;
165 __METHOD__
167 $this->assertFalse( $called, 'Not called when transaction is active' );
168 $db->commit( __METHOD__ );
169 $this->assertTrue( $called, 'Called when transaction is committed' );
173 * @covers Wikimedia\Rdbms\Database::onTransactionPreCommitOrIdle
174 * @covers Wikimedia\Rdbms\Database::runOnTransactionPreCommitCallbacks
176 public function testTransactionPreCommitOrIdle_TRX() {
177 $db = $this->getMockDB( [ 'isOpen' ] );
178 $db->method( 'isOpen' )->willReturn( true );
179 $db->setFlag( DBO_TRX );
181 $lbFactory = LBFactorySingle::newFromConnection( $db );
182 // Ask for the connectin so that LB sets internal state
183 // about this connection being the master connection
184 $lb = $lbFactory->getMainLB();
185 $conn = $lb->openConnection( $lb->getWriterIndex() );
186 $this->assertSame( $db, $conn, 'Same DB instance' );
187 $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX is set' );
189 $called = false;
190 $db->onTransactionPreCommitOrIdle(
191 function () use ( &$called ) {
192 $called = true;
195 $this->assertFalse( $called, 'Not called when idle if DBO_TRX is set' );
197 $lbFactory->beginMasterChanges( __METHOD__ );
198 $this->assertFalse( $called, 'Not called when lb-transaction is active' );
200 $lbFactory->commitMasterChanges( __METHOD__ );
201 $this->assertTrue( $called, 'Called when lb-transaction is committed' );
205 * @covers Wikimedia\Rdbms\Database::onTransactionResolution
206 * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks
208 public function testTransactionResolution() {
209 $db = $this->db;
211 $db->clearFlag( DBO_TRX );
212 $db->begin( __METHOD__ );
213 $called = false;
214 $db->onTransactionResolution( function () use ( $db, &$called ) {
215 $called = true;
216 $db->setFlag( DBO_TRX );
217 } );
218 $db->commit( __METHOD__ );
219 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
220 $this->assertTrue( $called, 'Callback reached' );
222 $db->clearFlag( DBO_TRX );
223 $db->begin( __METHOD__ );
224 $called = false;
225 $db->onTransactionResolution( function () use ( $db, &$called ) {
226 $called = true;
227 $db->setFlag( DBO_TRX );
228 } );
229 $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
230 $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
231 $this->assertTrue( $called, 'Callback reached' );
235 * @covers Wikimedia\Rdbms\Database::setTransactionListener
237 public function testTransactionListener() {
238 $db = $this->db;
240 $db->setTransactionListener( 'ping', function () use ( $db, &$called ) {
241 $called = true;
242 } );
244 $called = false;
245 $db->begin( __METHOD__ );
246 $db->commit( __METHOD__ );
247 $this->assertTrue( $called, 'Callback reached' );
249 $called = false;
250 $db->begin( __METHOD__ );
251 $db->commit( __METHOD__ );
252 $this->assertTrue( $called, 'Callback still reached' );
254 $called = false;
255 $db->begin( __METHOD__ );
256 $db->rollback( __METHOD__ );
257 $this->assertTrue( $called, 'Callback reached' );
259 $db->setTransactionListener( 'ping', null );
260 $called = false;
261 $db->begin( __METHOD__ );
262 $db->commit( __METHOD__ );
263 $this->assertFalse( $called, 'Callback not reached' );
267 * Use this mock instead of DatabaseTestHelper for cases where
268 * DatabaseTestHelper is too inflexibile due to mocking too much
269 * or being too restrictive about fname matching (e.g. for tests
270 * that assert behaviour when the name is a mismatch, we need to
271 * catch the error here instead of there).
273 * @return Database
275 private function getMockDB( $methods = [] ) {
276 static $abstractMethods = [
277 'affectedRows',
278 'closeConnection',
279 'dataSeek',
280 'doQuery',
281 'fetchObject', 'fetchRow',
282 'fieldInfo', 'fieldName',
283 'getSoftwareLink', 'getServerVersion',
284 'getType',
285 'indexInfo',
286 'insertId',
287 'lastError', 'lastErrno',
288 'numFields', 'numRows',
289 'open',
290 'strencode',
292 $db = $this->getMockBuilder( Database::class )
293 ->disableOriginalConstructor()
294 ->setMethods( array_values( array_unique( array_merge(
295 $abstractMethods,
296 $methods
297 ) ) ) )
298 ->getMock();
299 $wdb = TestingAccessWrapper::newFromObject( $db );
300 $wdb->trxProfiler = new TransactionProfiler();
301 $wdb->connLogger = new \Psr\Log\NullLogger();
302 $wdb->queryLogger = new \Psr\Log\NullLogger();
303 return $db;
307 * @covers Wikimedia\Rdbms\Database::flushSnapshot
309 public function testFlushSnapshot() {
310 $db = $this->getMockDB( [ 'isOpen' ] );
311 $db->method( 'isOpen' )->willReturn( true );
313 $db->flushSnapshot( __METHOD__ ); // ok
314 $db->flushSnapshot( __METHOD__ ); // ok
316 $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
317 $db->query( 'SELECT 1', __METHOD__ );
318 $this->assertTrue( (bool)$db->trxLevel(), "Transaction started." );
319 $db->flushSnapshot( __METHOD__ ); // ok
320 $db->restoreFlags( $db::RESTORE_PRIOR );
322 $this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." );
325 public function testGetScopedLock() {
326 $db = $this->getMockDB( [ 'isOpen' ] );
327 $db->method( 'isOpen' )->willReturn( true );
329 $db->setFlag( DBO_TRX );
330 try {
331 $this->badLockingMethodImplicit( $db );
332 } catch ( RunTimeException $e ) {
333 $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
335 $db->clearFlag( DBO_TRX );
336 $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
337 $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
339 try {
340 $this->badLockingMethodExplicit( $db );
341 } catch ( RunTimeException $e ) {
342 $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
344 $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
345 $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
348 private function badLockingMethodImplicit( IDatabase $db ) {
349 $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
350 $db->query( "SELECT 1" ); // trigger DBO_TRX
351 throw new RunTimeException( "Uh oh!" );
354 private function badLockingMethodExplicit( IDatabase $db ) {
355 $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
356 $db->begin( __METHOD__ );
357 throw new RunTimeException( "Uh oh!" );
361 * @covers Wikimedia\Rdbms\Database::getFlag
362 * @covers Wikimedia\Rdbms\Database::setFlag
363 * @covers Wikimedia\Rdbms\Database::restoreFlags
365 public function testFlagSetting() {
366 $db = $this->db;
367 $origTrx = $db->getFlag( DBO_TRX );
368 $origSsl = $db->getFlag( DBO_SSL );
370 $origTrx
371 ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
372 : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
373 $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
375 $origSsl
376 ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
377 : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
378 $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) );
380 $db->restoreFlags( $db::RESTORE_INITIAL );
381 $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
382 $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
384 $origTrx
385 ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR )
386 : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR );
387 $origSsl
388 ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR )
389 : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR );
391 $db->restoreFlags();
392 $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
393 $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) );
395 $db->restoreFlags();
396 $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) );
397 $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) );
401 * @covers Wikimedia\Rdbms\Database::tablePrefix
402 * @covers Wikimedia\Rdbms\Database::dbSchema
404 public function testMutators() {
405 $old = $this->db->tablePrefix();
406 $this->assertInternalType( 'string', $old, 'Prefix is string' );
407 $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" );
408 $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) );
409 $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" );
410 $this->db->tablePrefix( $old );
411 $this->assertNotEquals( 'xxx', $this->db->tablePrefix() );
413 $old = $this->db->dbSchema();
414 $this->assertInternalType( 'string', $old, 'Schema is string' );
415 $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" );
416 $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) );
417 $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" );
418 $this->db->dbSchema( $old );
419 $this->assertNotEquals( 'xxx', $this->db->dbSchema() );