object = new ExportSql(); } /** * tearDown for test cases */ protected function tearDown(): void { parent::tearDown(); unset($this->object); } /** * @group medium */ public function testSetPropertiesWithHideSql(): void { // test with hide structure and hide sql as true $GLOBALS['plugin_param']['export_type'] = 'table'; $GLOBALS['plugin_param']['single_table'] = false; $method = new ReflectionMethod(ExportSql::class, 'setProperties'); $method->setAccessible(true); $properties = $method->invoke($this->object, null); self::assertInstanceOf(ExportPluginProperties::class, $properties); self::assertSame('SQL', $properties->getText()); self::assertNull($properties->getOptions()); } /** * @group medium */ public function testSetProperties(): void { // test with hide structure and hide sql as false $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->once()) ->method('getCompatibilities') ->will($this->returnValue(['v1', 'v2'])); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $GLOBALS['dbi'] = $dbi; $GLOBALS['plugin_param']['export_type'] = 'server'; $GLOBALS['plugin_param']['single_table'] = false; $relationParameters = RelationParameters::fromArray([ 'db' => 'db', 'relation' => 'relation', 'column_info' => 'column_info', 'relwork' => true, 'mimework' => true, ]); $_SESSION = ['relation' => [$GLOBALS['server'] => $relationParameters->toArray()]]; $method = new ReflectionMethod(ExportSql::class, 'setProperties'); $method->setAccessible(true); $properties = $method->invoke($this->object, null); self::assertInstanceOf(ExportPluginProperties::class, $properties); self::assertSame('SQL', $properties->getText()); $options = $properties->getOptions(); self::assertInstanceOf(OptionsPropertyRootGroup::class, $options); $generalOptionsArray = $options->getProperties(); $generalOptions = array_shift($generalOptionsArray); self::assertInstanceOf(OptionsPropertyMainGroup::class, $generalOptions); $properties = $generalOptions->getProperties(); $property = array_shift($properties); self::assertInstanceOf(OptionsPropertySubgroup::class, $property); self::assertInstanceOf(BoolPropertyItem::class, $property->getSubgroupHeader()); $leaves = $property->getProperties(); $leaf = array_shift($leaves); self::assertInstanceOf(TextPropertyItem::class, $leaf); $leaf = array_shift($leaves); self::assertInstanceOf(BoolPropertyItem::class, $leaf); $leaf = array_shift($leaves); self::assertInstanceOf(BoolPropertyItem::class, $leaf); $leaf = array_shift($leaves); self::assertInstanceOf(BoolPropertyItem::class, $leaf); $property = array_shift($properties); self::assertInstanceOf(BoolPropertyItem::class, $property); $property = array_shift($properties); self::assertInstanceOf(BoolPropertyItem::class, $property); $property = array_shift($properties); self::assertInstanceOf(BoolPropertyItem::class, $property); $property = array_shift($properties); self::assertInstanceOf(BoolPropertyItem::class, $property); $property = array_shift($properties); self::assertInstanceOf(SelectPropertyItem::class, $property); self::assertSame([ 'v1' => 'v1', 'v2' => 'v2', ], $property->getValues()); $property = array_shift($properties); self::assertInstanceOf(OptionsPropertySubgroup::class, $property); self::assertInstanceOf(RadioPropertyItem::class, $property->getSubgroupHeader()); $structureOptions = array_shift($generalOptionsArray); self::assertInstanceOf(OptionsPropertyMainGroup::class, $structureOptions); $properties = $structureOptions->getProperties(); $property = array_shift($properties); self::assertInstanceOf(OptionsPropertySubgroup::class, $property); self::assertInstanceOf(MessageOnlyPropertyItem::class, $property->getSubgroupHeader()); $leaves = $property->getProperties(); $leaf = array_shift($leaves); self::assertInstanceOf(BoolPropertyItem::class, $leaf); $leaf = array_shift($leaves); self::assertInstanceOf(BoolPropertyItem::class, $leaf); self::assertSame( 'Add DROP TABLE / VIEW / PROCEDURE / FUNCTION / EVENT / TRIGGER statement', $leaf->getText() ); $leaf = array_shift($leaves); self::assertInstanceOf(OptionsPropertySubgroup::class, $leaf); self::assertCount(2, $leaf->getProperties()); self::assertInstanceOf(BoolPropertyItem::class, $leaf->getSubgroupHeader()); $leaf = array_shift($leaves); self::assertInstanceOf(OptionsPropertySubgroup::class, $leaf); self::assertCount(3, $leaf->getProperties()); self::assertInstanceOf(BoolPropertyItem::class, $leaf->getSubgroupHeader()); $leaf = array_shift($leaves); self::assertInstanceOf(BoolPropertyItem::class, $leaf); $leaf = array_shift($leaves); self::assertInstanceOf(BoolPropertyItem::class, $leaf); $property = array_shift($properties); self::assertInstanceOf(BoolPropertyItem::class, $property); $dataOptions = array_shift($generalOptionsArray); self::assertInstanceOf(OptionsPropertyMainGroup::class, $dataOptions); $properties = $dataOptions->getProperties(); self::assertCount(7, $properties); self::assertCount(2, $properties[1]->getProperties()); } public function testExportRoutines(): void { $GLOBALS['crlf'] = '##'; $GLOBALS['sql_drop_table'] = true; $this->expectOutputString( '##DELIMITER $$##DROP PROCEDURE IF EXISTS `test_proc1`$$##CREATE PROCEDURE' . ' `test_proc1` (`p` INT) BEGIN END$$####DROP PROCEDURE IF EXISTS' . ' `test_proc2`$$##CREATE PROCEDURE `test_proc2` (`p` INT) BEGIN END$$####DROP' . ' FUNCTION IF EXISTS `test_func`$$##CREATE FUNCTION' . ' `test_func` (`p` INT) RETURNS INT(11) BEGIN END$$####DELIMITER ;##' ); $this->object->exportRoutines('test_db'); } public function testExportComment(): void { $method = new ReflectionMethod(ExportSql::class, 'exportComment'); $method->setAccessible(true); $GLOBALS['crlf'] = '##'; $GLOBALS['sql_include_comments'] = true; self::assertSame('--##', $method->invoke($this->object, '')); self::assertSame('-- Comment##', $method->invoke($this->object, 'Comment')); $GLOBALS['sql_include_comments'] = false; self::assertSame('', $method->invoke($this->object, 'Comment')); unset($GLOBALS['sql_include_comments']); self::assertSame('', $method->invoke($this->object, 'Comment')); } public function testPossibleCRLF(): void { $method = new ReflectionMethod(ExportSql::class, 'possibleCRLF'); $method->setAccessible(true); $GLOBALS['crlf'] = '##'; $GLOBALS['sql_include_comments'] = true; self::assertSame('##', $method->invoke($this->object, '')); self::assertSame('##', $method->invoke($this->object, 'Comment')); $GLOBALS['sql_include_comments'] = false; self::assertSame('', $method->invoke($this->object, 'Comment')); unset($GLOBALS['sql_include_comments']); self::assertSame('', $method->invoke($this->object, 'Comment')); } public function testExportFooter(): void { $GLOBALS['crlf'] = ''; $GLOBALS['sql_disable_fk'] = true; $GLOBALS['sql_use_transaction'] = true; $GLOBALS['charset'] = 'utf-8'; $GLOBALS['sql_utc_time'] = true; $GLOBALS['old_tz'] = 'GMT'; $GLOBALS['asfile'] = 'yes'; $GLOBALS['output_charset_conversion'] = 'utf-8'; $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->once()) ->method('query') ->with('SET time_zone = "GMT"'); $GLOBALS['dbi'] = $dbi; $this->expectOutputString('SET FOREIGN_KEY_CHECKS=1;COMMIT;'); self::assertTrue($this->object->exportFooter()); } public function testExportHeader(): void { $GLOBALS['crlf'] = "\n"; $GLOBALS['sql_compatibility'] = 'NONE'; $GLOBALS['cfg']['Server']['host'] = 'localhost'; $GLOBALS['cfg']['Server']['port'] = 80; $GLOBALS['sql_disable_fk'] = true; $GLOBALS['sql_use_transaction'] = true; $GLOBALS['sql_utc_time'] = true; $GLOBALS['old_tz'] = 'GMT'; $GLOBALS['asfile'] = 'yes'; $GLOBALS['output_charset_conversion'] = 'utf-8'; $GLOBALS['sql_header_comment'] = "h1C\nh2C"; $GLOBALS['sql_use_transaction'] = true; $GLOBALS['sql_include_comments'] = true; $GLOBALS['charset'] = 'utf-8'; $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->once()) ->method('tryQuery') ->with('SET SQL_MODE=""'); $dbi->expects($this->once()) ->method('fetchValue') ->with('SELECT @@session.time_zone') ->will($this->returnValue('old_tz')); $dbi->expects($this->once()) ->method('query') ->with('SET time_zone = "+00:00"'); $GLOBALS['dbi'] = $dbi; ob_start(); self::assertTrue($this->object->exportHeader()); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString('h1C', $result); self::assertStringContainsString('h2C', $result); self::assertStringContainsString("SET FOREIGN_KEY_CHECKS=0;\n", $result); self::assertStringContainsString('40101 SET', $result); self::assertStringContainsString("SET FOREIGN_KEY_CHECKS=0;\n" . "SET SQL_MODE = \"NO_AUTO_VALUE_ON_ZERO\";\n" . "START TRANSACTION;\n" . "SET time_zone = \"+00:00\";\n", $result); } public function testExportDBCreate(): void { $GLOBALS['sql_compatibility'] = 'NONE'; $GLOBALS['sql_drop_database'] = true; $GLOBALS['sql_backquotes'] = true; $GLOBALS['sql_create_database'] = true; $GLOBALS['sql_create_table'] = true; $GLOBALS['sql_create_view'] = true; $GLOBALS['crlf'] = "\n"; $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $dbi->expects($this->once()) ->method('getDbCollation') ->with('db') ->will($this->returnValue('utf8_general_ci')); $GLOBALS['dbi'] = $dbi; ob_start(); self::assertTrue($this->object->exportDBCreate('db', 'database')); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString("DROP DATABASE IF EXISTS `db`;\n", $result); self::assertStringContainsString( 'CREATE DATABASE IF NOT EXISTS `db` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;', $result ); self::assertStringContainsString('USE `db`;', $result); // case2: no backquotes unset($GLOBALS['sql_compatibility']); $GLOBALS['cfg']['Server']['DisableIS'] = true; unset($GLOBALS['sql_backquotes']); $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $dbi->expects($this->once()) ->method('getDbCollation') ->with('db') ->will($this->returnValue('testcollation')); $GLOBALS['dbi'] = $dbi; ob_start(); self::assertTrue($this->object->exportDBCreate('db', 'database')); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString("DROP DATABASE IF EXISTS db;\n", $result); self::assertStringContainsString( 'CREATE DATABASE IF NOT EXISTS db DEFAULT CHARACTER SET testcollation;', $result ); self::assertStringContainsString('USE db;', $result); } public function testExportDBHeader(): void { $GLOBALS['sql_compatibility'] = 'MSSQL'; $GLOBALS['sql_backquotes'] = true; $GLOBALS['sql_include_comments'] = true; $GLOBALS['crlf'] = "\n"; ob_start(); self::assertTrue($this->object->exportDBHeader('testDB')); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString('"testDB"', $result); // case 2 unset($GLOBALS['sql_compatibility']); unset($GLOBALS['sql_backquotes']); ob_start(); self::assertTrue($this->object->exportDBHeader('testDB')); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString('testDB', $result); } public function testExportEvents(): void { $GLOBALS['crlf'] = "\n"; $GLOBALS['sql_structure_or_data'] = 'structure'; $GLOBALS['sql_procedure_function'] = true; $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->once()) ->method('fetchResult') ->with('SELECT EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA= \'db\';') ->will($this->returnValue(['f1', 'f2'])); $dbi->expects($this->exactly(2)) ->method('getDefinition') ->will( $this->returnValueMap( [ [ 'db', 'EVENT', 'f1', DatabaseInterface::CONNECT_USER, 'f1event', ], [ 'db', 'EVENT', 'f2', DatabaseInterface::CONNECT_USER, 'f2event', ], ] ) ); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $GLOBALS['dbi'] = $dbi; ob_start(); self::assertTrue($this->object->exportEvents('db')); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString("DELIMITER $$\n", $result); self::assertStringContainsString("DELIMITER ;\n", $result); self::assertStringContainsString("f1event$$\n", $result); self::assertStringContainsString("f2event$$\n", $result); } public function testExportDBFooter(): void { $GLOBALS['crlf'] = "\n"; $GLOBALS['sql_constraints'] = 'SqlConstraints'; $GLOBALS['sql_structure_or_data'] = 'structure'; $GLOBALS['sql_procedure_function'] = true; $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $GLOBALS['dbi'] = $dbi; ob_start(); self::assertTrue($this->object->exportDBFooter('db')); $result = ob_get_clean(); self::assertSame('SqlConstraints', $result); } public function testGetTableDefStandIn(): void { $GLOBALS['sql_drop_table'] = true; $GLOBALS['sql_if_not_exists'] = true; $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $dbi->expects($this->once()) ->method('getColumnsFull') ->with('db', 'view') ->will( $this->returnValue( ['cname' => ['Type' => 'int']] ) ); $GLOBALS['dbi'] = $dbi; $result = $this->object->getTableDefStandIn('db', 'view', ''); self::assertStringContainsString('DROP VIEW IF EXISTS `view`;', $result); self::assertStringContainsString('CREATE TABLE IF NOT EXISTS `view` (`cname` int);', $result); } public function testGetTableDefForView(): void { $GLOBALS['sql_drop_table'] = true; $GLOBALS['sql_if_not_exists'] = true; $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $dbi->expects($this->any()) ->method('getColumns') ->with('db', 'view') ->will( $this->returnValue( [ 'cname' => [ 'Type' => 'char', 'Collation' => 'utf-8', 'Null' => 'NO', 'Default' => 'a', 'Comment' => 'cmt', 'Field' => 'fname', ], ] ) ); $GLOBALS['dbi'] = $dbi; $GLOBALS['sql_compatibility'] = 'MSSQL'; $method = new ReflectionMethod(ExportSql::class, 'getTableDefForView'); $method->setAccessible(true); $result = $method->invoke($this->object, 'db', 'view', "\n"); self::assertSame("CREATE TABLE `view`(\n" . " `fname` char COLLATE utf-8 NOT NULL DEFAULT 'a' COMMENT 'cmt'\n" . ");\n", $result); // case 2 unset($GLOBALS['sql_compatibility']); $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $dbi->expects($this->any()) ->method('getColumns') ->with('db', 'view') ->will( $this->returnValue( [ 'cname' => [ 'Type' => 'char', 'Collation' => 'utf-8', 'Null' => 'YES', 'Comment' => 'cmt', 'Field' => 'fname', ], ] ) ); $GLOBALS['dbi'] = $dbi; $result = $method->invoke($this->object, 'db', 'view', "\n", false); self::assertSame("CREATE TABLE IF NOT EXISTS `view`(\n" . " `fname` char COLLATE utf-8 DEFAULT NULL COMMENT 'cmt'\n" . ")\n", $result); } /** * @group medium * @requires PHPUnit < 10 */ public function testGetTableDef(): void { $GLOBALS['sql_compatibility'] = 'MSSQL'; $GLOBALS['sql_auto_increment'] = true; $GLOBALS['sql_drop_table'] = true; $GLOBALS['sql_backquotes'] = true; $GLOBALS['sql_if_not_exists'] = true; $GLOBALS['sql_include_comments'] = true; $GLOBALS['crlf'] = "\n"; if (isset($GLOBALS['sql_constraints'])) { unset($GLOBALS['sql_constraints']); } if (isset($GLOBALS['no_constraints_comments'])) { unset($GLOBALS['no_constraints_comments']); } $resultStub = $this->createMock(DummyResult::class); $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->any()) ->method('query') ->will($this->returnValue($resultStub)); $dbi->expects($this->never()) ->method('fetchSingleRow'); $resultStub->expects($this->once()) ->method('numRows') ->will($this->returnValue(1)); $dbi->expects($this->any()) ->method('fetchValue') ->will($this->returnValue(false)); $tmpres = [ 'Auto_increment' => 1, 'Create_time' => '2000-01-01 10:00:00', 'Update_time' => '2000-01-02 12:00:00', 'Check_time' => '2000-01-02 13:00:00', ]; $resultStub->expects($this->once()) ->method('fetchAssoc') ->will($this->returnValue($tmpres)); $dbi->expects($this->exactly(3)) ->method('tryQuery') ->withConsecutive( ["SHOW TABLE STATUS FROM `db` WHERE Name = 'table'"], ['USE `db`'], ['SHOW CREATE TABLE `db`.`table`'] ) ->willReturnOnConsecutiveCalls($resultStub, $resultStub, $resultStub); $row = [ '', "CREATE TABLE `table` (\n" . "`payment_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,\n" . "`customer_id` smallint(5) unsigned NOT NULL,\n" . "`staff_id` tinyint(3) unsigned NOT NULL,\n" . "`rental_id` int(11) DEFAULT NULL,\n" . "`amount` decimal(5,2) NOT NULL,\n" . "`payment_date` datetime NOT NULL,\n" . "`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n" . "PRIMARY KEY (`payment_id`),\n" . "KEY `idx_fk_staff_id` (`staff_id`),\n" . "KEY `idx_fk_customer_id` (`customer_id`),\n" . "KEY `fk_payment_rental` (`rental_id`),\n" . 'CONSTRAINT `fk_payment_customer` FOREIGN KEY (`customer_id`) REFERENCES' . " `customer` (`customer_id`) ON UPDATE CASCADE,\n" . 'CONSTRAINT `fk_payment_rental` FOREIGN KEY (`rental_id`) REFERENCES' . " `rental` (`rental_id`) ON DELETE SET NULL ON UPDATE CASCADE,\n" . 'CONSTRAINT `fk_payment_staff` FOREIGN KEY (`staff_id`) REFERENCES' . " `staff` (`staff_id`) ON UPDATE CASCADE\n" . ") ENGINE=InnoDB AUTO_INCREMENT=16050 DEFAULT CHARSET=utf8\n", ]; $resultStub->expects($this->exactly(1)) ->method('fetchRow') ->will($this->returnValue($row)); $dbi->expects($this->exactly(2)) ->method('getTable') ->will($this->returnValue(new Table('table', 'db', $dbi))); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $GLOBALS['dbi'] = $dbi; $GLOBALS['cfg']['Server']['DisableIS'] = false; $result = $this->object->getTableDef('db', 'table', "\n", 'example.com/err', true, true, false); self::assertStringContainsString('-- Creation: Jan 01, 2000 at 10:00 AM', $result); self::assertStringContainsString('-- Last update: Jan 02, 2000 at 12:00 PM', $result); self::assertStringContainsString('-- Last check: Jan 02, 2000 at 01:00 PM', $result); self::assertStringContainsString('DROP TABLE IF EXISTS `table`;', $result); self::assertStringContainsString('CREATE TABLE `table`', $result); self::assertStringContainsString('-- Constraints for dumped tables', $GLOBALS['sql_constraints']); self::assertStringContainsString('-- Constraints for table "table"', $GLOBALS['sql_constraints']); self::assertStringContainsString('ALTER TABLE "table"', $GLOBALS['sql_constraints']); self::assertStringContainsString('ADD CONSTRAINT', $GLOBALS['sql_constraints']); self::assertStringContainsString('ALTER TABLE "table"', $GLOBALS['sql_constraints_query']); self::assertStringContainsString('ADD CONSTRAINT', $GLOBALS['sql_constraints_query']); self::assertStringContainsString('ALTER TABLE "table"', $GLOBALS['sql_drop_foreign_keys']); self::assertStringContainsString('DROP FOREIGN KEY', $GLOBALS['sql_drop_foreign_keys']); } /** * @requires PHPUnit < 10 */ public function testGetTableDefWithError(): void { $GLOBALS['sql_compatibility'] = ''; $GLOBALS['sql_auto_increment'] = true; $GLOBALS['sql_drop_table'] = true; $GLOBALS['sql_backquotes'] = false; $GLOBALS['sql_if_not_exists'] = true; $GLOBALS['sql_include_comments'] = true; $GLOBALS['crlf'] = "\n"; if (isset($GLOBALS['sql_constraints'])) { unset($GLOBALS['sql_constraints']); } if (isset($GLOBALS['no_constraints_comments'])) { unset($GLOBALS['no_constraints_comments']); } $resultStub = $this->createMock(DummyResult::class); $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->any()) ->method('query') ->will($this->returnValue($resultStub)); $dbi->expects($this->never()) ->method('fetchSingleRow'); $resultStub->expects($this->once()) ->method('numRows') ->will($this->returnValue(2)); $dbi->expects($this->any()) ->method('fetchValue') ->will($this->returnValue(false)); $tmpres = [ 'Auto_increment' => 1, 'Create_time' => '2000-01-01 10:00:00', 'Update_time' => '2000-01-02 12:00:00', 'Check_time' => '2000-01-02 13:00:00', ]; $resultStub->expects($this->once()) ->method('fetchAssoc') ->will($this->returnValue($tmpres)); $dbi->expects($this->exactly(3)) ->method('tryQuery') ->withConsecutive( ["SHOW TABLE STATUS FROM `db` WHERE Name = 'table'"], ['USE `db`'], ['SHOW CREATE TABLE `db`.`table`'] ) ->willReturnOnConsecutiveCalls($resultStub, $resultStub, $resultStub); $dbi->expects($this->once()) ->method('getError') ->will($this->returnValue('error occurred')); $dbi->expects($this->exactly(2)) ->method('getTable') ->will($this->returnValue(new Table('table', 'db', $dbi))); $dbi->expects($this->any()) ->method('escapeString') ->will($this->returnArgument(0)); $GLOBALS['dbi'] = $dbi; $GLOBALS['cfg']['Server']['DisableIS'] = false; $result = $this->object->getTableDef('db', 'table', "\n", 'example.com/err', true, true, false); self::assertStringContainsString('-- Error reading structure for table db.table: error occurred', $result); } public function testGetTableComments(): void { $_SESSION['relation'] = []; $_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([ 'relwork' => true, 'commwork' => true, 'mimework' => true, 'db' => 'database', 'relation' => 'rel', 'column_info' => 'col', ])->toArray(); $GLOBALS['sql_include_comments'] = true; $GLOBALS['crlf'] = "\n"; $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $dbi->expects($this->exactly(2)) ->method('fetchResult') ->willReturnOnConsecutiveCalls( [ 'foo' => [ 'foreign_table' => 'ftable', 'foreign_field' => 'ffield', ], ], [ 'fieldname' => [ 'values' => 'test-', 'transformation' => 'testfoo', 'mimetype' => 'test<', ], ] ); $GLOBALS['dbi'] = $dbi; $this->object->relation = new Relation($dbi); $method = new ReflectionMethod(ExportSql::class, 'getTableComments'); $method->setAccessible(true); $result = $method->invoke($this->object, 'db', '', true, true); self::assertStringContainsString("-- MEDIA TYPES FOR TABLE :\n" . "-- fieldname\n" . '-- Test<', $result); self::assertStringContainsString("-- RELATIONSHIPS FOR TABLE :\n" . "-- foo\n" . '-- ftable -> ffield', $result); } /** * @group medium */ public function testExportStructure(): void { $GLOBALS['sql_compatibility'] = 'MSSQL'; $GLOBALS['sql_backquotes'] = true; $GLOBALS['sql_include_comments'] = true; $GLOBALS['crlf'] = "\n"; // case 1 ob_start(); self::assertTrue($this->object->exportStructure( 'test_db', 'test_table', "\n", 'localhost', 'create_table', 'test' )); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString('-- Table structure for table "test_table"', $result); self::assertStringContainsString('CREATE TABLE `test_table`', $result); // case 2 unset($GLOBALS['sql_compatibility']); unset($GLOBALS['sql_backquotes']); $GLOBALS['sql_create_trigger'] = true; $GLOBALS['sql_drop_table'] = true; ob_start(); self::assertTrue($this->object->exportStructure( 'test_db', 'test_table', "\n", 'localhost', 'triggers', 'test' )); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString('-- Triggers test_table', $result); self::assertStringContainsString( "CREATE TRIGGER `test_trigger` AFTER INSERT ON `test_table` FOR EACH ROW BEGIN END\n$$", $result ); unset($GLOBALS['sql_create_trigger']); unset($GLOBALS['sql_drop_table']); // case 3 $GLOBALS['sql_views_as_tables'] = false; ob_start(); self::assertTrue($this->object->exportStructure( 'test_db', 'test_table', "\n", 'localhost', 'create_view', 'test' )); $result = ob_get_clean(); $sqlViewsProp = new ReflectionProperty(ExportSql::class, 'sqlViews'); $sqlViewsProp->setAccessible(true); $sqlViews = $sqlViewsProp->getValue($this->object); self::assertSame('', $result); self::assertIsString($sqlViews); self::assertStringContainsString('-- Structure for view test_table', $sqlViews); self::assertStringContainsString('DROP TABLE IF EXISTS `test_table`;', $sqlViews); self::assertStringContainsString('CREATE TABLE `test_table`', $sqlViews); // case 4 $GLOBALS['sql_views_as_tables'] = true; unset($GLOBALS['sql_if_not_exists']); ob_start(); self::assertTrue($this->object->exportStructure( 'test_db', 'test_table', "\n", 'localhost', 'create_view', 'test' )); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString('-- Structure for view test_table exported as a table', $result); self::assertStringContainsString('DROP TABLE IF EXISTS `test_table`;', $result); self::assertStringContainsString('CREATE TABLE`test_table`', $result); // case 5 ob_start(); self::assertTrue($this->object->exportStructure( 'test_db', 'test_table', "\n", 'localhost', 'stand_in', 'test' )); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString('-- Stand-in structure for view test_table', $result); self::assertStringContainsString('CREATE TABLE `test_table`', $result); } /** * @group medium */ public function testExportData(): void { $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $flags = []; $a = new stdClass(); $a->name = 'name'; $a->length = 2; $flags[] = new FieldMetadata(MYSQLI_TYPE_LONG, 0, $a); $a = new stdClass(); $a->name = 'name'; $a->length = 2; $flags[] = new FieldMetadata(-1, MYSQLI_NUM_FLAG, $a); $a = new stdClass(); $a->name = 'name'; $a->length = 2; $a->charsetnr = 63; $flags[] = new FieldMetadata(MYSQLI_TYPE_STRING, 0, $a); $a = new stdClass(); $a->name = 'name'; $a->length = 2; $a->charsetnr = 63; $flags[] = new FieldMetadata(MYSQLI_TYPE_STRING, 0, $a); $a = new stdClass(); $a->name = 'name'; $a->length = 2; $a->charsetnr = 63; $flags[] = new FieldMetadata(MYSQLI_TYPE_BLOB, 0, $a); $resultStub = $this->createMock(DummyResult::class); $dbi->expects($this->once()) ->method('getFieldsMeta') ->with($resultStub) ->will($this->returnValue($flags)); $dbi->expects($this->once()) ->method('tryQuery') ->with('SELECT a FROM b WHERE 1', DatabaseInterface::CONNECT_USER, DatabaseInterface::QUERY_UNBUFFERED) ->will($this->returnValue($resultStub)); $resultStub->expects($this->once()) ->method('numFields') ->will($this->returnValue(5)); $resultStub->expects($this->exactly(2)) ->method('fetchRow') ->willReturnOnConsecutiveCalls( [ null, 'test', '10', '6', "\x00\x0a\x0d\x1a", ], [] ); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $_table = $this->getMockBuilder(Table::class) ->disableOriginalConstructor() ->getMock(); $_table->expects($this->once()) ->method('isMerge') ->will($this->returnValue(false)); $_table->expects($this->once()) ->method('isView') ->will($this->returnValue(false)); $dbi->expects($this->any()) ->method('getTable') ->will($this->returnValue($_table)); $GLOBALS['dbi'] = $dbi; $GLOBALS['sql_compatibility'] = 'MSSQL'; $GLOBALS['sql_backquotes'] = true; $GLOBALS['sql_max_query_size'] = 50000; $GLOBALS['sql_views_as_tables'] = true; $GLOBALS['sql_type'] = 'INSERT'; $GLOBALS['sql_delayed'] = ' DELAYED'; $GLOBALS['sql_ignore'] = true; $GLOBALS['sql_truncate'] = true; $GLOBALS['sql_insert_syntax'] = 'both'; $GLOBALS['sql_hex_for_binary'] = true; $GLOBALS['cfg']['Server']['DisableIS'] = false; ob_start(); $this->object->exportData('db', 'table', "\n", 'example.com/err', 'SELECT a FROM b WHERE 1'); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString('TRUNCATE TABLE "table";', $result); self::assertStringContainsString('SET IDENTITY_INSERT "table" ON ;', $result); self::assertStringContainsString('INSERT DELAYED IGNORE INTO "table" ("name", ' . '"name", "name", "name", ' . '"name") VALUES', $result); self::assertStringContainsString('(NULL, \'test\', 0x3130, 0x36, 0x000a0d1a);', $result); self::assertStringContainsString('SET IDENTITY_INSERT "table" OFF;', $result); } /** * @group medium */ public function testExportDataWithUpdate(): void { $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $flags = []; $a = new stdClass(); $a->name = 'name'; $a->orgname = 'pma'; $a->table = 'tbl'; $a->orgtable = 'tbl'; $a->length = 2; $flags[] = new FieldMetadata(MYSQLI_TYPE_FLOAT, MYSQLI_PRI_KEY_FLAG, $a); $a = new stdClass(); $a->name = 'name'; $a->orgname = 'pma'; $a->table = 'tbl'; $a->orgtable = 'tbl'; $a->length = 2; $flags[] = new FieldMetadata(MYSQLI_TYPE_FLOAT, MYSQLI_UNIQUE_KEY_FLAG, $a); $resultStub = $this->createMock(DummyResult::class); $dbi->expects($this->once()) ->method('getFieldsMeta') ->with($resultStub) ->will($this->returnValue($flags)); $dbi->expects($this->once()) ->method('tryQuery') ->with('SELECT a FROM b WHERE 1', DatabaseInterface::CONNECT_USER, DatabaseInterface::QUERY_UNBUFFERED) ->will($this->returnValue($resultStub)); $resultStub->expects($this->once()) ->method('numFields') ->will($this->returnValue(2)); $resultStub->expects($this->exactly(2)) ->method('fetchRow') ->willReturnOnConsecutiveCalls( [ null, null, ], [] ); $_table = $this->getMockBuilder(Table::class) ->disableOriginalConstructor() ->getMock(); $_table->expects($this->once()) ->method('isMerge') ->will($this->returnValue(false)); $_table->expects($this->once()) ->method('isView') ->will($this->returnValue(false)); $dbi->expects($this->any()) ->method('getTable') ->will($this->returnValue($_table)); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $GLOBALS['dbi'] = $dbi; $GLOBALS['sql_compatibility'] = 'MSSQL'; $GLOBALS['sql_backquotes'] = true; $GLOBALS['sql_views_as_tables'] = true; $GLOBALS['sql_type'] = 'UPDATE'; $GLOBALS['sql_delayed'] = ' DELAYED'; $GLOBALS['sql_ignore'] = true; $GLOBALS['sql_truncate'] = true; $GLOBALS['sql_insert_syntax'] = 'both'; $GLOBALS['sql_hex_for_binary'] = true; $GLOBALS['cfg']['Server']['DisableIS'] = false; ob_start(); $this->object->exportData('db', 'table', "\n", 'example.com/err', 'SELECT a FROM b WHERE 1'); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString('UPDATE IGNORE "table" SET "name" = NULL,' . '"name" = NULL WHERE CONCAT(`tbl`.`pma`) IS NULL;', $result); } public function testExportDataWithIsView(): void { $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $_table = $this->getMockBuilder(Table::class) ->disableOriginalConstructor() ->getMock(); $_table->expects($this->once()) ->method('isMerge') ->will($this->returnValue(false)); $_table->expects($this->once()) ->method('isView') ->will($this->returnValue(true)); $dbi->expects($this->any()) ->method('getTable') ->will($this->returnValue($_table)); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $GLOBALS['dbi'] = $dbi; $GLOBALS['cfg']['Server']['DisableIS'] = false; $GLOBALS['sql_views_as_tables'] = false; $GLOBALS['sql_include_comments'] = true; $GLOBALS['crlf'] = "\n"; $oldVal = $GLOBALS['sql_compatibility'] ?? ''; $GLOBALS['sql_compatibility'] = 'NONE'; $GLOBALS['sql_backquotes'] = true; ob_start(); self::assertTrue($this->object->exportData('db', 'tbl', "\n", 'err.com', 'SELECT')); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString("-- VIEW `tbl`\n", $result); self::assertStringContainsString("-- Data: None\n", $result); // reset $GLOBALS['sql_compatibility'] = $oldVal; } public function testExportDataWithError(): void { $dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); $dbi->expects($this->once()) ->method('getError') ->will($this->returnValue('err')); $_table = $this->getMockBuilder(Table::class) ->disableOriginalConstructor() ->getMock(); $_table->expects($this->once()) ->method('isMerge') ->will($this->returnValue(false)); $_table->expects($this->once()) ->method('isView') ->will($this->returnValue(false)); $dbi->expects($this->any()) ->method('getTable') ->will($this->returnValue($_table)); $dbi->expects($this->any())->method('escapeString') ->will($this->returnArgument(0)); $GLOBALS['dbi'] = $dbi; $GLOBALS['cfg']['Server']['DisableIS'] = false; $GLOBALS['sql_views_as_tables'] = true; $GLOBALS['sql_include_comments'] = true; $GLOBALS['crlf'] = "\n"; ob_start(); self::assertTrue($this->object->exportData('db', 'table', "\n", 'err.com', 'SELECT')); $result = ob_get_clean(); self::assertIsString($result); self::assertStringContainsString('-- Error reading data for table db.table: err', $result); } public function testMakeCreateTableMSSQLCompatible(): void { $query = "CREATE TABLE IF NOT EXISTS (\" date DEFAULT NULL,\n" . "\" date DEFAULT NULL\n\" date NOT NULL,\n\" date NOT NULL\n," . " \" date NOT NULL DEFAULT 'asd'," . " ) unsigned NOT NULL\n, ) unsigned NOT NULL,\n" . " ) unsigned DEFAULT NULL\n, ) unsigned DEFAULT NULL,\n" . " ) unsigned NOT NULL DEFAULT 'dsa',\n" . " \" int(10) DEFAULT NULL,\n" . " \" tinyint(0) DEFAULT NULL\n" . " \" smallint(10) NOT NULL,\n" . " \" bigint(0) NOT NULL\n" . " \" bigint(0) NOT NULL DEFAULT '12'\n" . " \" float(22,2,) DEFAULT NULL,\n" . " \" double DEFAULT NULL\n" . " \" float(22,2,) NOT NULL,\n" . " \" double NOT NULL\n" . " \" double NOT NULL DEFAULT '213'\n"; $method = new ReflectionMethod(ExportSql::class, 'makeCreateTableMSSQLCompatible'); $method->setAccessible(true); $result = $method->invoke($this->object, $query); self::assertSame("CREATE TABLE (\" datetime DEFAULT NULL,\n" . "\" datetime DEFAULT NULL\n" . "\" datetime NOT NULL,\n" . "\" datetime NOT NULL\n" . ", \" datetime NOT NULL DEFAULT 'asd', ) NOT NULL\n" . ", ) NOT NULL,\n" . " ) DEFAULT NULL\n" . ", ) DEFAULT NULL,\n" . " ) NOT NULL DEFAULT 'dsa',\n" . " \" int DEFAULT NULL,\n" . " \" tinyint DEFAULT NULL\n" . " \" smallint NOT NULL,\n" . " \" bigint NOT NULL\n" . " \" bigint NOT NULL DEFAULT '12'\n" . " \" float DEFAULT NULL,\n" . " \" float DEFAULT NULL\n" . " \" float NOT NULL,\n" . " \" float NOT NULL\n" . " \" float NOT NULL DEFAULT '213'\n", $result); } public function testInitAlias(): void { $aliases = [ 'a' => [ 'alias' => 'aliastest', 'tables' => [ 'foo' => ['alias' => 'qwerty'], 'bar' => ['alias' => 'f'], ], ], ]; $db = 'a'; $table = null; $this->object->initAlias($aliases, $db, $table); self::assertSame('aliastest', $db); self::assertNull($table); $db = 'foo'; $table = 'qwerty'; $this->object->initAlias($aliases, $db, $table); self::assertSame('foo', $db); self::assertSame('qwerty', $table); $db = 'a'; $table = 'foo'; $this->object->initAlias($aliases, $db, $table); self::assertSame('aliastest', $db); self::assertSame('qwerty', $table); } public function testGetAlias(): void { $aliases = [ 'a' => [ 'alias' => 'aliastest', 'tables' => [ 'foo' => [ 'alias' => 'qwerty', 'columns' => [ 'baz' => 'p', 'pqr' => 'pphymdain', ], ], 'bar' => [ 'alias' => 'f', 'columns' => ['xy' => 'n'], ], ], ], ]; self::assertSame('f', $this->object->getAlias($aliases, 'bar')); self::assertSame('aliastest', $this->object->getAlias($aliases, 'a')); self::assertSame('pphymdain', $this->object->getAlias($aliases, 'pqr')); self::assertSame('', $this->object->getAlias($aliases, 'abc')); } public function testReplaceWithAlias(): void { $aliases = [ 'a' => [ 'alias' => 'aliastest', 'tables' => [ 'foo' => [ 'alias' => 'bartest', 'columns' => [ 'baz' => 'p', 'pqr' => 'pphymdain', ], ], 'bar' => [ 'alias' => 'f', 'columns' => ['xy' => 'n'], ], ], ], ]; $db = 'a'; $table = 'foo'; $sql_query = "CREATE TABLE IF NOT EXISTS foo (\n" . "baz tinyint(3) unsigned NOT NULL COMMENT 'Primary Key',\n" . 'xyz varchar(255) COLLATE latin1_general_ci NOT NULL ' . "COMMENT 'xyz',\n" . 'pqr varchar(10) COLLATE latin1_general_ci NOT NULL ' . "COMMENT 'pqr',\n" . 'CONSTRAINT fk_om_dept FOREIGN KEY (baz) ' . "REFERENCES dept_master (baz)\n" . ') ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=' . "latin1_general_ci COMMENT='List' AUTO_INCREMENT=5"; $result = $this->object->replaceWithAliases(null, $sql_query, $aliases, $db, $table); self::assertSame("CREATE TABLE IF NOT EXISTS `bartest` (\n" . " `p` tinyint(3) UNSIGNED NOT NULL COMMENT 'Primary Key',\n" . " `xyz` varchar(255) COLLATE latin1_general_ci NOT NULL COMMENT 'xyz',\n" . " `pphymdain` varchar(10) COLLATE latin1_general_ci NOT NULL COMMENT 'pqr',\n" . " CONSTRAINT `fk_om_dept` FOREIGN KEY (`p`) REFERENCES dept_master (`baz`)\n" . ") ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='List'", $result); $result = $this->object->replaceWithAliases(null, $sql_query, [], '', ''); self::assertSame("CREATE TABLE IF NOT EXISTS foo (\n" . " `baz` tinyint(3) UNSIGNED NOT NULL COMMENT 'Primary Key',\n" . " `xyz` varchar(255) COLLATE latin1_general_ci NOT NULL COMMENT 'xyz',\n" . " `pqr` varchar(10) COLLATE latin1_general_ci NOT NULL COMMENT 'pqr',\n" . " CONSTRAINT `fk_om_dept` FOREIGN KEY (`baz`) REFERENCES dept_master (`baz`)\n" . ") ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='List'", $result); $table = 'bar'; $sql_query = 'CREATE TRIGGER `BEFORE_bar_INSERT` ' . 'BEFORE INSERT ON `bar` ' . 'FOR EACH ROW BEGIN ' . 'SET @cnt=(SELECT count(*) FROM bar WHERE ' . 'xy=NEW.xy AND id=NEW.id AND ' . 'abc=NEW.xy LIMIT 1); ' . 'IF @cnt<>0 THEN ' . 'SET NEW.xy=1; ' . 'END IF; END'; $result = $this->object->replaceWithAliases('$$', $sql_query, $aliases, $db, $table); self::assertSame('CREATE TRIGGER `BEFORE_bar_INSERT` BEFORE INSERT ON `f` FOR EACH ROW BEGIN ' . 'SET @cnt=(SELECT count(*) FROM `f` WHERE `n`=NEW.`n` AND id=NEW.id AND abc=NEW.`n` LIMIT 1); ' . 'IF @cnt<>0 THEN ' . 'SET NEW.`n`=1; ' . 'END IF; ' . 'END', $result); $table = 'bar'; $sql_query = <<<'SQL' CREATE FUNCTION `HTML_UnEncode`(`x` TEXT CHARSET utf8) RETURNS text CHARSET utf8 BEGIN DECLARE TextString TEXT ; SET TextString = x ; #quotation mark IF INSTR( x , '"' ) THEN SET TextString = REPLACE(TextString, '"','"') ; END IF ; #apostrophe IF INSTR( x , ''' ) THEN SET TextString = REPLACE(TextString, ''','"') ; END IF ; RETURN TextString ; END SQL; $result = $this->object->replaceWithAliases('$$', $sql_query, $aliases, $db, $table); $expectedQuery = <<<'SQL' CREATE FUNCTION `HTML_UnEncode` (`x` TEXT CHARSET utf8) RETURNS TEXT CHARSET utf8 BEGIN DECLARE TextString TEXT ; SET TextString = x ; #quotation mark IF INSTR( x , '"' ) THEN SET TextString = REPLACE(TextString, '"','"') ; END IF ; #apostrophe IF INSTR( x , ''' ) THEN SET TextString = REPLACE(TextString, ''','"') ; END IF ; RETURN TextString ; END SQL; self::assertSame($expectedQuery, $result); } }