From: Timo Tijhof Date: Thu, 20 Jul 2017 20:17:11 +0000 (-0700) Subject: rdbms: Refactor DatabaseTest X-Git-Tag: 1.31.0-rc.0~2645^2 X-Git-Url: https://git.cyclocoop.org/%242?a=commitdiff_plain;h=ddb7575e4ec22ce3a4cdd0917deb9f4f3403613a;p=lhc%2Fweb%2Fwiklou.git rdbms: Refactor DatabaseTest * Move DatabaseTest and DatabaseSQLTest to libs, and remove MediaWikiTestCase dependency. * Refactor DatabaseTest to be a test of the Database abstract class, not of whatever current DB backend is configured by LocalSettings. - Remove most switches/conditionals and other tests for specific database backends. Move those to individual test classes for those backends instead. - Some tests appear to have been integration tests for the PHP driver and/or the db backend itself. Moved to a new DatabaseIntegrationTest. - Now that only the abstract Database is invoked, the test runs a bit faster (no real connections/queries). * Add missing @covers tags, and remove or fix broken ones (follows-up 26e52f0c491a4). Change-Id: I9dc4a558e701d00e95789e7eb8e02926783b65ad --- diff --git a/tests/phpunit/includes/db/DatabaseSQLTest.php b/tests/phpunit/includes/db/DatabaseSQLTest.php deleted file mode 100644 index 2b587db157..0000000000 --- a/tests/phpunit/includes/db/DatabaseSQLTest.php +++ /dev/null @@ -1,1075 +0,0 @@ -database = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => true ] ); - } - - protected function assertLastSql( $sqlText ) { - $this->assertEquals( - $sqlText, - $this->database->getLastSqls() - ); - } - - protected function assertLastSqlDb( $sqlText, $db ) { - $this->assertEquals( $sqlText, $db->getLastSqls() ); - } - - /** - * @dataProvider provideSelect - * @covers Database::select - */ - public function testSelect( $sql, $sqlText ) { - $this->database->select( - $sql['tables'], - $sql['fields'], - isset( $sql['conds'] ) ? $sql['conds'] : [], - __METHOD__, - isset( $sql['options'] ) ? $sql['options'] : [], - isset( $sql['join_conds'] ) ? $sql['join_conds'] : [] - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideSelect() { - return [ - [ - [ - 'tables' => 'table', - 'fields' => [ 'field', 'alias' => 'field2' ], - 'conds' => [ 'alias' => 'text' ], - ], - "SELECT field,field2 AS alias " . - "FROM table " . - "WHERE alias = 'text'" - ], - [ - [ - 'tables' => 'table', - 'fields' => [ 'field', 'alias' => 'field2' ], - 'conds' => [ 'alias' => 'text' ], - 'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ], - ], - "SELECT field,field2 AS alias " . - "FROM table " . - "WHERE alias = 'text' " . - "ORDER BY field " . - "LIMIT 1" - ], - [ - [ - 'tables' => [ 'table', 't2' => 'table2' ], - 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ], - 'conds' => [ 'alias' => 'text' ], - 'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ], - 'join_conds' => [ 't2' => [ - 'LEFT JOIN', 'tid = t2.id' - ] ], - ], - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "ORDER BY field " . - "LIMIT 1" - ], - [ - [ - 'tables' => [ 'table', 't2' => 'table2' ], - 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ], - 'conds' => [ 'alias' => 'text' ], - 'options' => [ 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ], - 'join_conds' => [ 't2' => [ - 'LEFT JOIN', 'tid = t2.id' - ] ], - ], - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "GROUP BY field HAVING COUNT(*) > 1 " . - "LIMIT 1" - ], - [ - [ - 'tables' => [ 'table', 't2' => 'table2' ], - 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ], - 'conds' => [ 'alias' => 'text' ], - 'options' => [ - 'LIMIT' => 1, - 'GROUP BY' => [ 'field', 'field2' ], - 'HAVING' => [ 'COUNT(*) > 1', 'field' => 1 ] - ], - 'join_conds' => [ 't2' => [ - 'LEFT JOIN', 'tid = t2.id' - ] ], - ], - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " . - "LIMIT 1" - ], - [ - [ - 'tables' => [ 'table' ], - 'fields' => [ 'alias' => 'field' ], - 'conds' => [ 'alias' => [ 1, 2, 3, 4 ] ], - ], - "SELECT field AS alias " . - "FROM table " . - "WHERE alias IN ('1','2','3','4')" - ], - ]; - } - - /** - * @dataProvider provideUpdate - * @covers Database::update - */ - public function testUpdate( $sql, $sqlText ) { - $this->database->update( - $sql['table'], - $sql['values'], - $sql['conds'], - __METHOD__, - isset( $sql['options'] ) ? $sql['options'] : [] - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideUpdate() { - return [ - [ - [ - 'table' => 'table', - 'values' => [ 'field' => 'text', 'field2' => 'text2' ], - 'conds' => [ 'alias' => 'text' ], - ], - "UPDATE table " . - "SET field = 'text'" . - ",field2 = 'text2' " . - "WHERE alias = 'text'" - ], - [ - [ - 'table' => 'table', - 'values' => [ 'field = other', 'field2' => 'text2' ], - 'conds' => [ 'id' => '1' ], - ], - "UPDATE table " . - "SET field = other" . - ",field2 = 'text2' " . - "WHERE id = '1'" - ], - [ - [ - 'table' => 'table', - 'values' => [ 'field = other', 'field2' => 'text2' ], - 'conds' => '*', - ], - "UPDATE table " . - "SET field = other" . - ",field2 = 'text2'" - ], - ]; - } - - /** - * @dataProvider provideDelete - * @covers Database::delete - */ - public function testDelete( $sql, $sqlText ) { - $this->database->delete( - $sql['table'], - $sql['conds'], - __METHOD__ - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideDelete() { - return [ - [ - [ - 'table' => 'table', - 'conds' => [ 'alias' => 'text' ], - ], - "DELETE FROM table " . - "WHERE alias = 'text'" - ], - [ - [ - 'table' => 'table', - 'conds' => '*', - ], - "DELETE FROM table" - ], - ]; - } - - /** - * @dataProvider provideUpsert - * @covers Database::upsert - */ - public function testUpsert( $sql, $sqlText ) { - $this->database->upsert( - $sql['table'], - $sql['rows'], - $sql['uniqueIndexes'], - $sql['set'], - __METHOD__ - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideUpsert() { - return [ - [ - [ - 'table' => 'upsert_table', - 'rows' => [ 'field' => 'text', 'field2' => 'text2' ], - 'uniqueIndexes' => [ 'field' ], - 'set' => [ 'field' => 'set' ], - ], - "BEGIN; " . - "UPDATE upsert_table " . - "SET field = 'set' " . - "WHERE ((field = 'text')); " . - "INSERT IGNORE INTO upsert_table " . - "(field,field2) " . - "VALUES ('text','text2'); " . - "COMMIT" - ], - ]; - } - - /** - * @dataProvider provideDeleteJoin - * @covers Database::deleteJoin - */ - public function testDeleteJoin( $sql, $sqlText ) { - $this->database->deleteJoin( - $sql['delTable'], - $sql['joinTable'], - $sql['delVar'], - $sql['joinVar'], - $sql['conds'], - __METHOD__ - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideDeleteJoin() { - return [ - [ - [ - 'delTable' => 'table', - 'joinTable' => 'table_join', - 'delVar' => 'field', - 'joinVar' => 'field_join', - 'conds' => [ 'alias' => 'text' ], - ], - "DELETE FROM table " . - "WHERE field IN (" . - "SELECT field_join FROM table_join WHERE alias = 'text'" . - ")" - ], - [ - [ - 'delTable' => 'table', - 'joinTable' => 'table_join', - 'delVar' => 'field', - 'joinVar' => 'field_join', - 'conds' => '*', - ], - "DELETE FROM table " . - "WHERE field IN (" . - "SELECT field_join FROM table_join " . - ")" - ], - ]; - } - - /** - * @dataProvider provideInsert - * @covers Database::insert - */ - public function testInsert( $sql, $sqlText ) { - $this->database->insert( - $sql['table'], - $sql['rows'], - __METHOD__, - isset( $sql['options'] ) ? $sql['options'] : [] - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideInsert() { - return [ - [ - [ - 'table' => 'table', - 'rows' => [ 'field' => 'text', 'field2' => 2 ], - ], - "INSERT INTO table " . - "(field,field2) " . - "VALUES ('text','2')" - ], - [ - [ - 'table' => 'table', - 'rows' => [ 'field' => 'text', 'field2' => 2 ], - 'options' => 'IGNORE', - ], - "INSERT IGNORE INTO table " . - "(field,field2) " . - "VALUES ('text','2')" - ], - [ - [ - 'table' => 'table', - 'rows' => [ - [ 'field' => 'text', 'field2' => 2 ], - [ 'field' => 'multi', 'field2' => 3 ], - ], - 'options' => 'IGNORE', - ], - "INSERT IGNORE INTO table " . - "(field,field2) " . - "VALUES " . - "('text','2')," . - "('multi','3')" - ], - ]; - } - - /** - * @dataProvider provideInsertSelect - * @covers Database::insertSelect - */ - public function testInsertSelect( $sql, $sqlTextNative, $sqlSelect, $sqlInsert ) { - $this->database->insertSelect( - $sql['destTable'], - $sql['srcTable'], - $sql['varMap'], - $sql['conds'], - __METHOD__, - isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [], - isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [], - isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : [] - ); - $this->assertLastSql( $sqlTextNative ); - - $dbWeb = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => false ] ); - $dbWeb->forceNextResult( [ - array_flip( array_keys( $sql['varMap'] ) ) - ] ); - $dbWeb->insertSelect( - $sql['destTable'], - $sql['srcTable'], - $sql['varMap'], - $sql['conds'], - __METHOD__, - isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [], - isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [], - isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : [] - ); - $this->assertLastSqlDb( implode( '; ', [ $sqlSelect, $sqlInsert ] ), $dbWeb ); - } - - public static function provideInsertSelect() { - return [ - [ - [ - 'destTable' => 'insert_table', - 'srcTable' => 'select_table', - 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], - 'conds' => '*', - ], - "INSERT INTO insert_table " . - "(field_insert,field) " . - "SELECT field_select,field2 " . - "FROM select_table WHERE *", - "SELECT field_select AS field_insert,field2 AS field " . - "FROM select_table WHERE * FOR UPDATE", - "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')" - ], - [ - [ - 'destTable' => 'insert_table', - 'srcTable' => 'select_table', - 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], - 'conds' => [ 'field' => 2 ], - ], - "INSERT INTO insert_table " . - "(field_insert,field) " . - "SELECT field_select,field2 " . - "FROM select_table " . - "WHERE field = '2'", - "SELECT field_select AS field_insert,field2 AS field FROM " . - "select_table WHERE field = '2' FOR UPDATE", - "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')" - ], - [ - [ - 'destTable' => 'insert_table', - 'srcTable' => 'select_table', - 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], - 'conds' => [ 'field' => 2 ], - 'insertOptions' => 'IGNORE', - 'selectOptions' => [ 'ORDER BY' => 'field' ], - ], - "INSERT IGNORE INTO insert_table " . - "(field_insert,field) " . - "SELECT field_select,field2 " . - "FROM select_table " . - "WHERE field = '2' " . - "ORDER BY field", - "SELECT field_select AS field_insert,field2 AS field " . - "FROM select_table WHERE field = '2' ORDER BY field FOR UPDATE", - "INSERT IGNORE INTO insert_table (field_insert,field) VALUES ('0','1')" - ], - [ - [ - 'destTable' => 'insert_table', - 'srcTable' => [ 'select_table1', 'select_table2' ], - 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], - 'conds' => [ 'field' => 2 ], - 'selectOptions' => [ 'ORDER BY' => 'field', 'FORCE INDEX' => [ 'select_table1' => 'index1' ] ], - 'selectJoinConds' => [ - 'select_table2' => [ 'LEFT JOIN', [ 'select_table1.foo = select_table2.bar' ] ], - ], - ], - "INSERT INTO insert_table " . - "(field_insert,field) " . - "SELECT field_select,field2 " . - "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " . - "WHERE field = '2' " . - "ORDER BY field", - "SELECT field_select AS field_insert,field2 AS field " . - "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " . - "WHERE field = '2' ORDER BY field FOR UPDATE", - "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')" - ], - ]; - } - - /** - * @dataProvider provideReplace - * @covers Database::replace - */ - public function testReplace( $sql, $sqlText ) { - $this->database->replace( - $sql['table'], - $sql['uniqueIndexes'], - $sql['rows'], - __METHOD__ - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideReplace() { - return [ - [ - [ - 'table' => 'replace_table', - 'uniqueIndexes' => [ 'field' ], - 'rows' => [ 'field' => 'text', 'field2' => 'text2' ], - ], - "DELETE FROM replace_table " . - "WHERE ( field='text' ); " . - "INSERT INTO replace_table " . - "(field,field2) " . - "VALUES ('text','text2')" - ], - [ - [ - 'table' => 'module_deps', - 'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ], - 'rows' => [ - 'md_module' => 'module', - 'md_skin' => 'skin', - 'md_deps' => 'deps', - ], - ], - "DELETE FROM module_deps " . - "WHERE ( md_module='module' AND md_skin='skin' ); " . - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module','skin','deps')" - ], - [ - [ - 'table' => 'module_deps', - 'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ], - 'rows' => [ - [ - 'md_module' => 'module', - 'md_skin' => 'skin', - 'md_deps' => 'deps', - ], [ - 'md_module' => 'module2', - 'md_skin' => 'skin2', - 'md_deps' => 'deps2', - ], - ], - ], - "DELETE FROM module_deps " . - "WHERE ( md_module='module' AND md_skin='skin' ); " . - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module','skin','deps'); " . - "DELETE FROM module_deps " . - "WHERE ( md_module='module2' AND md_skin='skin2' ); " . - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module2','skin2','deps2')" - ], - [ - [ - 'table' => 'module_deps', - 'uniqueIndexes' => [ 'md_module', 'md_skin' ], - 'rows' => [ - [ - 'md_module' => 'module', - 'md_skin' => 'skin', - 'md_deps' => 'deps', - ], [ - 'md_module' => 'module2', - 'md_skin' => 'skin2', - 'md_deps' => 'deps2', - ], - ], - ], - "DELETE FROM module_deps " . - "WHERE ( md_module='module' ) OR ( md_skin='skin' ); " . - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module','skin','deps'); " . - "DELETE FROM module_deps " . - "WHERE ( md_module='module2' ) OR ( md_skin='skin2' ); " . - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module2','skin2','deps2')" - ], - [ - [ - 'table' => 'module_deps', - 'uniqueIndexes' => [], - 'rows' => [ - 'md_module' => 'module', - 'md_skin' => 'skin', - 'md_deps' => 'deps', - ], - ], - "INSERT INTO module_deps " . - "(md_module,md_skin,md_deps) " . - "VALUES ('module','skin','deps')" - ], - ]; - } - - /** - * @dataProvider provideNativeReplace - * @covers Database::nativeReplace - */ - public function testNativeReplace( $sql, $sqlText ) { - $this->database->nativeReplace( - $sql['table'], - $sql['rows'], - __METHOD__ - ); - $this->assertLastSql( $sqlText ); - } - - public static function provideNativeReplace() { - return [ - [ - [ - 'table' => 'replace_table', - 'rows' => [ 'field' => 'text', 'field2' => 'text2' ], - ], - "REPLACE INTO replace_table " . - "(field,field2) " . - "VALUES ('text','text2')" - ], - ]; - } - - /** - * @dataProvider provideConditional - * @covers Database::conditional - */ - public function testConditional( $sql, $sqlText ) { - $this->assertEquals( trim( $this->database->conditional( - $sql['conds'], - $sql['true'], - $sql['false'] - ) ), $sqlText ); - } - - public static function provideConditional() { - return [ - [ - [ - 'conds' => [ 'field' => 'text' ], - 'true' => 1, - 'false' => 'NULL', - ], - "(CASE WHEN field = 'text' THEN 1 ELSE NULL END)" - ], - [ - [ - 'conds' => [ 'field' => 'text', 'field2' => 'anothertext' ], - 'true' => 1, - 'false' => 'NULL', - ], - "(CASE WHEN field = 'text' AND field2 = 'anothertext' THEN 1 ELSE NULL END)" - ], - [ - [ - 'conds' => 'field=1', - 'true' => 1, - 'false' => 'NULL', - ], - "(CASE WHEN field=1 THEN 1 ELSE NULL END)" - ], - ]; - } - - /** - * @dataProvider provideBuildConcat - * @covers Database::buildConcat - */ - public function testBuildConcat( $stringList, $sqlText ) { - $this->assertEquals( trim( $this->database->buildConcat( - $stringList - ) ), $sqlText ); - } - - public static function provideBuildConcat() { - return [ - [ - [ 'field', 'field2' ], - "CONCAT(field,field2)" - ], - [ - [ "'test'", 'field2' ], - "CONCAT('test',field2)" - ], - ]; - } - - /** - * @dataProvider provideBuildLike - * @covers Database::buildLike - */ - public function testBuildLike( $array, $sqlText ) { - $this->assertEquals( trim( $this->database->buildLike( - $array - ) ), $sqlText ); - } - - public static function provideBuildLike() { - return [ - [ - 'text', - "LIKE 'text' ESCAPE '`'" - ], - [ - [ 'text', new LikeMatch( '%' ) ], - "LIKE 'text%' ESCAPE '`'" - ], - [ - [ 'text', new LikeMatch( '%' ), 'text2' ], - "LIKE 'text%text2' ESCAPE '`'" - ], - [ - [ 'text', new LikeMatch( '_' ) ], - "LIKE 'text_' ESCAPE '`'" - ], - [ - 'more_text', - "LIKE 'more`_text' ESCAPE '`'" - ], - [ - [ 'C:\\Windows\\', new LikeMatch( '%' ) ], - "LIKE 'C:\\Windows\\%' ESCAPE '`'" - ], - [ - [ 'accent`_test`', new LikeMatch( '%' ) ], - "LIKE 'accent```_test``%' ESCAPE '`'" - ], - ]; - } - - /** - * @dataProvider provideUnionQueries - * @covers Database::unionQueries - */ - public function testUnionQueries( $sql, $sqlText ) { - $this->assertEquals( trim( $this->database->unionQueries( - $sql['sqls'], - $sql['all'] - ) ), $sqlText ); - } - - public static function provideUnionQueries() { - return [ - [ - [ - 'sqls' => [ 'RAW SQL', 'RAW2SQL' ], - 'all' => true, - ], - "(RAW SQL) UNION ALL (RAW2SQL)" - ], - [ - [ - 'sqls' => [ 'RAW SQL', 'RAW2SQL' ], - 'all' => false, - ], - "(RAW SQL) UNION (RAW2SQL)" - ], - [ - [ - 'sqls' => [ 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ], - 'all' => false, - ], - "(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)" - ], - ]; - } - - /** - * @dataProvider provideUnionConditionPermutations - * @covers Database::unionConditionPermutations - */ - public function testUnionConditionPermutations( $params, $expect ) { - if ( isset( $params['unionSupportsOrderAndLimit'] ) ) { - $this->database->setUnionSupportsOrderAndLimit( $params['unionSupportsOrderAndLimit'] ); - } - - $sql = trim( $this->database->unionConditionPermutations( - $params['table'], - $params['vars'], - $params['permute_conds'], - isset( $params['extra_conds'] ) ? $params['extra_conds'] : '', - 'FNAME', - isset( $params['options'] ) ? $params['options'] : [], - isset( $params['join_conds'] ) ? $params['join_conds'] : [] - ) ); - $this->assertEquals( $expect, $sql ); - } - - public static function provideUnionConditionPermutations() { - return [ - // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong - [ - [ - 'table' => [ 'table1', 'table2' ], - 'vars' => [ 'field1', 'alias' => 'field2' ], - 'permute_conds' => [ - 'field3' => [ 1, 2, 3 ], - 'duplicates' => [ 4, 5, 4 ], - 'empty' => [], - 'single' => [ 0 ], - ], - 'extra_conds' => 'table2.bar > 23', - 'options' => [ - 'ORDER BY' => [ 'field1', 'alias' ], - 'INNER ORDER BY' => [ 'field1', 'field2' ], - 'LIMIT' => 100, - ], - 'join_conds' => [ - 'table2' => [ 'JOIN', 'table1.foo_id = table2.foo_id' ], - ], - ], - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '1' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '1' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '2' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '2' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '3' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . - "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '3' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) " . - "ORDER BY field1,alias LIMIT 100" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [ - 'bar' => [ 1, 2, 3 ], - ], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'NOTALL', - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - ], - ], - "(SELECT foo_id FROM foo WHERE bar = '1' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) UNION " . - "(SELECT foo_id FROM foo WHERE bar = '2' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) UNION " . - "(SELECT foo_id FROM foo WHERE bar = '3' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) " . - "ORDER BY foo_id LIMIT 25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [ - 'bar' => [ 1, 2, 3 ], - ], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'NOTALL' => true, - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - ], - 'unionSupportsOrderAndLimit' => false, - ], - "(SELECT foo_id FROM foo WHERE bar = '1' AND baz IS NULL ) UNION " . - "(SELECT foo_id FROM foo WHERE bar = '2' AND baz IS NULL ) UNION " . - "(SELECT foo_id FROM foo WHERE bar = '3' AND baz IS NULL ) " . - "ORDER BY foo_id LIMIT 25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - ], - ], - "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [ - 'bar' => [], - ], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - ], - ], - "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [ - 'bar' => [ 1 ], - ], - 'options' => [ - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - 'OFFSET' => 150, - ], - ], - "SELECT foo_id FROM foo WHERE bar = '1' ORDER BY foo_id LIMIT 150,25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - 'OFFSET' => 150, - 'INNER ORDER BY' => [ 'bar_id' ], - ], - ], - "(SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY bar_id LIMIT 175 ) ORDER BY foo_id LIMIT 150,25" - ], - [ - [ - 'table' => 'foo', - 'vars' => [ 'foo_id' ], - 'permute_conds' => [], - 'extra_conds' => [ 'baz' => null ], - 'options' => [ - 'ORDER BY' => [ 'foo_id' ], - 'LIMIT' => 25, - 'OFFSET' => 150, - 'INNER ORDER BY' => [ 'bar_id' ], - ], - 'unionSupportsOrderAndLimit' => false, - ], - "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 150,25" - ], - // @codingStandardsIgnoreEnd - ]; - } - - /** - * @covers Database::commit - */ - public function testTransactionCommit() { - $this->database->begin( __METHOD__ ); - $this->database->commit( __METHOD__ ); - $this->assertLastSql( 'BEGIN; COMMIT' ); - } - - /** - * @covers Database::rollback - */ - public function testTransactionRollback() { - $this->database->begin( __METHOD__ ); - $this->database->rollback( __METHOD__ ); - $this->assertLastSql( 'BEGIN; ROLLBACK' ); - } - - /** - * @covers Database::dropTable - */ - public function testDropTable() { - $this->database->setExistingTables( [ 'table' ] ); - $this->database->dropTable( 'table', __METHOD__ ); - $this->assertLastSql( 'DROP TABLE table CASCADE' ); - } - - /** - * @covers Database::dropTable - */ - public function testDropNonExistingTable() { - $this->assertFalse( - $this->database->dropTable( 'non_existing', __METHOD__ ) - ); - } - - /** - * @dataProvider provideMakeList - * @covers Database::makeList - */ - public function testMakeList( $list, $mode, $sqlText ) { - $this->assertEquals( trim( $this->database->makeList( - $list, $mode - ) ), $sqlText ); - } - - public static function provideMakeList() { - return [ - [ - [ 'value', 'value2' ], - LIST_COMMA, - "'value','value2'" - ], - [ - [ 'field', 'field2' ], - LIST_NAMES, - "field,field2" - ], - [ - [ 'field' => 'value', 'field2' => 'value2' ], - LIST_AND, - "field = 'value' AND field2 = 'value2'" - ], - [ - [ 'field' => null, "field2 != 'value2'" ], - LIST_AND, - "field IS NULL AND (field2 != 'value2')" - ], - [ - [ 'field' => [ 'value', null, 'value2' ], 'field2' => 'value2' ], - LIST_AND, - "(field IN ('value','value2') OR field IS NULL) AND field2 = 'value2'" - ], - [ - [ 'field' => [ null ], 'field2' => null ], - LIST_AND, - "field IS NULL AND field2 IS NULL" - ], - [ - [ 'field' => 'value', 'field2' => 'value2' ], - LIST_OR, - "field = 'value' OR field2 = 'value2'" - ], - [ - [ 'field' => 'value', 'field2' => null ], - LIST_OR, - "field = 'value' OR field2 IS NULL" - ], - [ - [ 'field' => [ 'value', 'value2' ], 'field2' => [ 'value' ] ], - LIST_OR, - "field IN ('value','value2') OR field2 = 'value'" - ], - [ - [ 'field' => [ null, 'value', null, 'value2' ], "field2 != 'value2'" ], - LIST_OR, - "(field IN ('value','value2') OR field IS NULL) OR (field2 != 'value2')" - ], - [ - [ 'field' => 'value', 'field2' => 'value2' ], - LIST_SET, - "field = 'value',field2 = 'value2'" - ], - [ - [ 'field' => 'value', 'field2' => null ], - LIST_SET, - "field = 'value',field2 = NULL" - ], - [ - [ 'field' => 'value', "field2 != 'value2'" ], - LIST_SET, - "field = 'value',field2 != 'value2'" - ], - ]; - } - - public function testSessionTempTables() { - $temp1 = $this->database->tableName( 'tmp_table_1' ); - $temp2 = $this->database->tableName( 'tmp_table_2' ); - $temp3 = $this->database->tableName( 'tmp_table_3' ); - - $this->database->query( "CREATE TEMPORARY TABLE $temp1 LIKE orig_tbl", __METHOD__ ); - $this->database->query( "CREATE TEMPORARY TABLE $temp2 LIKE orig_tbl", __METHOD__ ); - $this->database->query( "CREATE TEMPORARY TABLE $temp3 LIKE orig_tbl", __METHOD__ ); - - $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); - $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); - $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); - - $this->database->dropTable( 'tmp_table_1', __METHOD__ ); - $this->database->dropTable( 'tmp_table_2', __METHOD__ ); - $this->database->dropTable( 'tmp_table_3', __METHOD__ ); - - $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); - $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); - $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); - - $this->database->query( "CREATE TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ ); - $this->database->query( "CREATE TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ ); - $this->database->query( "CREATE TEMPORARY TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ ); - - $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); - $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); - $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); - - $this->database->query( "DROP TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ ); - $this->database->query( "DROP TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ ); - $this->database->query( "DROP TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ ); - - $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); - $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); - $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); - } -} diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index b90b1ad312..ae61070f96 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -87,6 +87,10 @@ class DatabaseSqliteTest extends MediaWikiTestCase { new Blob( "hello" ), "x'68656c6c6f'", ], + [ // #5: null + null, + "''", + ], ]; } diff --git a/tests/phpunit/includes/db/DatabaseTest.php b/tests/phpunit/includes/db/DatabaseTest.php deleted file mode 100644 index 45791e2424..0000000000 --- a/tests/phpunit/includes/db/DatabaseTest.php +++ /dev/null @@ -1,415 +0,0 @@ -db = wfGetDB( DB_MASTER ); - } - - protected function tearDown() { - parent::tearDown(); - if ( $this->functionTest ) { - $this->dropFunctions(); - $this->functionTest = false; - } - $this->db->restoreFlags( IDatabase::RESTORE_INITIAL ); - } - - /** - * @covers Database::dropTable - */ - public function testAddQuotesNull() { - $check = "NULL"; - if ( $this->db->getType() === 'sqlite' || $this->db->getType() === 'oracle' ) { - $check = "''"; - } - $this->assertEquals( $check, $this->db->addQuotes( null ) ); - } - - public function testAddQuotesInt() { - # returning just "1234" should be ok too, though... - # maybe - $this->assertEquals( - "'1234'", - $this->db->addQuotes( 1234 ) ); - } - - public function testAddQuotesFloat() { - # returning just "1234.5678" would be ok too, though - $this->assertEquals( - "'1234.5678'", - $this->db->addQuotes( 1234.5678 ) ); - } - - public function testAddQuotesString() { - $this->assertEquals( - "'string'", - $this->db->addQuotes( 'string' ) ); - } - - public function testAddQuotesStringQuote() { - $check = "'string''s cause trouble'"; - if ( $this->db->getType() === 'mysql' ) { - $check = "'string\'s cause trouble'"; - } - $this->assertEquals( - $check, - $this->db->addQuotes( "string's cause trouble" ) ); - } - - private function getSharedTableName( $table, $database, $prefix, $format = 'quoted' ) { - global $wgSharedDB, $wgSharedTables, $wgSharedPrefix, $wgSharedSchema; - - $this->db->setTableAliases( [ - $table => [ - 'dbname' => $database, - 'schema' => null, - 'prefix' => $prefix - ] - ] ); - - $ret = $this->db->tableName( $table, $format ); - - $this->db->setTableAliases( array_fill_keys( - $wgSharedDB ? $wgSharedTables : [], - [ - 'dbname' => $wgSharedDB, - 'schema' => $wgSharedSchema, - 'prefix' => $wgSharedPrefix - ] - ) ); - - return $ret; - } - - private function prefixAndQuote( $table, $database = null, $prefix = null, $format = 'quoted' ) { - if ( $this->db->getType() === 'sqlite' || $format !== 'quoted' ) { - $quote = ''; - } elseif ( $this->db->getType() === 'mysql' ) { - $quote = '`'; - } elseif ( $this->db->getType() === 'oracle' ) { - $quote = '/*Q*/'; - } else { - $quote = '"'; - } - - if ( $database !== null ) { - if ( $this->db->getType() === 'oracle' ) { - $database = $quote . $database . '.'; - } else { - $database = $quote . $database . $quote . '.'; - } - } - - if ( $prefix === null ) { - $prefix = $this->dbPrefix(); - } - - if ( $this->db->getType() === 'oracle' ) { - return strtoupper( $database . $quote . $prefix . $table ); - } else { - return $database . $quote . $prefix . $table . $quote; - } - } - - public function testTableNameLocal() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename' ), - $this->db->tableName( 'tablename' ) - ); - } - - public function testTableNameRawLocal() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename', null, null, 'raw' ), - $this->db->tableName( 'tablename', 'raw' ) - ); - } - - public function testTableNameShared() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_' ), - $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_' ) - ); - - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'sharedatabase', null ), - $this->getSharedTableName( 'tablename', 'sharedatabase', null ) - ); - } - - public function testTableNameRawShared() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_', 'raw' ), - $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_', 'raw' ) - ); - - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'sharedatabase', null, 'raw' ), - $this->getSharedTableName( 'tablename', 'sharedatabase', null, 'raw' ) - ); - } - - public function testTableNameForeign() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'databasename', '' ), - $this->db->tableName( 'databasename.tablename' ) - ); - } - - public function testTableNameRawForeign() { - $this->assertEquals( - $this->prefixAndQuote( 'tablename', 'databasename', '', 'raw' ), - $this->db->tableName( 'databasename.tablename', 'raw' ) - ); - } - - public function testStoredFunctions() { - if ( !in_array( wfGetDB( DB_MASTER )->getType(), [ 'mysql', 'postgres' ] ) ) { - $this->markTestSkipped( 'MySQL or Postgres required' ); - } - global $IP; - $this->dropFunctions(); - $this->functionTest = true; - $this->assertTrue( - $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" ) - ); - $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ ); - $this->assertEquals( 42, $res->fetchObject()->test ); - } - - private function dropFunctions() { - $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function' - . ( $this->db->getType() == 'postgres' ? '()' : '' ) - ); - } - - public function testUnknownTableCorruptsResults() { - $res = $this->db->select( 'page', '*', [ 'page_id' => 1 ] ); - $this->assertFalse( $this->db->tableExists( 'foobarbaz' ) ); - $this->assertInternalType( 'int', $res->numRows() ); - } - - public function testTransactionIdle() { - $db = $this->db; - - $db->setFlag( DBO_TRX ); - $called = false; - $flagSet = null; - $db->onTransactionIdle( - function () use ( $db, &$flagSet, &$called ) { - $called = true; - $flagSet = $db->getFlag( DBO_TRX ); - }, - __METHOD__ - ); - $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); - $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - $this->assertTrue( $called, 'Callback reached' ); - - $db->clearFlag( DBO_TRX ); - $flagSet = null; - $db->onTransactionIdle( - function () use ( $db, &$flagSet ) { - $flagSet = $db->getFlag( DBO_TRX ); - }, - __METHOD__ - ); - $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - - $db->clearFlag( DBO_TRX ); - $db->onTransactionIdle( - function () use ( $db ) { - $db->setFlag( DBO_TRX ); - }, - __METHOD__ - ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - } - - public function testTransactionResolution() { - $db = $this->db; - - $db->clearFlag( DBO_TRX ); - $db->begin( __METHOD__ ); - $called = false; - $db->onTransactionResolution( function () use ( $db, &$called ) { - $called = true; - $db->setFlag( DBO_TRX ); - } ); - $db->commit( __METHOD__ ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - $this->assertTrue( $called, 'Callback reached' ); - - $db->clearFlag( DBO_TRX ); - $db->begin( __METHOD__ ); - $called = false; - $db->onTransactionResolution( function () use ( $db, &$called ) { - $called = true; - $db->setFlag( DBO_TRX ); - } ); - $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); - $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); - $this->assertTrue( $called, 'Callback reached' ); - } - - /** - * @covers Database::setTransactionListener() - */ - public function testTransactionListener() { - $db = $this->db; - - $db->setTransactionListener( 'ping', function () use ( $db, &$called ) { - $called = true; - } ); - - $called = false; - $db->begin( __METHOD__ ); - $db->commit( __METHOD__ ); - $this->assertTrue( $called, 'Callback reached' ); - - $called = false; - $db->begin( __METHOD__ ); - $db->commit( __METHOD__ ); - $this->assertTrue( $called, 'Callback still reached' ); - - $called = false; - $db->begin( __METHOD__ ); - $db->rollback( __METHOD__ ); - $this->assertTrue( $called, 'Callback reached' ); - - $db->setTransactionListener( 'ping', null ); - $called = false; - $db->begin( __METHOD__ ); - $db->commit( __METHOD__ ); - $this->assertFalse( $called, 'Callback not reached' ); - } - - /** - * @covers Database::flushSnapshot() - */ - public function testFlushSnapshot() { - $db = $this->db; - - $db->flushSnapshot( __METHOD__ ); // ok - $db->flushSnapshot( __METHOD__ ); // ok - - $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); - $db->query( 'SELECT 1', __METHOD__ ); - $this->assertTrue( (bool)$db->trxLevel(), "Transaction started." ); - $db->flushSnapshot( __METHOD__ ); // ok - $db->restoreFlags( $db::RESTORE_PRIOR ); - - $this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." ); - } - - public function testGetScopedLock() { - $db = $this->db; - - $db->setFlag( DBO_TRX ); - try { - $this->badLockingMethodImplicit( $db ); - } catch ( RunTimeException $e ) { - $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." ); - } - $db->clearFlag( DBO_TRX ); - $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); - $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) ); - - try { - $this->badLockingMethodExplicit( $db ); - } catch ( RunTimeException $e ) { - $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." ); - } - $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); - $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) ); - } - - private function badLockingMethodImplicit( IDatabase $db ) { - $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 ); - $db->query( "SELECT 1" ); // trigger DBO_TRX - throw new RunTimeException( "Uh oh!" ); - } - - private function badLockingMethodExplicit( IDatabase $db ) { - $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 ); - $db->begin( __METHOD__ ); - throw new RunTimeException( "Uh oh!" ); - } - - /** - * @covers Database::getFlag( - * @covers Database::setFlag() - * @covers Database::restoreFlags() - */ - public function testFlagSetting() { - $db = $this->db; - $origTrx = $db->getFlag( DBO_TRX ); - $origSsl = $db->getFlag( DBO_SSL ); - - $origTrx - ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); - $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) ); - - $origSsl - ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR ); - $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) ); - - $db->restoreFlags( $db::RESTORE_INITIAL ); - $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) ); - $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); - - $origTrx - ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); - $origSsl - ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR ) - : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR ); - - $db->restoreFlags(); - $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); - $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) ); - - $db->restoreFlags(); - $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); - $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) ); - } - - /** - * @covers Database::tablePrefix() - * @covers Database::dbSchema() - */ - public function testMutators() { - $old = $this->db->tablePrefix(); - $this->assertType( 'string', $old, 'Prefix is string' ); - $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" ); - $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) ); - $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" ); - $this->db->tablePrefix( $old ); - $this->assertNotEquals( 'xxx', $this->db->tablePrefix() ); - - $old = $this->db->dbSchema(); - $this->assertType( 'string', $old, 'Schema is string' ); - $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" ); - $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) ); - $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" ); - $this->db->dbSchema( $old ); - $this->assertNotEquals( 'xxx', $this->db->dbSchema() ); - } -} diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php index 0d817fa50e..b564310429 100644 --- a/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php +++ b/tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php @@ -107,7 +107,7 @@ class FakeDatabaseMysqlBase extends DatabaseMysqlBase { class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideDiapers - * @covers DatabaseMysqlBase::addIdentifierQuotes + * @covers Wikimedia\Rdbms\DatabaseMysqlBase::addIdentifierQuotes */ public function testAddIdentifierQuotes( $expected, $in ) { $db = new FakeDatabaseMysqlBase(); @@ -189,7 +189,7 @@ class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase { } /** - * @covers DatabaseMysqlBase::listViews + * @covers Wikimedia\Rdbms\DatabaseMysqlBase::listViews */ public function testListviews() { $db = $this->getMockForViews(); @@ -210,7 +210,7 @@ class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideComparePositions - * @covers MySQLMasterPos + * @covers Wikimedia\Rdbms\MySQLMasterPos */ public function testHasReached( MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos, $match ) { if ( $match ) { @@ -283,7 +283,7 @@ class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideChannelPositions - * @covers MySQLMasterPos + * @covers Wikimedia\Rdbms\MySQLMasterPos */ public function testChannelsMatch( MySQLMasterPos $pos1, MySQLMasterPos $pos2, $matches ) { $this->assertEquals( $matches, $pos1->channelsMatch( $pos2 ) ); @@ -317,8 +317,8 @@ class DatabaseMysqlBaseTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provideLagAmounts - * @covers DatabaseMysqlBase::getLag - * @covers DatabaseMysqlBase::getLagFromPtHeartbeat + * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLag + * @covers Wikimedia\Rdbms\DatabaseMysqlBase::getLagFromPtHeartbeat */ public function testPtHeartbeat( $lag ) { $db = $this->getMockBuilder( 'DatabaseMysqli' ) diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php new file mode 100644 index 0000000000..57666bdc0c --- /dev/null +++ b/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php @@ -0,0 +1,1075 @@ +database = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => true ] ); + } + + protected function assertLastSql( $sqlText ) { + $this->assertEquals( + $sqlText, + $this->database->getLastSqls() + ); + } + + protected function assertLastSqlDb( $sqlText, $db ) { + $this->assertEquals( $sqlText, $db->getLastSqls() ); + } + + /** + * @dataProvider provideSelect + * @covers Wikimedia\Rdbms\Database::select + */ + public function testSelect( $sql, $sqlText ) { + $this->database->select( + $sql['tables'], + $sql['fields'], + isset( $sql['conds'] ) ? $sql['conds'] : [], + __METHOD__, + isset( $sql['options'] ) ? $sql['options'] : [], + isset( $sql['join_conds'] ) ? $sql['join_conds'] : [] + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideSelect() { + return [ + [ + [ + 'tables' => 'table', + 'fields' => [ 'field', 'alias' => 'field2' ], + 'conds' => [ 'alias' => 'text' ], + ], + "SELECT field,field2 AS alias " . + "FROM table " . + "WHERE alias = 'text'" + ], + [ + [ + 'tables' => 'table', + 'fields' => [ 'field', 'alias' => 'field2' ], + 'conds' => [ 'alias' => 'text' ], + 'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ], + ], + "SELECT field,field2 AS alias " . + "FROM table " . + "WHERE alias = 'text' " . + "ORDER BY field " . + "LIMIT 1" + ], + [ + [ + 'tables' => [ 'table', 't2' => 'table2' ], + 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ], + 'conds' => [ 'alias' => 'text' ], + 'options' => [ 'LIMIT' => 1, 'ORDER BY' => 'field' ], + 'join_conds' => [ 't2' => [ + 'LEFT JOIN', 'tid = t2.id' + ] ], + ], + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "ORDER BY field " . + "LIMIT 1" + ], + [ + [ + 'tables' => [ 'table', 't2' => 'table2' ], + 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ], + 'conds' => [ 'alias' => 'text' ], + 'options' => [ 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ], + 'join_conds' => [ 't2' => [ + 'LEFT JOIN', 'tid = t2.id' + ] ], + ], + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "GROUP BY field HAVING COUNT(*) > 1 " . + "LIMIT 1" + ], + [ + [ + 'tables' => [ 'table', 't2' => 'table2' ], + 'fields' => [ 'tid', 'field', 'alias' => 'field2', 't2.id' ], + 'conds' => [ 'alias' => 'text' ], + 'options' => [ + 'LIMIT' => 1, + 'GROUP BY' => [ 'field', 'field2' ], + 'HAVING' => [ 'COUNT(*) > 1', 'field' => 1 ] + ], + 'join_conds' => [ 't2' => [ + 'LEFT JOIN', 'tid = t2.id' + ] ], + ], + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " . + "LIMIT 1" + ], + [ + [ + 'tables' => [ 'table' ], + 'fields' => [ 'alias' => 'field' ], + 'conds' => [ 'alias' => [ 1, 2, 3, 4 ] ], + ], + "SELECT field AS alias " . + "FROM table " . + "WHERE alias IN ('1','2','3','4')" + ], + ]; + } + + /** + * @dataProvider provideUpdate + * @covers Wikimedia\Rdbms\Database::update + */ + public function testUpdate( $sql, $sqlText ) { + $this->database->update( + $sql['table'], + $sql['values'], + $sql['conds'], + __METHOD__, + isset( $sql['options'] ) ? $sql['options'] : [] + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideUpdate() { + return [ + [ + [ + 'table' => 'table', + 'values' => [ 'field' => 'text', 'field2' => 'text2' ], + 'conds' => [ 'alias' => 'text' ], + ], + "UPDATE table " . + "SET field = 'text'" . + ",field2 = 'text2' " . + "WHERE alias = 'text'" + ], + [ + [ + 'table' => 'table', + 'values' => [ 'field = other', 'field2' => 'text2' ], + 'conds' => [ 'id' => '1' ], + ], + "UPDATE table " . + "SET field = other" . + ",field2 = 'text2' " . + "WHERE id = '1'" + ], + [ + [ + 'table' => 'table', + 'values' => [ 'field = other', 'field2' => 'text2' ], + 'conds' => '*', + ], + "UPDATE table " . + "SET field = other" . + ",field2 = 'text2'" + ], + ]; + } + + /** + * @dataProvider provideDelete + * @covers Wikimedia\Rdbms\Database::delete + */ + public function testDelete( $sql, $sqlText ) { + $this->database->delete( + $sql['table'], + $sql['conds'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideDelete() { + return [ + [ + [ + 'table' => 'table', + 'conds' => [ 'alias' => 'text' ], + ], + "DELETE FROM table " . + "WHERE alias = 'text'" + ], + [ + [ + 'table' => 'table', + 'conds' => '*', + ], + "DELETE FROM table" + ], + ]; + } + + /** + * @dataProvider provideUpsert + * @covers Wikimedia\Rdbms\Database::upsert + */ + public function testUpsert( $sql, $sqlText ) { + $this->database->upsert( + $sql['table'], + $sql['rows'], + $sql['uniqueIndexes'], + $sql['set'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideUpsert() { + return [ + [ + [ + 'table' => 'upsert_table', + 'rows' => [ 'field' => 'text', 'field2' => 'text2' ], + 'uniqueIndexes' => [ 'field' ], + 'set' => [ 'field' => 'set' ], + ], + "BEGIN; " . + "UPDATE upsert_table " . + "SET field = 'set' " . + "WHERE ((field = 'text')); " . + "INSERT IGNORE INTO upsert_table " . + "(field,field2) " . + "VALUES ('text','text2'); " . + "COMMIT" + ], + ]; + } + + /** + * @dataProvider provideDeleteJoin + * @covers Wikimedia\Rdbms\Database::deleteJoin + */ + public function testDeleteJoin( $sql, $sqlText ) { + $this->database->deleteJoin( + $sql['delTable'], + $sql['joinTable'], + $sql['delVar'], + $sql['joinVar'], + $sql['conds'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideDeleteJoin() { + return [ + [ + [ + 'delTable' => 'table', + 'joinTable' => 'table_join', + 'delVar' => 'field', + 'joinVar' => 'field_join', + 'conds' => [ 'alias' => 'text' ], + ], + "DELETE FROM table " . + "WHERE field IN (" . + "SELECT field_join FROM table_join WHERE alias = 'text'" . + ")" + ], + [ + [ + 'delTable' => 'table', + 'joinTable' => 'table_join', + 'delVar' => 'field', + 'joinVar' => 'field_join', + 'conds' => '*', + ], + "DELETE FROM table " . + "WHERE field IN (" . + "SELECT field_join FROM table_join " . + ")" + ], + ]; + } + + /** + * @dataProvider provideInsert + * @covers Wikimedia\Rdbms\Database::insert + */ + public function testInsert( $sql, $sqlText ) { + $this->database->insert( + $sql['table'], + $sql['rows'], + __METHOD__, + isset( $sql['options'] ) ? $sql['options'] : [] + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideInsert() { + return [ + [ + [ + 'table' => 'table', + 'rows' => [ 'field' => 'text', 'field2' => 2 ], + ], + "INSERT INTO table " . + "(field,field2) " . + "VALUES ('text','2')" + ], + [ + [ + 'table' => 'table', + 'rows' => [ 'field' => 'text', 'field2' => 2 ], + 'options' => 'IGNORE', + ], + "INSERT IGNORE INTO table " . + "(field,field2) " . + "VALUES ('text','2')" + ], + [ + [ + 'table' => 'table', + 'rows' => [ + [ 'field' => 'text', 'field2' => 2 ], + [ 'field' => 'multi', 'field2' => 3 ], + ], + 'options' => 'IGNORE', + ], + "INSERT IGNORE INTO table " . + "(field,field2) " . + "VALUES " . + "('text','2')," . + "('multi','3')" + ], + ]; + } + + /** + * @dataProvider provideInsertSelect + * @covers Wikimedia\Rdbms\Database::insertSelect + */ + public function testInsertSelect( $sql, $sqlTextNative, $sqlSelect, $sqlInsert ) { + $this->database->insertSelect( + $sql['destTable'], + $sql['srcTable'], + $sql['varMap'], + $sql['conds'], + __METHOD__, + isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [], + isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [], + isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : [] + ); + $this->assertLastSql( $sqlTextNative ); + + $dbWeb = new DatabaseTestHelper( __CLASS__, [ 'cliMode' => false ] ); + $dbWeb->forceNextResult( [ + array_flip( array_keys( $sql['varMap'] ) ) + ] ); + $dbWeb->insertSelect( + $sql['destTable'], + $sql['srcTable'], + $sql['varMap'], + $sql['conds'], + __METHOD__, + isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : [], + isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : [], + isset( $sql['selectJoinConds'] ) ? $sql['selectJoinConds'] : [] + ); + $this->assertLastSqlDb( implode( '; ', [ $sqlSelect, $sqlInsert ] ), $dbWeb ); + } + + public static function provideInsertSelect() { + return [ + [ + [ + 'destTable' => 'insert_table', + 'srcTable' => 'select_table', + 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], + 'conds' => '*', + ], + "INSERT INTO insert_table " . + "(field_insert,field) " . + "SELECT field_select,field2 " . + "FROM select_table WHERE *", + "SELECT field_select AS field_insert,field2 AS field " . + "FROM select_table WHERE * FOR UPDATE", + "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')" + ], + [ + [ + 'destTable' => 'insert_table', + 'srcTable' => 'select_table', + 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], + 'conds' => [ 'field' => 2 ], + ], + "INSERT INTO insert_table " . + "(field_insert,field) " . + "SELECT field_select,field2 " . + "FROM select_table " . + "WHERE field = '2'", + "SELECT field_select AS field_insert,field2 AS field FROM " . + "select_table WHERE field = '2' FOR UPDATE", + "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')" + ], + [ + [ + 'destTable' => 'insert_table', + 'srcTable' => 'select_table', + 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], + 'conds' => [ 'field' => 2 ], + 'insertOptions' => 'IGNORE', + 'selectOptions' => [ 'ORDER BY' => 'field' ], + ], + "INSERT IGNORE INTO insert_table " . + "(field_insert,field) " . + "SELECT field_select,field2 " . + "FROM select_table " . + "WHERE field = '2' " . + "ORDER BY field", + "SELECT field_select AS field_insert,field2 AS field " . + "FROM select_table WHERE field = '2' ORDER BY field FOR UPDATE", + "INSERT IGNORE INTO insert_table (field_insert,field) VALUES ('0','1')" + ], + [ + [ + 'destTable' => 'insert_table', + 'srcTable' => [ 'select_table1', 'select_table2' ], + 'varMap' => [ 'field_insert' => 'field_select', 'field' => 'field2' ], + 'conds' => [ 'field' => 2 ], + 'selectOptions' => [ 'ORDER BY' => 'field', 'FORCE INDEX' => [ 'select_table1' => 'index1' ] ], + 'selectJoinConds' => [ + 'select_table2' => [ 'LEFT JOIN', [ 'select_table1.foo = select_table2.bar' ] ], + ], + ], + "INSERT INTO insert_table " . + "(field_insert,field) " . + "SELECT field_select,field2 " . + "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " . + "WHERE field = '2' " . + "ORDER BY field", + "SELECT field_select AS field_insert,field2 AS field " . + "FROM select_table1 LEFT JOIN select_table2 ON ((select_table1.foo = select_table2.bar)) " . + "WHERE field = '2' ORDER BY field FOR UPDATE", + "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')" + ], + ]; + } + + /** + * @dataProvider provideReplace + * @covers Wikimedia\Rdbms\Database::replace + */ + public function testReplace( $sql, $sqlText ) { + $this->database->replace( + $sql['table'], + $sql['uniqueIndexes'], + $sql['rows'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideReplace() { + return [ + [ + [ + 'table' => 'replace_table', + 'uniqueIndexes' => [ 'field' ], + 'rows' => [ 'field' => 'text', 'field2' => 'text2' ], + ], + "DELETE FROM replace_table " . + "WHERE ( field='text' ); " . + "INSERT INTO replace_table " . + "(field,field2) " . + "VALUES ('text','text2')" + ], + [ + [ + 'table' => 'module_deps', + 'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ], + 'rows' => [ + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ], + ], + "DELETE FROM module_deps " . + "WHERE ( md_module='module' AND md_skin='skin' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps')" + ], + [ + [ + 'table' => 'module_deps', + 'uniqueIndexes' => [ [ 'md_module', 'md_skin' ] ], + 'rows' => [ + [ + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ], [ + 'md_module' => 'module2', + 'md_skin' => 'skin2', + 'md_deps' => 'deps2', + ], + ], + ], + "DELETE FROM module_deps " . + "WHERE ( md_module='module' AND md_skin='skin' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps'); " . + "DELETE FROM module_deps " . + "WHERE ( md_module='module2' AND md_skin='skin2' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module2','skin2','deps2')" + ], + [ + [ + 'table' => 'module_deps', + 'uniqueIndexes' => [ 'md_module', 'md_skin' ], + 'rows' => [ + [ + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ], [ + 'md_module' => 'module2', + 'md_skin' => 'skin2', + 'md_deps' => 'deps2', + ], + ], + ], + "DELETE FROM module_deps " . + "WHERE ( md_module='module' ) OR ( md_skin='skin' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps'); " . + "DELETE FROM module_deps " . + "WHERE ( md_module='module2' ) OR ( md_skin='skin2' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module2','skin2','deps2')" + ], + [ + [ + 'table' => 'module_deps', + 'uniqueIndexes' => [], + 'rows' => [ + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ], + ], + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps')" + ], + ]; + } + + /** + * @dataProvider provideNativeReplace + * @covers Wikimedia\Rdbms\Database::nativeReplace + */ + public function testNativeReplace( $sql, $sqlText ) { + $this->database->nativeReplace( + $sql['table'], + $sql['rows'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideNativeReplace() { + return [ + [ + [ + 'table' => 'replace_table', + 'rows' => [ 'field' => 'text', 'field2' => 'text2' ], + ], + "REPLACE INTO replace_table " . + "(field,field2) " . + "VALUES ('text','text2')" + ], + ]; + } + + /** + * @dataProvider provideConditional + * @covers Wikimedia\Rdbms\Database::conditional + */ + public function testConditional( $sql, $sqlText ) { + $this->assertEquals( trim( $this->database->conditional( + $sql['conds'], + $sql['true'], + $sql['false'] + ) ), $sqlText ); + } + + public static function provideConditional() { + return [ + [ + [ + 'conds' => [ 'field' => 'text' ], + 'true' => 1, + 'false' => 'NULL', + ], + "(CASE WHEN field = 'text' THEN 1 ELSE NULL END)" + ], + [ + [ + 'conds' => [ 'field' => 'text', 'field2' => 'anothertext' ], + 'true' => 1, + 'false' => 'NULL', + ], + "(CASE WHEN field = 'text' AND field2 = 'anothertext' THEN 1 ELSE NULL END)" + ], + [ + [ + 'conds' => 'field=1', + 'true' => 1, + 'false' => 'NULL', + ], + "(CASE WHEN field=1 THEN 1 ELSE NULL END)" + ], + ]; + } + + /** + * @dataProvider provideBuildConcat + * @covers Wikimedia\Rdbms\Database::buildConcat + */ + public function testBuildConcat( $stringList, $sqlText ) { + $this->assertEquals( trim( $this->database->buildConcat( + $stringList + ) ), $sqlText ); + } + + public static function provideBuildConcat() { + return [ + [ + [ 'field', 'field2' ], + "CONCAT(field,field2)" + ], + [ + [ "'test'", 'field2' ], + "CONCAT('test',field2)" + ], + ]; + } + + /** + * @dataProvider provideBuildLike + * @covers Wikimedia\Rdbms\Database::buildLike + */ + public function testBuildLike( $array, $sqlText ) { + $this->assertEquals( trim( $this->database->buildLike( + $array + ) ), $sqlText ); + } + + public static function provideBuildLike() { + return [ + [ + 'text', + "LIKE 'text' ESCAPE '`'" + ], + [ + [ 'text', new LikeMatch( '%' ) ], + "LIKE 'text%' ESCAPE '`'" + ], + [ + [ 'text', new LikeMatch( '%' ), 'text2' ], + "LIKE 'text%text2' ESCAPE '`'" + ], + [ + [ 'text', new LikeMatch( '_' ) ], + "LIKE 'text_' ESCAPE '`'" + ], + [ + 'more_text', + "LIKE 'more`_text' ESCAPE '`'" + ], + [ + [ 'C:\\Windows\\', new LikeMatch( '%' ) ], + "LIKE 'C:\\Windows\\%' ESCAPE '`'" + ], + [ + [ 'accent`_test`', new LikeMatch( '%' ) ], + "LIKE 'accent```_test``%' ESCAPE '`'" + ], + ]; + } + + /** + * @dataProvider provideUnionQueries + * @covers Wikimedia\Rdbms\Database::unionQueries + */ + public function testUnionQueries( $sql, $sqlText ) { + $this->assertEquals( trim( $this->database->unionQueries( + $sql['sqls'], + $sql['all'] + ) ), $sqlText ); + } + + public static function provideUnionQueries() { + return [ + [ + [ + 'sqls' => [ 'RAW SQL', 'RAW2SQL' ], + 'all' => true, + ], + "(RAW SQL) UNION ALL (RAW2SQL)" + ], + [ + [ + 'sqls' => [ 'RAW SQL', 'RAW2SQL' ], + 'all' => false, + ], + "(RAW SQL) UNION (RAW2SQL)" + ], + [ + [ + 'sqls' => [ 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ], + 'all' => false, + ], + "(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)" + ], + ]; + } + + /** + * @dataProvider provideUnionConditionPermutations + * @covers Wikimedia\Rdbms\Database::unionConditionPermutations + */ + public function testUnionConditionPermutations( $params, $expect ) { + if ( isset( $params['unionSupportsOrderAndLimit'] ) ) { + $this->database->setUnionSupportsOrderAndLimit( $params['unionSupportsOrderAndLimit'] ); + } + + $sql = trim( $this->database->unionConditionPermutations( + $params['table'], + $params['vars'], + $params['permute_conds'], + isset( $params['extra_conds'] ) ? $params['extra_conds'] : '', + 'FNAME', + isset( $params['options'] ) ? $params['options'] : [], + isset( $params['join_conds'] ) ? $params['join_conds'] : [] + ) ); + $this->assertEquals( $expect, $sql ); + } + + public static function provideUnionConditionPermutations() { + return [ + // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong + [ + [ + 'table' => [ 'table1', 'table2' ], + 'vars' => [ 'field1', 'alias' => 'field2' ], + 'permute_conds' => [ + 'field3' => [ 1, 2, 3 ], + 'duplicates' => [ 4, 5, 4 ], + 'empty' => [], + 'single' => [ 0 ], + ], + 'extra_conds' => 'table2.bar > 23', + 'options' => [ + 'ORDER BY' => [ 'field1', 'alias' ], + 'INNER ORDER BY' => [ 'field1', 'field2' ], + 'LIMIT' => 100, + ], + 'join_conds' => [ + 'table2' => [ 'JOIN', 'table1.foo_id = table2.foo_id' ], + ], + ], + "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '1' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . + "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '1' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . + "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '2' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . + "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '2' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . + "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '3' AND duplicates = '4' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) UNION ALL " . + "(SELECT field1,field2 AS alias FROM table1 JOIN table2 ON ((table1.foo_id = table2.foo_id)) WHERE field3 = '3' AND duplicates = '5' AND single = '0' AND (table2.bar > 23) ORDER BY field1,field2 LIMIT 100 ) " . + "ORDER BY field1,alias LIMIT 100" + ], + [ + [ + 'table' => 'foo', + 'vars' => [ 'foo_id' ], + 'permute_conds' => [ + 'bar' => [ 1, 2, 3 ], + ], + 'extra_conds' => [ 'baz' => null ], + 'options' => [ + 'NOTALL', + 'ORDER BY' => [ 'foo_id' ], + 'LIMIT' => 25, + ], + ], + "(SELECT foo_id FROM foo WHERE bar = '1' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) UNION " . + "(SELECT foo_id FROM foo WHERE bar = '2' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) UNION " . + "(SELECT foo_id FROM foo WHERE bar = '3' AND baz IS NULL ORDER BY foo_id LIMIT 25 ) " . + "ORDER BY foo_id LIMIT 25" + ], + [ + [ + 'table' => 'foo', + 'vars' => [ 'foo_id' ], + 'permute_conds' => [ + 'bar' => [ 1, 2, 3 ], + ], + 'extra_conds' => [ 'baz' => null ], + 'options' => [ + 'NOTALL' => true, + 'ORDER BY' => [ 'foo_id' ], + 'LIMIT' => 25, + ], + 'unionSupportsOrderAndLimit' => false, + ], + "(SELECT foo_id FROM foo WHERE bar = '1' AND baz IS NULL ) UNION " . + "(SELECT foo_id FROM foo WHERE bar = '2' AND baz IS NULL ) UNION " . + "(SELECT foo_id FROM foo WHERE bar = '3' AND baz IS NULL ) " . + "ORDER BY foo_id LIMIT 25" + ], + [ + [ + 'table' => 'foo', + 'vars' => [ 'foo_id' ], + 'permute_conds' => [], + 'extra_conds' => [ 'baz' => null ], + 'options' => [ + 'ORDER BY' => [ 'foo_id' ], + 'LIMIT' => 25, + ], + ], + "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 25" + ], + [ + [ + 'table' => 'foo', + 'vars' => [ 'foo_id' ], + 'permute_conds' => [ + 'bar' => [], + ], + 'extra_conds' => [ 'baz' => null ], + 'options' => [ + 'ORDER BY' => [ 'foo_id' ], + 'LIMIT' => 25, + ], + ], + "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 25" + ], + [ + [ + 'table' => 'foo', + 'vars' => [ 'foo_id' ], + 'permute_conds' => [ + 'bar' => [ 1 ], + ], + 'options' => [ + 'ORDER BY' => [ 'foo_id' ], + 'LIMIT' => 25, + 'OFFSET' => 150, + ], + ], + "SELECT foo_id FROM foo WHERE bar = '1' ORDER BY foo_id LIMIT 150,25" + ], + [ + [ + 'table' => 'foo', + 'vars' => [ 'foo_id' ], + 'permute_conds' => [], + 'extra_conds' => [ 'baz' => null ], + 'options' => [ + 'ORDER BY' => [ 'foo_id' ], + 'LIMIT' => 25, + 'OFFSET' => 150, + 'INNER ORDER BY' => [ 'bar_id' ], + ], + ], + "(SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY bar_id LIMIT 175 ) ORDER BY foo_id LIMIT 150,25" + ], + [ + [ + 'table' => 'foo', + 'vars' => [ 'foo_id' ], + 'permute_conds' => [], + 'extra_conds' => [ 'baz' => null ], + 'options' => [ + 'ORDER BY' => [ 'foo_id' ], + 'LIMIT' => 25, + 'OFFSET' => 150, + 'INNER ORDER BY' => [ 'bar_id' ], + ], + 'unionSupportsOrderAndLimit' => false, + ], + "SELECT foo_id FROM foo WHERE baz IS NULL ORDER BY foo_id LIMIT 150,25" + ], + // @codingStandardsIgnoreEnd + ]; + } + + /** + * @covers Wikimedia\Rdbms\Database::commit + */ + public function testTransactionCommit() { + $this->database->begin( __METHOD__ ); + $this->database->commit( __METHOD__ ); + $this->assertLastSql( 'BEGIN; COMMIT' ); + } + + /** + * @covers Wikimedia\Rdbms\Database::rollback + */ + public function testTransactionRollback() { + $this->database->begin( __METHOD__ ); + $this->database->rollback( __METHOD__ ); + $this->assertLastSql( 'BEGIN; ROLLBACK' ); + } + + /** + * @covers Wikimedia\Rdbms\Database::dropTable + */ + public function testDropTable() { + $this->database->setExistingTables( [ 'table' ] ); + $this->database->dropTable( 'table', __METHOD__ ); + $this->assertLastSql( 'DROP TABLE table CASCADE' ); + } + + /** + * @covers Wikimedia\Rdbms\Database::dropTable + */ + public function testDropNonExistingTable() { + $this->assertFalse( + $this->database->dropTable( 'non_existing', __METHOD__ ) + ); + } + + /** + * @dataProvider provideMakeList + * @covers Wikimedia\Rdbms\Database::makeList + */ + public function testMakeList( $list, $mode, $sqlText ) { + $this->assertEquals( trim( $this->database->makeList( + $list, $mode + ) ), $sqlText ); + } + + public static function provideMakeList() { + return [ + [ + [ 'value', 'value2' ], + LIST_COMMA, + "'value','value2'" + ], + [ + [ 'field', 'field2' ], + LIST_NAMES, + "field,field2" + ], + [ + [ 'field' => 'value', 'field2' => 'value2' ], + LIST_AND, + "field = 'value' AND field2 = 'value2'" + ], + [ + [ 'field' => null, "field2 != 'value2'" ], + LIST_AND, + "field IS NULL AND (field2 != 'value2')" + ], + [ + [ 'field' => [ 'value', null, 'value2' ], 'field2' => 'value2' ], + LIST_AND, + "(field IN ('value','value2') OR field IS NULL) AND field2 = 'value2'" + ], + [ + [ 'field' => [ null ], 'field2' => null ], + LIST_AND, + "field IS NULL AND field2 IS NULL" + ], + [ + [ 'field' => 'value', 'field2' => 'value2' ], + LIST_OR, + "field = 'value' OR field2 = 'value2'" + ], + [ + [ 'field' => 'value', 'field2' => null ], + LIST_OR, + "field = 'value' OR field2 IS NULL" + ], + [ + [ 'field' => [ 'value', 'value2' ], 'field2' => [ 'value' ] ], + LIST_OR, + "field IN ('value','value2') OR field2 = 'value'" + ], + [ + [ 'field' => [ null, 'value', null, 'value2' ], "field2 != 'value2'" ], + LIST_OR, + "(field IN ('value','value2') OR field IS NULL) OR (field2 != 'value2')" + ], + [ + [ 'field' => 'value', 'field2' => 'value2' ], + LIST_SET, + "field = 'value',field2 = 'value2'" + ], + [ + [ 'field' => 'value', 'field2' => null ], + LIST_SET, + "field = 'value',field2 = NULL" + ], + [ + [ 'field' => 'value', "field2 != 'value2'" ], + LIST_SET, + "field = 'value',field2 != 'value2'" + ], + ]; + } + + public function testSessionTempTables() { + $temp1 = $this->database->tableName( 'tmp_table_1' ); + $temp2 = $this->database->tableName( 'tmp_table_2' ); + $temp3 = $this->database->tableName( 'tmp_table_3' ); + + $this->database->query( "CREATE TEMPORARY TABLE $temp1 LIKE orig_tbl", __METHOD__ ); + $this->database->query( "CREATE TEMPORARY TABLE $temp2 LIKE orig_tbl", __METHOD__ ); + $this->database->query( "CREATE TEMPORARY TABLE $temp3 LIKE orig_tbl", __METHOD__ ); + + $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); + $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); + $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); + + $this->database->dropTable( 'tmp_table_1', __METHOD__ ); + $this->database->dropTable( 'tmp_table_2', __METHOD__ ); + $this->database->dropTable( 'tmp_table_3', __METHOD__ ); + + $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); + $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); + $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); + + $this->database->query( "CREATE TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ ); + $this->database->query( "CREATE TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ ); + $this->database->query( "CREATE TEMPORARY TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ ); + + $this->assertTrue( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); + $this->assertTrue( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); + $this->assertTrue( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); + + $this->database->query( "DROP TEMPORARY TABLE tmp_table_1 LIKE orig_tbl", __METHOD__ ); + $this->database->query( "DROP TEMPORARY TABLE 'tmp_table_2' LIKE orig_tbl", __METHOD__ ); + $this->database->query( "DROP TABLE `tmp_table_3` LIKE orig_tbl", __METHOD__ ); + + $this->assertFalse( $this->database->tableExists( "tmp_table_1", __METHOD__ ) ); + $this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) ); + $this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) ); + } +} diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php new file mode 100644 index 0000000000..9bea7ff0ea --- /dev/null +++ b/tests/phpunit/includes/libs/rdbms/database/DatabaseTest.php @@ -0,0 +1,355 @@ +db = new DatabaseTestHelper( __CLASS__ . '::' . $this->getName() ); + } + + public static function provideAddQuotes() { + return [ + [ null, 'NULL' ], + [ 1234, "'1234'" ], + [ 1234.5678, "'1234.5678'" ], + [ 'string', "'string'" ], + [ 'string\'s cause trouble', "'string\'s cause trouble'" ], + ]; + } + + /** + * @dataProvider provideAddQuotes + * @covers Wikimedia\Rdbms\Database::addQuotes + */ + public function testAddQuotes( $input, $expected ) { + $this->assertEquals( $expected, $this->db->addQuotes( $input ) ); + } + + public static function provideTableName() { + // Formatting is mostly ignored since addIdentifierQuotes is abstract. + // For testing of addIdentifierQuotes, see actual Database subclas tests. + return [ + 'local' => [ + 'tablename', + 'tablename', + 'quoted', + ], + 'local-raw' => [ + 'tablename', + 'tablename', + 'raw', + ], + 'shared' => [ + 'sharedb.tablename', + 'tablename', + 'quoted', + [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ], + ], + 'shared-raw' => [ + 'sharedb.tablename', + 'tablename', + 'raw', + [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => '' ], + ], + 'shared-prefix' => [ + 'sharedb.sh_tablename', + 'tablename', + 'quoted', + [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ], + ], + 'shared-prefix-raw' => [ + 'sharedb.sh_tablename', + 'tablename', + 'raw', + [ 'dbname' => 'sharedb', 'schema' => null, 'prefix' => 'sh_' ], + ], + 'foreign' => [ + 'databasename.tablename', + 'databasename.tablename', + 'quoted', + ], + 'foreign-raw' => [ + 'databasename.tablename', + 'databasename.tablename', + 'raw', + ], + ]; + } + + /** + * @dataProvider provideTableName + * @covers Wikimedia\Rdbms\Database::tableName + */ + public function testTableName( $expected, $table, $format, array $alias = null ) { + if ( $alias ) { + $this->db->setTableAliases( [ $table => $alias ] ); + } + $this->assertEquals( + $expected, + $this->db->tableName( $table, $format ?: 'quoted' ) + ); + } + + /** + * @covers Wikimedia\Rdbms\Database::onTransactionIdle + * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks + */ + public function testTransactionIdle() { + $db = $this->db; + + $db->setFlag( DBO_TRX ); + $called = false; + $flagSet = null; + $db->onTransactionIdle( + function () use ( $db, &$flagSet, &$called ) { + $called = true; + $flagSet = $db->getFlag( DBO_TRX ); + }, + __METHOD__ + ); + $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); + $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); + $this->assertTrue( $called, 'Callback reached' ); + + $db->clearFlag( DBO_TRX ); + $flagSet = null; + $db->onTransactionIdle( + function () use ( $db, &$flagSet ) { + $flagSet = $db->getFlag( DBO_TRX ); + }, + __METHOD__ + ); + $this->assertFalse( $flagSet, 'DBO_TRX off in callback' ); + $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); + + $db->clearFlag( DBO_TRX ); + $db->onTransactionIdle( + function () use ( $db ) { + $db->setFlag( DBO_TRX ); + }, + __METHOD__ + ); + $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); + } + + /** + * @covers Wikimedia\Rdbms\Database::onTransactionResolution + * @covers Wikimedia\Rdbms\Database::runOnTransactionIdleCallbacks + */ + public function testTransactionResolution() { + $db = $this->db; + + $db->clearFlag( DBO_TRX ); + $db->begin( __METHOD__ ); + $called = false; + $db->onTransactionResolution( function () use ( $db, &$called ) { + $called = true; + $db->setFlag( DBO_TRX ); + } ); + $db->commit( __METHOD__ ); + $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); + $this->assertTrue( $called, 'Callback reached' ); + + $db->clearFlag( DBO_TRX ); + $db->begin( __METHOD__ ); + $called = false; + $db->onTransactionResolution( function () use ( $db, &$called ) { + $called = true; + $db->setFlag( DBO_TRX ); + } ); + $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); + $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' ); + $this->assertTrue( $called, 'Callback reached' ); + } + + /** + * @covers Wikimedia\Rdbms\Database::setTransactionListener + */ + public function testTransactionListener() { + $db = $this->db; + + $db->setTransactionListener( 'ping', function () use ( $db, &$called ) { + $called = true; + } ); + + $called = false; + $db->begin( __METHOD__ ); + $db->commit( __METHOD__ ); + $this->assertTrue( $called, 'Callback reached' ); + + $called = false; + $db->begin( __METHOD__ ); + $db->commit( __METHOD__ ); + $this->assertTrue( $called, 'Callback still reached' ); + + $called = false; + $db->begin( __METHOD__ ); + $db->rollback( __METHOD__ ); + $this->assertTrue( $called, 'Callback reached' ); + + $db->setTransactionListener( 'ping', null ); + $called = false; + $db->begin( __METHOD__ ); + $db->commit( __METHOD__ ); + $this->assertFalse( $called, 'Callback not reached' ); + } + + /** + * Use this mock instead of DatabaseTestHelper for cases where + * DatabaseTestHelper is too inflexibile due to mocking too much + * or being too restrictive about fname matching (e.g. for tests + * that assert behaviour when the name is a mismatch, we need to + * catch the error here instead of there). + * + * @return Database + */ + private function getMockDB( $methods = [] ) { + static $abstractMethods = [ + 'affectedRows', + 'closeConnection', + 'dataSeek', + 'doQuery', + 'fetchObject', 'fetchRow', + 'fieldInfo', 'fieldName', + 'getSoftwareLink', 'getServerVersion', + 'getType', + 'indexInfo', + 'insertId', + 'lastError', 'lastErrno', + 'numFields', 'numRows', + 'open', + 'strencode', + ]; + $db = $this->getMockBuilder( Database::class ) + ->disableOriginalConstructor() + ->setMethods( array_values( array_unique( array_merge( + $abstractMethods, + $methods + ) ) ) ) + ->getMock(); + $wdb = TestingAccessWrapper::newFromObject( $db ); + $wdb->trxProfiler = new TransactionProfiler(); + $wdb->connLogger = new \Psr\Log\NullLogger(); + $wdb->queryLogger = new \Psr\Log\NullLogger(); + return $db; + } + + /** + * @covers Wikimedia\Rdbms\Database::flushSnapshot + */ + public function testFlushSnapshot() { + $db = $this->getMockDB( [ 'isOpen' ] ); + $db->method( 'isOpen' )->willReturn( true ); + + $db->flushSnapshot( __METHOD__ ); // ok + $db->flushSnapshot( __METHOD__ ); // ok + + $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); + $db->query( 'SELECT 1', __METHOD__ ); + $this->assertTrue( (bool)$db->trxLevel(), "Transaction started." ); + $db->flushSnapshot( __METHOD__ ); // ok + $db->restoreFlags( $db::RESTORE_PRIOR ); + + $this->assertFalse( (bool)$db->trxLevel(), "Transaction cleared." ); + } + + public function testGetScopedLock() { + $db = $this->getMockDB( [ 'isOpen' ] ); + $db->method( 'isOpen' )->willReturn( true ); + + $db->setFlag( DBO_TRX ); + try { + $this->badLockingMethodImplicit( $db ); + } catch ( RunTimeException $e ) { + $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." ); + } + $db->clearFlag( DBO_TRX ); + $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); + $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) ); + + try { + $this->badLockingMethodExplicit( $db ); + } catch ( RunTimeException $e ) { + $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." ); + } + $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS ); + $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) ); + } + + private function badLockingMethodImplicit( IDatabase $db ) { + $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 ); + $db->query( "SELECT 1" ); // trigger DBO_TRX + throw new RunTimeException( "Uh oh!" ); + } + + private function badLockingMethodExplicit( IDatabase $db ) { + $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 ); + $db->begin( __METHOD__ ); + throw new RunTimeException( "Uh oh!" ); + } + + /** + * @covers Wikimedia\Rdbms\Database::getFlag + * @covers Wikimedia\Rdbms\Database::setFlag + * @covers Wikimedia\Rdbms\Database::restoreFlags + */ + public function testFlagSetting() { + $db = $this->db; + $origTrx = $db->getFlag( DBO_TRX ); + $origSsl = $db->getFlag( DBO_SSL ); + + $origTrx + ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR ) + : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); + $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) ); + + $origSsl + ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR ) + : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR ); + $this->assertEquals( !$origSsl, $db->getFlag( DBO_SSL ) ); + + $db->restoreFlags( $db::RESTORE_INITIAL ); + $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) ); + $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); + + $origTrx + ? $db->clearFlag( DBO_TRX, $db::REMEMBER_PRIOR ) + : $db->setFlag( DBO_TRX, $db::REMEMBER_PRIOR ); + $origSsl + ? $db->clearFlag( DBO_SSL, $db::REMEMBER_PRIOR ) + : $db->setFlag( DBO_SSL, $db::REMEMBER_PRIOR ); + + $db->restoreFlags(); + $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); + $this->assertEquals( !$origTrx, $db->getFlag( DBO_TRX ) ); + + $db->restoreFlags(); + $this->assertEquals( $origSsl, $db->getFlag( DBO_SSL ) ); + $this->assertEquals( $origTrx, $db->getFlag( DBO_TRX ) ); + } + + /** + * @covers Wikimedia\Rdbms\Database::tablePrefix + * @covers Wikimedia\Rdbms\Database::dbSchema + */ + public function testMutators() { + $old = $this->db->tablePrefix(); + $this->assertInternalType( 'string', $old, 'Prefix is string' ); + $this->assertEquals( $old, $this->db->tablePrefix(), "Prefix unchanged" ); + $this->assertEquals( $old, $this->db->tablePrefix( 'xxx' ) ); + $this->assertEquals( 'xxx', $this->db->tablePrefix(), "Prefix set" ); + $this->db->tablePrefix( $old ); + $this->assertNotEquals( 'xxx', $this->db->tablePrefix() ); + + $old = $this->db->dbSchema(); + $this->assertInternalType( 'string', $old, 'Schema is string' ); + $this->assertEquals( $old, $this->db->dbSchema(), "Schema unchanged" ); + $this->assertEquals( $old, $this->db->dbSchema( 'xxx' ) ); + $this->assertEquals( 'xxx', $this->db->dbSchema(), "Schema set" ); + $this->db->dbSchema( $old ); + $this->assertNotEquals( 'xxx', $this->db->dbSchema() ); + } +} diff --git a/tests/phpunit/structure/DatabaseIntegrationTest.php b/tests/phpunit/structure/DatabaseIntegrationTest.php new file mode 100644 index 0000000000..b0c1c8f1f5 --- /dev/null +++ b/tests/phpunit/structure/DatabaseIntegrationTest.php @@ -0,0 +1,56 @@ +db = wfGetDB( DB_MASTER ); + } + + protected function tearDown() { + parent::tearDown(); + if ( $this->functionTest ) { + $this->dropFunctions(); + $this->functionTest = false; + } + $this->db->restoreFlags( IDatabase::RESTORE_INITIAL ); + } + + public function testStoredFunctions() { + if ( !in_array( wfGetDB( DB_MASTER )->getType(), [ 'mysql', 'postgres' ] ) ) { + $this->markTestSkipped( 'MySQL or Postgres required' ); + } + global $IP; + $this->dropFunctions(); + $this->functionTest = true; + $this->assertTrue( + $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" ) + ); + $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ ); + $this->assertEquals( 42, $res->fetchObject()->test ); + } + + private function dropFunctions() { + $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function' + . ( $this->db->getType() == 'postgres' ? '()' : '' ) + ); + } + + public function testUnknownTableCorruptsResults() { + $res = $this->db->select( 'page', '*', [ 'page_id' => 1 ] ); + $this->assertFalse( $this->db->tableExists( 'foobarbaz' ) ); + $this->assertInternalType( 'int', $res->numRows() ); + } +}