diff --git a/tests/WP_SQLite_Driver_Tests.php b/tests/WP_SQLite_Driver_Tests.php index 65c3611a..651ba735 100644 --- a/tests/WP_SQLite_Driver_Tests.php +++ b/tests/WP_SQLite_Driver_Tests.php @@ -2,6 +2,7 @@ require_once __DIR__ . '/WP_SQLite_Translator_Tests.php'; require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-driver.php'; +require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php'; require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-expression.php'; require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-token-factory.php'; require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-token.php'; @@ -39,7 +40,7 @@ public static function setUpBeforeClass(): void { public function setUp(): void { $this->sqlite = new PDO( 'sqlite::memory:' ); - $this->engine = new WP_SQLite_Driver( $this->sqlite ); + $this->engine = new WP_SQLite_Driver( 'wp', $this->sqlite ); $this->engine->query( "CREATE TABLE _options ( ID INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, @@ -287,13 +288,13 @@ public function testShowCreateTable1() { # TODO: Should we fix mismatch with original `option_value` text NOT NULL,` without default? $this->assertEquals( "CREATE TABLE `_tmp_table` ( - `ID` bigint NOT NULL AUTO_INCREMENT, - `option_name` varchar(255) DEFAULT '', - `option_value` text NOT NULL DEFAULT '', - PRIMARY KEY (`ID`), - KEY `composite` (`option_name`, `option_value`), - UNIQUE KEY `option_name` (`option_name`) -);", + `ID` bigint NOT NULL AUTO_INCREMENT, + `option_name` varchar(255) DEFAULT '', + `option_value` text NOT NULL, + PRIMARY KEY (`ID`), + UNIQUE KEY `option_name` (`option_name`), + KEY `composite` (`option_name`, `option_value`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", $results[0]->{'Create Table'} ); } @@ -316,13 +317,13 @@ public function testShowCreateTableQuoted() { # TODO: Should we fix mismatch with original `option_value` text NOT NULL,` without default? $this->assertEquals( "CREATE TABLE `_tmp_table` ( - `ID` bigint NOT NULL AUTO_INCREMENT, - `option_name` varchar(255) DEFAULT '', - `option_value` text NOT NULL DEFAULT '', - PRIMARY KEY (`ID`), - KEY `composite` (`option_name`, `option_value`), - UNIQUE KEY `option_name` (`option_name`) -);", + `ID` bigint NOT NULL AUTO_INCREMENT, + `option_name` varchar(255) DEFAULT '', + `option_value` text NOT NULL, + PRIMARY KEY (`ID`), + UNIQUE KEY `option_name` (`option_name`), + KEY `composite` (`option_name`, `option_value`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", $results[0]->{'Create Table'} ); } @@ -340,8 +341,8 @@ public function testShowCreateTableSimpleTable() { $results = $this->engine->get_query_results(); $this->assertEquals( 'CREATE TABLE `_tmp_table` ( - `ID` bigint NOT NULL DEFAULT 0 -);', + `ID` bigint NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci', $results[0]->{'Create Table'} ); } @@ -369,12 +370,12 @@ public function testShowCreateTableWithAlterAndCreateIndex() { $results = $this->engine->get_query_results(); $this->assertEquals( 'CREATE TABLE `_tmp_table` ( - `ID` bigint NOT NULL AUTO_INCREMENT, - `option_name` smallint NOT NULL DEFAULT 14, - `option_value` text NOT NULL DEFAULT \'\', - PRIMARY KEY (`ID`), - KEY `option_name` (`option_name`) -);', + `ID` bigint NOT NULL AUTO_INCREMENT, + `option_name` smallint NOT NULL DEFAULT \'14\', + `option_value` text NOT NULL, + PRIMARY KEY (`ID`), + KEY `option_name` (`option_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci', $results[0]->{'Create Table'} ); } @@ -418,13 +419,13 @@ public function testShowCreateTablePreservesDoubleUnderscoreKeyNames() { $results = $this->engine->get_query_results(); $this->assertEquals( 'CREATE TABLE `_tmp__table` ( - `ID` bigint NOT NULL AUTO_INCREMENT, - `option_name` varchar(255) DEFAULT \'\', - `option_value` text NOT NULL DEFAULT \'\', - PRIMARY KEY (`ID`), - KEY `double__underscores` (`option_name`, `ID`), - KEY `option_name` (`option_name`) -);', + `ID` bigint NOT NULL AUTO_INCREMENT, + `option_name` varchar(255) DEFAULT \'\', + `option_value` text NOT NULL, + PRIMARY KEY (`ID`), + KEY `option_name` (`option_name`), + KEY `double__underscores` (`option_name`, `ID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci', $results[0]->{'Create Table'} ); } @@ -445,11 +446,11 @@ public function testShowCreateTableWithPrimaryKeyColumnsReverseOrdered() { $results = $this->engine->get_query_results(); $this->assertEquals( 'CREATE TABLE `_tmp_table` ( - `ID_A` bigint NOT NULL DEFAULT 0, - `ID_B` bigint NOT NULL DEFAULT 0, - `ID_C` bigint NOT NULL DEFAULT 0, - PRIMARY KEY (`ID_B`, `ID_A`, `ID_C`) -);', + `ID_A` bigint NOT NULL, + `ID_B` bigint NOT NULL, + `ID_C` bigint NOT NULL, + PRIMARY KEY (`ID_B`, `ID_A`, `ID_C`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci', $results[0]->{'Create Table'} ); } @@ -480,11 +481,11 @@ public function testShowCreateTableWithCorrectDefaultValues() { $results = $this->engine->get_query_results(); $this->assertEquals( 'CREATE TABLE `_tmp__table` ( - `ID` bigint NOT NULL AUTO_INCREMENT, - `default_empty_string` varchar(255) DEFAULT \'\', - `null_no_default` varchar(255), - PRIMARY KEY (`ID`) -);', + `ID` bigint NOT NULL AUTO_INCREMENT, + `default_empty_string` varchar(255) DEFAULT \'\', + `null_no_default` varchar(255), + PRIMARY KEY (`ID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci', $results[0]->{'Create Table'} ); } @@ -847,8 +848,8 @@ enum_column ENUM('a', 'b', 'c') NOT NULL DEFAULT 'a', 'Type' => 'bigint(20) unsigned', 'Null' => 'NO', 'Key' => 'PRI', - 'Default' => '0', - 'Extra' => '', + 'Default' => null, + 'Extra' => 'auto_increment', ), (object) array( 'Field' => 'decimal_column', @@ -1051,7 +1052,7 @@ public function testColumnWithOnUpdate() { 'Type' => 'int(11)', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1060,7 +1061,7 @@ public function testColumnWithOnUpdate() { 'Null' => 'YES', 'Key' => '', 'Default' => null, - 'Extra' => '', + 'Extra' => 'on update CURRENT_TIMESTAMP', ), ), $results @@ -1078,7 +1079,7 @@ public function testColumnWithOnUpdate() { 'Type' => 'int(11)', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1087,7 +1088,7 @@ public function testColumnWithOnUpdate() { 'Null' => 'YES', 'Key' => '', 'Default' => null, - 'Extra' => '', + 'Extra' => 'on update CURRENT_TIMESTAMP', ), (object) array( 'Field' => 'updated_at', @@ -1095,7 +1096,7 @@ public function testColumnWithOnUpdate() { 'Null' => 'YES', 'Key' => '', 'Default' => null, - 'Extra' => '', + 'Extra' => 'on update CURRENT_TIMESTAMP', ), ), $results @@ -1220,7 +1221,7 @@ public function testChangeColumnWithOnUpdate() { 'Type' => 'int(11)', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1253,7 +1254,7 @@ public function testChangeColumnWithOnUpdate() { 'Type' => 'int(11)', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1262,7 +1263,7 @@ public function testChangeColumnWithOnUpdate() { 'Null' => 'YES', 'Key' => '', 'Default' => null, - 'Extra' => '', + 'Extra' => 'on update CURRENT_TIMESTAMP', ), ), $results @@ -1285,7 +1286,7 @@ public function testChangeColumnWithOnUpdate() { 'Type' => 'int(11)', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1327,7 +1328,7 @@ public function testAlterTableWithColumnFirstAndAfter() { 'Type' => 'int(11)', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1335,7 +1336,7 @@ public function testAlterTableWithColumnFirstAndAfter() { 'Type' => 'varchar(20)', 'Null' => 'NO', 'Key' => '', - 'Default' => null, + 'Default' => '', 'Extra' => '', ), (object) array( @@ -1362,7 +1363,7 @@ public function testAlterTableWithColumnFirstAndAfter() { 'Type' => 'int(11)', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1456,7 +1457,7 @@ public function testAlterTableWithColumnFirstAndAfter() { 'Type' => 'varchar(20)', 'Null' => 'NO', 'Key' => '', - 'Default' => null, + 'Default' => '', 'Extra' => '', ), (object) array( @@ -1502,7 +1503,7 @@ public function testAlterTableWithMultiColumnFirstAndAfter() { 'Type' => 'int(11)', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1548,7 +1549,7 @@ public function testAlterTableWithMultiColumnFirstAndAfter() { 'Type' => 'int(11)', 'Null' => 'NO', 'Key' => '', - 'Default' => '0', + 'Default' => null, 'Extra' => '', ), (object) array( @@ -1599,7 +1600,7 @@ public function testAlterTableAddIndex() { 'Table' => '_tmp_table', 'Non_unique' => '1', 'Key_name' => 'name', - 'Seq_in_index' => '0', + 'Seq_in_index' => '1', 'Column_name' => 'name', 'Collation' => 'A', 'Cardinality' => '0', @@ -1609,6 +1610,8 @@ public function testAlterTableAddIndex() { 'Index_type' => 'BTREE', 'Comment' => '', 'Index_comment' => '', + 'Visible' => 'YES', + 'Expression' => null, ), ), $results @@ -1634,16 +1637,18 @@ public function testAlterTableAddUniqueIndex() { 'Table' => '_tmp_table', 'Non_unique' => '0', 'Key_name' => 'name', - 'Seq_in_index' => '0', + 'Seq_in_index' => '1', 'Column_name' => 'name', 'Collation' => 'A', 'Cardinality' => '0', - 'Sub_part' => null, + 'Sub_part' => '20', 'Packed' => null, 'Null' => '', 'Index_type' => 'BTREE', 'Comment' => '', 'Index_comment' => '', + 'Visible' => 'YES', + 'Expression' => null, ), ), $results @@ -1669,9 +1674,9 @@ public function testAlterTableAddFulltextIndex() { 'Table' => '_tmp_table', 'Non_unique' => '1', 'Key_name' => 'name', - 'Seq_in_index' => '0', + 'Seq_in_index' => '1', 'Column_name' => 'name', - 'Collation' => 'A', + 'Collation' => null, 'Cardinality' => '0', 'Sub_part' => null, 'Packed' => null, @@ -1679,6 +1684,8 @@ public function testAlterTableAddFulltextIndex() { 'Index_type' => 'FULLTEXT', 'Comment' => '', 'Index_comment' => '', + 'Visible' => 'YES', + 'Expression' => null, ), ), $results @@ -2166,15 +2173,14 @@ public function testNestedTransactionWorkComplexModify() { $fields = $this->engine->get_query_results(); $this->assertEquals( - $fields, array( (object) array( 'Field' => 'ID', - 'Type' => 'integer', + 'Type' => 'int', 'Null' => 'NO', 'Key' => 'PRI', - 'Default' => '0', - 'Extra' => '', + 'Default' => null, + 'Extra' => 'auto_increment', ), (object) array( 'Field' => 'option_name', @@ -2192,7 +2198,8 @@ public function testNestedTransactionWorkComplexModify() { 'Default' => '', 'Extra' => '', ), - ) + ), + $fields ); } @@ -2425,7 +2432,7 @@ public function testDescribeAccurate() { 'Field' => 'term_name', 'Type' => 'varchar(11)', 'Null' => 'NO', - 'Key' => '', + 'Key' => 'MUL', 'Default' => '0', 'Extra' => '', ), diff --git a/tests/WP_SQLite_Driver_Translation_Tests.php b/tests/WP_SQLite_Driver_Translation_Tests.php index 1da6334b..ac3bf376 100644 --- a/tests/WP_SQLite_Driver_Translation_Tests.php +++ b/tests/WP_SQLite_Driver_Translation_Tests.php @@ -1,6 +1,7 @@ driver = new WP_SQLite_Driver( new PDO( 'sqlite::memory:' ) ); + $this->driver = new WP_SQLite_Driver( 'wp', new PDO( 'sqlite::memory:' ) ); } public function testSelect(): void { @@ -206,22 +207,104 @@ public function testCreateTable(): void { 'CREATE TABLE t (id INT)' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'id', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testCreateTableWithMultipleColumns(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "id" INTEGER , "name" TEXT , "score" REAL DEFAULT 0.0 )', + 'CREATE TABLE "t" ( "id" INTEGER, "name" TEXT, "score" REAL DEFAULT \'0.0\' )', 'CREATE TABLE t (id INT, name TEXT, score FLOAT DEFAULT 0.0)' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'id', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'name', 2, null, 'YES', 'text', 65535, 65535, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'text', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'score', 3, '0.0', 'YES', 'float', null, null, 12, null, null, null, null, 'float', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testCreateTableWithBasicConstraints(): void { $this->assertQuery( 'CREATE TABLE "t" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT )', 'CREATE TABLE t (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT)' ); - // ENGINE is not supported in SQLite. + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'id', 1, null, 'NO', 'int', null, null, 10, 0, null, null, null, 'int', 'PRI', 'auto_increment', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_statistics (table_schema, table_name, non_unique, index_schema, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment, index_comment, is_visible, expression)' + . " VALUES ('wp', 't', 0, 'wp', 'PRIMARY', 1, 'id', 'A', 0, null, null, '', 'BTREE', '', '', 'YES', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testCreateTableWithEngine(): void { + // ENGINE is not supported in SQLite, we save it in information schema. + $this->assertQuery( + 'CREATE TABLE "t" ( "id" INTEGER )', + 'CREATE TABLE t (id INT) ENGINE=MyISAM' + ); + + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'MyISAM', 'FIXED', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'id', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testCreateTableWithCollate(): void { + // COLLATE is not supported in SQLite, we save it in information schema. $this->assertQuery( 'CREATE TABLE "t" ( "id" INTEGER )', - 'CREATE TABLE t (id INT) ENGINE=InnoDB' + 'CREATE TABLE t (id INT) COLLATE utf8mb4_czech_ci' + ); + + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_czech_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'id', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) ); + } + public function testCreateTableWithPrimaryKey(): void { /* * PRIMARY KEY without AUTOINCREMENT: * In this case, integer must be represented as INT, not INTEGER. SQLite @@ -232,34 +315,151 @@ public function testCreateTable(): void { * https://www.sqlite.org/lang_createtable.html#rowids_and_the_integer_primary_key */ $this->assertQuery( - 'CREATE TABLE "t" ( "id" INT PRIMARY KEY )', + 'CREATE TABLE "t" ( "id" INT NOT NULL, PRIMARY KEY ("id") )', 'CREATE TABLE t (id INT PRIMARY KEY)' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'id', 1, null, 'NO', 'int', null, null, 10, 0, null, null, null, 'int', 'PRI', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_statistics (table_schema, table_name, non_unique, index_schema, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment, index_comment, is_visible, expression)' + . " VALUES ('wp', 't', 0, 'wp', 'PRIMARY', 1, 'id', 'A', 0, null, null, '', 'BTREE', '', '', 'YES', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { // With AUTOINCREMENT, we expect "INTEGER". $this->assertQuery( - 'CREATE TABLE "t" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT )', - 'CREATE TABLE t (id INT PRIMARY KEY AUTO_INCREMENT)' + 'CREATE TABLE "t1" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT )', + 'CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT)' + ); + + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't1', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't1', 'id', 1, null, 'NO', 'int', null, null, 10, 0, null, null, null, 'int', 'PRI', 'auto_increment', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_statistics (table_schema, table_name, non_unique, index_schema, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment, index_comment, is_visible, expression)' + . " VALUES ('wp', 't1', 0, 'wp', 'PRIMARY', 1, 'id', 'A', 0, null, null, '', 'BTREE', '', '', 'YES', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't1'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't1'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't1'", + ) ); // In SQLite, PRIMARY KEY must come before AUTOINCREMENT. $this->assertQuery( - 'CREATE TABLE "t" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT )', - 'CREATE TABLE t (id INT AUTO_INCREMENT PRIMARY KEY)' + 'CREATE TABLE "t2" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT )', + 'CREATE TABLE t2 (id INT AUTO_INCREMENT PRIMARY KEY)' + ); + + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't2', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't2', 'id', 1, null, 'NO', 'int', null, null, 10, 0, null, null, null, 'int', 'PRI', 'auto_increment', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_statistics (table_schema, table_name, non_unique, index_schema, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment, index_comment, is_visible, expression)' + . " VALUES ('wp', 't2', 0, 'wp', 'PRIMARY', 1, 'id', 'A', 0, null, null, '', 'BTREE', '', '', 'YES', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't2'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't2'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't2'", + ) ); // In SQLite, AUTOINCREMENT cannot be specified separately from PRIMARY KEY. $this->assertQuery( - 'CREATE TABLE "t" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT )', - 'CREATE TABLE t (id INT AUTO_INCREMENT, PRIMARY KEY(id))' + 'CREATE TABLE "t3" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT )', + 'CREATE TABLE t3 (id INT AUTO_INCREMENT, PRIMARY KEY(id))' + ); + + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't3', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't3', 'id', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', 'auto_increment', 'select,insert,update,references', '', '', null)", + "SELECT column_name, data_type, is_nullable, character_maximum_length FROM _mysql_information_schema_columns WHERE table_name = 't3' AND column_name IN ('id')", + 'INSERT INTO _mysql_information_schema_statistics (table_schema, table_name, non_unique, index_schema, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment, index_comment, is_visible, expression)' + . " VALUES ('wp', 't3', 0, 'wp', 'PRIMARY', 1, 'id', 'A', 0, null, null, '', 'BTREE', '', '', 'YES', null)", + "UPDATE _mysql_information_schema_columns AS c SET column_key = CASE WHEN s.index_name = 'PRIMARY' THEN 'PRI' WHEN s.non_unique = 0 AND s.seq_in_index = 1 THEN 'UNI' WHEN s.seq_in_index = 1 THEN 'MUL' ELSE '' END, is_nullable = CASE WHEN s.nullable = 'YES' THEN 'YES' ELSE 'NO' END FROM _mysql_information_schema_statistics AS s WHERE c.table_name = 't3' AND c.table_name = s.table_name AND c.column_name = s.column_name", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't3'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't3'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't3'", + ) ); + } - // IF NOT EXISTS. + // @TODO: IF NOT EXISTS + /*public function testCreateTableWithIfNotExists(): void { $this->assertQuery( 'CREATE TABLE IF NOT EXISTS "t" ( "id" INTEGER )', 'CREATE TABLE IF NOT EXISTS t (id INT)' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'id', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", + ) + ); + }*/ + + public function testCreateTableWithInlineUniqueIndexes(): void { + $this->assertQuery( + array( + 'CREATE TABLE "t" ( "id" INTEGER, "name" TEXT )', + 'CREATE UNIQUE INDEX "id" ON "t" ("id")', + 'CREATE UNIQUE INDEX "name" ON "t" ("name")', + ), + 'CREATE TABLE t (id INT UNIQUE, name TEXT UNIQUE)' + ); + } + + public function testCreateTableWithStandaloneUniqueIndexes(): void { + $this->assertQuery( + array( + 'CREATE TABLE "t" ( "id" INTEGER, "name" TEXT )', + 'CREATE UNIQUE INDEX "id" ON "t" ("id")', + 'CREATE UNIQUE INDEX "name" ON "t" ("name")', + ), + 'CREATE TABLE t (id INT, name VARCHAR(100), UNIQUE (id), UNIQUE (name))' + ); + + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'id', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'name', 2, null, 'YES', 'varchar', 100, 400, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'varchar(100)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT column_name, data_type, is_nullable, character_maximum_length FROM _mysql_information_schema_columns WHERE table_name = 't' AND column_name IN ('id')", + 'INSERT INTO _mysql_information_schema_statistics (table_schema, table_name, non_unique, index_schema, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment, index_comment, is_visible, expression)' + . " VALUES ('wp', 't', 0, 'wp', 'id', 1, 'id', 'A', 0, null, null, 'YES', 'BTREE', '', '', 'YES', null)", + "UPDATE _mysql_information_schema_columns AS c SET column_key = CASE WHEN s.index_name = 'PRIMARY' THEN 'PRI' WHEN s.non_unique = 0 AND s.seq_in_index = 1 THEN 'UNI' WHEN s.seq_in_index = 1 THEN 'MUL' ELSE '' END, is_nullable = CASE WHEN s.nullable = 'YES' THEN 'YES' ELSE 'NO' END FROM _mysql_information_schema_statistics AS s WHERE c.table_name = 't' AND c.table_name = s.table_name AND c.column_name = s.column_name", + "SELECT column_name, data_type, is_nullable, character_maximum_length FROM _mysql_information_schema_columns WHERE table_name = 't' AND column_name IN ('name')", + 'INSERT INTO _mysql_information_schema_statistics (table_schema, table_name, non_unique, index_schema, index_name, seq_in_index, column_name, collation, cardinality, sub_part, packed, nullable, index_type, comment, index_comment, is_visible, expression)' + . " VALUES ('wp', 't', 0, 'wp', 'name', 1, 'name', 'A', 0, null, null, 'YES', 'BTREE', '', '', 'YES', null)", + "UPDATE _mysql_information_schema_columns AS c SET column_key = CASE WHEN s.index_name = 'PRIMARY' THEN 'PRI' WHEN s.non_unique = 0 AND s.seq_in_index = 1 THEN 'UNI' WHEN s.seq_in_index = 1 THEN 'MUL' ELSE '' END, is_nullable = CASE WHEN s.nullable = 'YES' THEN 'YES' ELSE 'NO' END FROM _mysql_information_schema_statistics AS s WHERE c.table_name = 't' AND c.table_name = s.table_name AND c.column_name = s.column_name", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testCreateTableFromSelectQuery(): void { // CREATE TABLE AS SELECT ... $this->assertQuery( 'CREATE TABLE "t1" AS SELECT * FROM "t2"', @@ -272,160 +472,530 @@ public function testCreateTable(): void { 'CREATE TABLE "t1" AS SELECT * FROM "t2"', 'CREATE TABLE t1 SELECT * FROM t2' ); + } - // TEMPORARY. + // @TODO: CREATE TEMPORARY TABLE + /*public yunction testCreateTemporaryTable(): void { $this->assertQuery( 'CREATE TEMPORARY TABLE "t" ( "id" INTEGER )', 'CREATE TEMPORARY TABLE t (id INT)' ); - // TEMPORARY & IF NOT EXISTS. + // With IF NOT EXISTS. $this->assertQuery( 'CREATE TEMPORARY TABLE IF NOT EXISTS "t" ( "id" INTEGER )', 'CREATE TEMPORARY TABLE IF NOT EXISTS t (id INT)' ); + }*/ + + public function testAlterTableAddColumn(): void { + $this->assertQuery( + 'ALTER TABLE "t" ADD COLUMN "a" INTEGER', + 'ALTER TABLE t ADD a INT' + ); + } + + public function testAlterTableAddColumnWithNotNull(): void { + $this->assertQuery( + 'ALTER TABLE "t" ADD COLUMN "a" INTEGER NOT NULL', + 'ALTER TABLE t ADD a INT NOT NULL' + ); } - public function testAlterTable(): void { - // Prepare a real table, so we can test multi-operation alter statements. - // Otherwise, we'd hit and exception and rollback after the first query. + public function testAlterTableAddColumnWithDefault(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT )', - 'CREATE TABLE t (id INT PRIMARY KEY AUTO_INCREMENT)' + 'ALTER TABLE "t" ADD COLUMN "a" INTEGER DEFAULT 0', + 'ALTER TABLE t ADD a INT DEFAULT 0' ); + } - // ADD COLUMN. + public function testAlterTableAddColumnWithNotNullAndDefault(): void { $this->assertQuery( - 'ALTER TABLE "t" ADD COLUMN "a" INTEGER', - 'ALTER TABLE t ADD a INT' + 'ALTER TABLE "t" ADD COLUMN "a" INTEGER NOT NULL DEFAULT 0', + 'ALTER TABLE t ADD a INT NOT NULL DEFAULT 0' ); + } - // ADD COLUMN with multiple columns. + public function testAlterTableAddMultipleColumns(): void { + $this->driver->get_pdo()->exec( 'CREATE TABLE t (id INT)' ); $this->assertQuery( array( - 'ALTER TABLE "t" ADD COLUMN "b" INTEGER', - 'ALTER TABLE "t" ADD COLUMN "c" TEXT', - 'ALTER TABLE "t" ADD COLUMN "d" INTEGER', + 'ALTER TABLE "t" ADD COLUMN "a" INTEGER', + 'ALTER TABLE "t" ADD COLUMN "b" TEXT', + 'ALTER TABLE "t" ADD COLUMN "c" INTEGER', ), - 'ALTER TABLE t ADD b INT, ADD c TEXT, ADD d BOOL' + 'ALTER TABLE t ADD a INT, ADD b TEXT, ADD c BOOL' ); + } - // DROP COLUMN. + public function testAlterTableDropColumn(): void { $this->assertQuery( 'ALTER TABLE "t" DROP COLUMN "a"', 'ALTER TABLE t DROP a' ); + } - // DROP COLUMN with multiple columns. + public function testAlterTableDropMultipleColumns(): void { + $this->driver->get_pdo()->exec( 'CREATE TABLE t (a INT, b INT)' ); $this->assertQuery( array( + 'ALTER TABLE "t" DROP COLUMN "a"', 'ALTER TABLE "t" DROP COLUMN "b"', - 'ALTER TABLE "t" DROP COLUMN "c"', ), - 'ALTER TABLE t DROP b, DROP c' + 'ALTER TABLE t DROP a, DROP b' ); + } - // ADD COLUMN and DROP COLUMN combined. + public function testAlterTableAddAndDropColumns(): void { + $this->driver->get_pdo()->exec( 'CREATE TABLE t (a INT)' ); $this->assertQuery( array( - 'ALTER TABLE "t" ADD COLUMN "a" INTEGER', - 'ALTER TABLE "t" DROP COLUMN "d"', + 'ALTER TABLE "t" ADD COLUMN "b" INTEGER', + 'ALTER TABLE "t" DROP COLUMN "a"', ), - 'ALTER TABLE t ADD a INT, DROP d' + 'ALTER TABLE t ADD b INT, DROP a' + ); + } + + public function testBitDataTypes(): void { + $this->assertQuery( + 'CREATE TABLE "t" ( "i1" INTEGER, "i2" INTEGER )', + 'CREATE TABLE t (i1 BIT, i2 BIT(10))' + ); + + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'i1', 1, null, 'YES', 'bit', null, null, 1, null, null, null, null, 'bit(1)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'i2', 2, null, 'YES', 'bit', null, null, 10, null, null, null, null, 'bit(10)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) ); } - public function testDataTypes(): void { - // Numeric data types. + public function testBooleanDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "i1" INTEGER , "i2" INTEGER , "i3" INTEGER )', - 'CREATE TABLE t (i1 BIT, i2 BOOL, i3 BOOLEAN)' + 'CREATE TABLE "t" ( "i1" INTEGER, "i2" INTEGER )', + 'CREATE TABLE t (i1 BOOL, i2 BOOLEAN)' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'i1', 1, null, 'YES', 'tinyint', null, null, 3, 0, null, null, null, 'tinyint(1)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'i2', 2, null, 'YES', 'tinyint', null, null, 3, 0, null, null, null, 'tinyint(1)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testIntegerDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "i1" INTEGER , "i2" INTEGER , "i3" INTEGER , "i4" INTEGER , "i5" INTEGER , "i6" INTEGER )', + 'CREATE TABLE "t" ( "i1" INTEGER, "i2" INTEGER, "i3" INTEGER, "i4" INTEGER, "i5" INTEGER, "i6" INTEGER )', 'CREATE TABLE t (i1 TINYINT, i2 SMALLINT, i3 MEDIUMINT, i4 INT, i5 INTEGER, i6 BIGINT)' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'i1', 1, null, 'YES', 'tinyint', null, null, 3, 0, null, null, null, 'tinyint', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'i2', 2, null, 'YES', 'smallint', null, null, 5, 0, null, null, null, 'smallint', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'i3', 3, null, 'YES', 'mediumint', null, null, 7, 0, null, null, null, 'mediumint', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'i4', 4, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'i5', 5, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'i6', 6, null, 'YES', 'bigint', null, null, 19, 0, null, null, null, 'bigint', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testFloatDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "f1" REAL , "f2" REAL , "f3" REAL , "f4" REAL )', + 'CREATE TABLE "t" ( "f1" REAL, "f2" REAL, "f3" REAL, "f4" REAL )', 'CREATE TABLE t (f1 FLOAT, f2 DOUBLE, f3 DOUBLE PRECISION, f4 REAL)' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'f1', 1, null, 'YES', 'float', null, null, 12, null, null, null, null, 'float', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'f2', 2, null, 'YES', 'double', null, null, 22, null, null, null, null, 'double', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'f3', 3, null, 'YES', 'double', null, null, 22, null, null, null, null, 'double', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'f4', 4, null, 'YES', 'double', null, null, 22, null, null, null, null, 'double', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testDecimalTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "f1" REAL , "f2" REAL , "f3" REAL , "f4" REAL )', + 'CREATE TABLE "t" ( "f1" REAL, "f2" REAL, "f3" REAL, "f4" REAL )', 'CREATE TABLE t (f1 DECIMAL, f2 DEC, f3 FIXED, f4 NUMERIC)' ); - // String data types. + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'f1', 1, null, 'YES', 'decimal', null, null, 10, 0, null, null, null, 'decimal(10,0)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'f2', 2, null, 'YES', 'decimal', null, null, 10, 0, null, null, null, 'decimal(10,0)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'f3', 3, null, 'YES', 'decimal', null, null, 10, 0, null, null, null, 'decimal(10,0)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'f4', 4, null, 'YES', 'decimal', null, null, 10, 0, null, null, null, 'decimal(10,0)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testCharDataTypes(): void { + $this->assertQuery( + 'CREATE TABLE "t" ( "c1" TEXT, "c2" TEXT )', + 'CREATE TABLE t (c1 CHAR, c2 CHAR(10))' + ); + + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c1', 1, null, 'YES', 'char', 1, 4, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'char(1)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c2', 2, null, 'YES', 'char', 10, 40, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'char(10)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testVarcharDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "c1" TEXT , "c2" TEXT , "c3" TEXT , "c4" TEXT )', - 'CREATE TABLE t (c1 CHAR, c2 VARCHAR(255), c3 CHAR VARYING(255), c4 CHARACTER VARYING(255))' + 'CREATE TABLE "t" ( "c1" TEXT, "c2" TEXT, "c3" TEXT )', + 'CREATE TABLE t (c1 VARCHAR(255), c2 CHAR VARYING(255), c3 CHARACTER VARYING(255))' + ); + + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c1', 1, null, 'YES', 'varchar', 255, 1020, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'varchar(255)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c2', 2, null, 'YES', 'varchar', 255, 1020, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'varchar(255)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c3', 3, null, 'YES', 'varchar', 255, 1020, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'varchar(255)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) ); + } + public function testNationalCharDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "c1" TEXT , "c2" TEXT )', - 'CREATE TABLE t (c1 NATIONAL CHAR, c2 NCHAR)' + 'CREATE TABLE "t" ( "c1" TEXT, "c2" TEXT, "c3" TEXT, "c4" TEXT )', + 'CREATE TABLE t (c1 NATIONAL CHAR, c2 NCHAR, c3 NATIONAL CHAR (10), c4 NCHAR(10))' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c1', 1, null, 'YES', 'char', 1, 3, null, null, null, 'utf8', 'utf8_general_ci', 'char(1)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c2', 2, null, 'YES', 'char', 1, 3, null, null, null, 'utf8', 'utf8_general_ci', 'char(1)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c3', 3, null, 'YES', 'char', 10, 30, null, null, null, 'utf8', 'utf8_general_ci', 'char(10)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c4', 4, null, 'YES', 'char', 10, 30, null, null, null, 'utf8', 'utf8_general_ci', 'char(10)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testNcharVarcharDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "c1" TEXT , "c2" TEXT , "c3" TEXT )', + 'CREATE TABLE "t" ( "c1" TEXT, "c2" TEXT, "c3" TEXT )', 'CREATE TABLE t (c1 NCHAR VARCHAR(255), c2 NCHAR VARYING(255), c3 NVARCHAR(255))' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c1', 1, null, 'YES', 'varchar', 255, 765, null, null, null, 'utf8', 'utf8_general_ci', 'varchar(255)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c2', 2, null, 'YES', 'varchar', 255, 765, null, null, null, 'utf8', 'utf8_general_ci', 'varchar(255)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c3', 3, null, 'YES', 'varchar', 255, 765, null, null, null, 'utf8', 'utf8_general_ci', 'varchar(255)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testNationalVarcharDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "c1" TEXT , "c2" TEXT , "c3" TEXT )', + 'CREATE TABLE "t" ( "c1" TEXT, "c2" TEXT, "c3" TEXT )', 'CREATE TABLE t (c1 NATIONAL VARCHAR(255), c2 NATIONAL CHAR VARYING(255), c3 NATIONAL CHARACTER VARYING(255))' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c1', 1, null, 'YES', 'varchar', 255, 765, null, null, null, 'utf8', 'utf8_general_ci', 'varchar(255)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c2', 2, null, 'YES', 'varchar', 255, 765, null, null, null, 'utf8', 'utf8_general_ci', 'varchar(255)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'c3', 3, null, 'YES', 'varchar', 255, 765, null, null, null, 'utf8', 'utf8_general_ci', 'varchar(255)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testTextDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "t1" TEXT , "t2" TEXT , "t3" TEXT , "t4" TEXT )', + 'CREATE TABLE "t" ( "t1" TEXT, "t2" TEXT, "t3" TEXT, "t4" TEXT )', 'CREATE TABLE t (t1 TINYTEXT, t2 TEXT, t3 MEDIUMTEXT, t4 LONGTEXT)' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 't1', 1, null, 'YES', 'tinytext', 255, 255, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'tinytext', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 't2', 2, null, 'YES', 'text', 65535, 65535, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'text', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 't3', 3, null, 'YES', 'mediumtext', 16777215, 16777215, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'mediumtext', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 't4', 4, null, 'YES', 'longtext', 4294967295, 4294967295, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'longtext', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testEnumDataTypes(): void { $this->assertQuery( 'CREATE TABLE "t" ( "e" TEXT )', 'CREATE TABLE t (e ENUM("a", "b", "c"))' ); - // Date and time data types. + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'e', 1, null, 'YES', 'enum', 1, 4, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'enum(''a'',''b'',''c'')', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testDateAndTimeDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "d" TEXT , "t" TEXT , "dt" TEXT , "ts" TEXT , "y" TEXT )', + 'CREATE TABLE "t" ( "d" TEXT, "t" TEXT, "dt" TEXT, "ts" TEXT, "y" TEXT )', 'CREATE TABLE t (d DATE, t TIME, dt DATETIME, ts TIMESTAMP, y YEAR)' ); - // Binary data types. + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'd', 1, null, 'YES', 'date', null, null, null, null, null, null, null, 'date', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 't', 2, null, 'YES', 'time', null, null, null, null, 0, null, null, 'time', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'dt', 3, null, 'YES', 'datetime', null, null, null, null, 0, null, null, 'datetime', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'ts', 4, null, 'YES', 'timestamp', null, null, null, null, 0, null, null, 'timestamp', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'y', 5, null, 'YES', 'year', null, null, null, null, null, null, null, 'year', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testBinaryDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "b" INTEGER , "v" BLOB )', + 'CREATE TABLE "t" ( "b" INTEGER, "v" BLOB )', 'CREATE TABLE t (b BINARY, v VARBINARY(255))' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'b', 1, null, 'YES', 'binary', 1, 1, null, null, null, null, null, 'binary(1)', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'v', 2, null, 'YES', 'varbinary', 255, 255, null, null, null, null, null, 'varbinary(255)', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testBlobDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "b1" BLOB , "b2" BLOB , "b3" BLOB , "b4" BLOB )', + 'CREATE TABLE "t" ( "b1" BLOB, "b2" BLOB, "b3" BLOB, "b4" BLOB )', 'CREATE TABLE t (b1 TINYBLOB, b2 BLOB, b3 MEDIUMBLOB, b4 LONGBLOB)' ); - // Spatial data types. + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'b1', 1, null, 'YES', 'tinyblob', 255, 255, null, null, null, null, null, 'tinyblob', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'b2', 2, null, 'YES', 'blob', 65535, 65535, null, null, null, null, null, 'blob', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'b3', 3, null, 'YES', 'mediumblob', 16777215, 16777215, null, null, null, null, null, 'mediumblob', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'b4', 4, null, 'YES', 'longblob', 4294967295, 4294967295, null, null, null, null, null, 'longblob', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testBasicSpatialDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "g1" TEXT , "g2" TEXT , "g3" TEXT , "g4" TEXT )', + 'CREATE TABLE "t" ( "g1" TEXT, "g2" TEXT, "g3" TEXT, "g4" TEXT )', 'CREATE TABLE t (g1 GEOMETRY, g2 POINT, g3 LINESTRING, g4 POLYGON)' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'g1', 1, null, 'YES', 'geometry', null, null, null, null, null, null, null, 'geometry', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'g2', 2, null, 'YES', 'point', null, null, null, null, null, null, null, 'point', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'g3', 3, null, 'YES', 'linestring', null, null, null, null, null, null, null, 'linestring', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'g4', 4, null, 'YES', 'polygon', null, null, null, null, null, null, null, 'polygon', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testMultiObjectSpatialDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "g1" TEXT , "g2" TEXT , "g3" TEXT )', + 'CREATE TABLE "t" ( "g1" TEXT, "g2" TEXT, "g3" TEXT )', 'CREATE TABLE t (g1 MULTIPOINT, g2 MULTILINESTRING, g3 MULTIPOLYGON)' ); + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'g1', 1, null, 'YES', 'multipoint', null, null, null, null, null, null, null, 'multipoint', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'g2', 2, null, 'YES', 'multilinestring', null, null, null, null, null, null, null, 'multilinestring', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'g3', 3, null, 'YES', 'multipolygon', null, null, null, null, null, null, null, 'multipolygon', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testGeometryCollectionDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "g1" TEXT , "g2" TEXT )', + 'CREATE TABLE "t" ( "g1" TEXT, "g2" TEXT )', 'CREATE TABLE t (g1 GEOMCOLLECTION, g2 GEOMETRYCOLLECTION)' ); - // SERIAL + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'g1', 1, null, 'YES', 'geomcollection', null, null, null, null, null, null, null, 'geomcollection', '', '', 'select,insert,update,references', '', '', null)", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'g2', 2, null, 'YES', 'geomcollection', null, null, null, null, null, null, null, 'geomcollection', '', '', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); + } + + public function testSerialDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE )', + 'CREATE TABLE "t" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT )', 'CREATE TABLE t (id SERIAL)' ); + + $this->assertExecutedInformationSchemaQueries( + array( + 'INSERT INTO _mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'DYNAMIC', 'utf8mb4_general_ci')", + 'INSERT INTO _mysql_information_schema_columns (table_schema, table_name, column_name, ordinal_position, column_default, is_nullable, data_type, character_maximum_length, character_octet_length, numeric_precision, numeric_scale, datetime_precision, character_set_name, collation_name, column_type, column_key, extra, privileges, column_comment, generation_expression, srs_id)' + . " VALUES ('wp', 't', 'id', 1, null, 'NO', 'bigint', null, null, 20, 0, null, null, null, 'bigint unsigned', 'PRI', 'auto_increment', 'select,insert,update,references', '', '', null)", + "SELECT * FROM _mysql_information_schema_tables WHERE table_type = \"BASE TABLE\" AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + ) + ); } public function testSystemVariables(): void { @@ -465,6 +1035,16 @@ private function assertQuery( $expected, string $query ): void { $executed_queries = array_values( array_slice( $executed_queries, 1, -1, true ) ); } + // Remove "information_schema" queries. + $executed_queries = array_values( + array_filter( + $executed_queries, + function ( $query ) { + return ! str_contains( $query, '_mysql_information_schema_' ); + } + ) + ); + // Remove "select changes()" executed after some queries. if ( count( $executed_queries ) > 1 @@ -475,6 +1055,44 @@ private function assertQuery( $expected, string $query ): void { if ( ! is_array( $expected ) ) { $expected = array( $expected ); } + + // Normalize whitespace. + foreach ( $executed_queries as $key => $executed_query ) { + $executed_queries[ $key ] = trim( preg_replace( '/\s+/', ' ', $executed_query ) ); + } + $this->assertSame( $expected, $executed_queries ); } + + private function assertExecutedInformationSchemaQueries( array $expected ): void { + // Collect and normalize "information_schema" queries. + $queries = array(); + foreach ( $this->driver->executed_sqlite_queries as $query ) { + if ( ! str_contains( $query['sql'], '_mysql_information_schema_' ) ) { + continue; + } + + // Normalize whitespace. + $sql = trim( preg_replace( '/\s+/', ' ', $query['sql'] ) ); + + // Inline parameters. + $sql = str_replace( '?', '%s', $sql ); + $queries[] = sprintf( + $sql, + ...array_map( + function ( $param ) { + if ( null === $param ) { + return 'null'; + } + if ( is_string( $param ) ) { + return $this->driver->get_pdo()->quote( $param ); + } + return $param; + }, + $query['params'] + ) + ); + } + $this->assertSame( $expected, $queries ); + } } diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index 7bd37d03..62a2b946 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -80,6 +80,65 @@ class WP_SQLite_Driver { // SERIAL, SET, and JSON types are handled in the translation process. ); + const DATA_TYPE_STRING_MAP = array( + // Numeric data types: + 'bit' => 'INTEGER', + 'bool' => 'INTEGER', + 'boolean' => 'INTEGER', + 'tinyint' => 'INTEGER', + 'smallint' => 'INTEGER', + 'mediumint' => 'INTEGER', + 'int' => 'INTEGER', + 'integer' => 'INTEGER', + 'bigint' => 'INTEGER', + 'float' => 'REAL', + 'double' => 'REAL', + 'real' => 'REAL', + 'decimal' => 'REAL', + 'dec' => 'REAL', + 'fixed' => 'REAL', + 'numeric' => 'REAL', + + // String data types: + 'char' => 'TEXT', + 'varchar' => 'TEXT', + 'nchar' => 'TEXT', + 'nvarchar' => 'TEXT', + 'tinytext' => 'TEXT', + 'text' => 'TEXT', + 'mediumtext' => 'TEXT', + 'longtext' => 'TEXT', + 'enum' => 'TEXT', + + // Date and time data types: + 'date' => 'TEXT', + 'time' => 'TEXT', + 'datetime' => 'TEXT', + 'timestamp' => 'TEXT', + 'year' => 'TEXT', + + // Binary data types: + 'binary' => 'INTEGER', + 'varbinary' => 'BLOB', + 'tinyblob' => 'BLOB', + 'blob' => 'BLOB', + 'mediumblob' => 'BLOB', + 'longblob' => 'BLOB', + + // Spatial data types: + 'geometry' => 'TEXT', + 'point' => 'TEXT', + 'linestring' => 'TEXT', + 'polygon' => 'TEXT', + 'multipoint' => 'TEXT', + 'multilinestring' => 'TEXT', + 'multipolygon' => 'TEXT', + 'geomcollection' => 'TEXT', + 'geometrycollection' => 'TEXT', + + // SERIAL, SET, and JSON types are handled in the translation process. + ); + const DATA_TYPES_CACHE_TABLE = '_mysql_data_types_cache'; const CREATE_DATA_TYPES_CACHE_TABLE = 'CREATE TABLE IF NOT EXISTS _mysql_data_types_cache ( @@ -842,6 +901,27 @@ private function execute_mysql_query( WP_Parser_Node $ast ) { */ $this->results = 0; break; + case 'showStatement': + $this->query_type = 'SHOW'; + $this->execute_show_statement( $ast ); + break; + case 'utilityStatement': + $this->query_type = 'DESCRIBE'; + $subtree = $ast->get_child_node(); + switch ( $subtree->rule_name ) { + case 'describeStatement': + $this->execute_describe_statement( $subtree ); + break; + default: + throw new Exception( + sprintf( + 'Unsupported statement type: "%s" > "%s"', + $ast->rule_name, + $subtree->rule_name + ) + ); + } + break; default: throw new Exception( sprintf( 'Unsupported statement type: "%s"', $ast->rule_name ) ); } @@ -902,91 +982,371 @@ private function execute_create_table_statement( WP_Parser_Node $node ): void { return; } - /* - * We need to handle some differences between MySQL and SQLite: - * - * 1. Inline index definitions: - * - * In MySQL, we can define an index inline with a column definition. - * In SQLite, we need to define indexes separately, using extra queries. - * - * 2. Column and constraint definition order: - * - * In MySQL, column and constraint definitions can be arbitrarily mixed. - * In SQLite, column definitions must come first, followed by constraints. - * - * 2. Auto-increment: - * - * In MySQL, there can at most one AUTO_INCREMENT column, and it must be - * a PRIMARY KEY, or the first column in a multi-column KEY. - * - * In SQLite, there can at most one AUTOINCREMENT column, and it must be - * a PRIMARY KEY, defined inline on a single column. - * - * Therefore, the following valid MySQL construct is not supported: - * CREATE TABLE t ( a INT AUTO_INCREMENT, b INT, PRIMARY KEY (a, b) ); - * @TODO: Support it with a single-column PK and a multi-column UNIQUE KEY. - */ + $table_name = trim( $this->translate( $node->get_descendant_node( 'tableName' ) ), '`"' ); + + // Save information to information schema tables. + $this->information_schema_builder->record_create_table( $node ); + + // Generate CREATE TABLE statement from the information schema tables. + $queries = $this->get_sqlite_create_table_statement( $table_name ); + foreach ( $queries as $query ) { + $this->execute_sqlite_query( $query ); + } + $this->set_result_from_affected_rows(); + } + + private function get_sqlite_create_table_statement( string $table_name, ?string $new_table_name = null ): array { + // 1. Get table info. + $table_info = $this->execute_sqlite_query( + ' + SELECT * + FROM _mysql_information_schema_tables + WHERE table_type = "BASE TABLE" + AND table_schema = ? + AND table_name = ? + ', + array( $this->db_name, $table_name ) + )->fetch( PDO::FETCH_ASSOC ); + + if ( false === $table_info ) { + throw new Exception( 'Table not found in information_schema' ); + } - // Collect column, index, and constraint nodes. - $columns = array(); - $constraints = array(); - $indexes = array(); - $has_autoincrement = false; - $primary_key_constraint = null; // Does not include inline PK definition. + // 2. Get column info. + $column_info = $this->execute_sqlite_query( + 'SELECT * FROM _mysql_information_schema_columns WHERE table_schema = ? AND table_name = ?', + array( $this->db_name, $table_name ) + )->fetchAll( PDO::FETCH_ASSOC ); + + // 3. Get index info, grouped by index name. + $constraint_info = $this->execute_sqlite_query( + 'SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = ? AND table_name = ?', + array( $this->db_name, $table_name ) + )->fetchAll( PDO::FETCH_ASSOC ); + + $grouped_constraints = array(); + foreach ( $constraint_info as $constraint ) { + $name = $constraint['INDEX_NAME']; + $seq = $constraint['SEQ_IN_INDEX']; + $grouped_constraints[ $name ][ $seq ] = $constraint; + } + + // 4. Generate CREATE TABLE statement columns. + $rows = array(); + $has_autoincrement = false; + foreach ( $column_info as $column ) { + $sql = ' '; + $sql .= sprintf( '"%s"', str_replace( '"', '""', $column['COLUMN_NAME'] ) ); + + $type = self::DATA_TYPE_STRING_MAP[ $column['DATA_TYPE'] ]; - foreach ( $element_list->get_descendant_nodes( 'columnDefinition' ) as $child ) { - if ( null !== $child->get_descendant_token( WP_MySQL_Lexer::AUTO_INCREMENT_SYMBOL ) ) { + /* + * In SQLite, there is a PRIMARY KEY quirk for backward compatibility. + * This applies to ROWID tables and single-column primary keys only: + * 1. "INTEGER PRIMARY KEY" creates an alias of ROWID. + * 2. "INT PRIMARY KEY" will not alias of ROWID. + * + * Therefore, we want to: + * 1. Use "INT PRIMARY KEY" when we have a single-column integer + * PRIMARY KEY without AUTOINCREMENT (to avoid the ROWID alias). + * 2. Use "INTEGER PRIMARY KEY" otherwise. + * + * See: + * - https://www.sqlite.org/autoinc.html + * - https://www.sqlite.org/lang_createtable.html + */ + if ( + 'INTEGER' === $type + && 'PRI' === $column['COLUMN_KEY'] + && 'auto_increment' !== $column['EXTRA'] + && count( $grouped_constraints['PRIMARY'] ) === 1 + ) { + $type = 'INT'; + } + + $sql .= ' ' . $type; + if ( 'NO' === $column['IS_NULLABLE'] ) { + $sql .= ' NOT NULL'; + } + if ( 'auto_increment' === $column['EXTRA'] ) { $has_autoincrement = true; + $sql .= ' PRIMARY KEY AUTOINCREMENT'; } - // @TODO: Collect inline index definitions. - $columns[] = $child; + if ( null !== $column['COLUMN_DEFAULT'] ) { + // @TODO: Correctly quote based on the data type. + $sql .= ' DEFAULT ' . $this->pdo->quote( $column['COLUMN_DEFAULT'] ); + } + $rows[] = $sql; } - foreach ( $element_list->get_descendant_nodes( 'tableConstraintDef' ) as $child ) { - if ( null !== $child->get_descendant_token( WP_MySQL_Lexer::PRIMARY_SYMBOL ) ) { - $primary_key_constraint = $child; + // 4. Generate CREATE TABLE statement constraints, collect indexes. + $create_index_sqls = array(); + foreach ( $grouped_constraints as $constraint ) { + ksort( $constraint ); + $info = $constraint[1]; + + if ( 'PRIMARY' === $info['INDEX_NAME'] ) { + if ( $has_autoincrement ) { + continue; + } + $sql = ' PRIMARY KEY ('; + $sql .= implode( + ', ', + array_map( + function ( $column ) { + return sprintf( '"%s"', str_replace( '"', '""', $column['COLUMN_NAME'] ) ); + }, + $constraint + ) + ); + $sql .= ')'; + $rows[] = $sql; } else { - $constraints[] = $child; + $is_unique = '0' === $info['NON_UNIQUE']; + + $sql = sprintf( 'CREATE %sINDEX', $is_unique ? 'UNIQUE ' : '' ); + $sql .= sprintf( ' "%s"', $info['INDEX_NAME'] ); + $sql .= sprintf( ' ON "%s" (', $table_name ); + $sql .= implode( + ', ', + array_map( + function ( $column ) { + return sprintf( '"%s"', str_replace( '"', '""', $column['COLUMN_NAME'] ) ); + }, + $constraint + ) + ); + $sql .= ')'; + + $create_index_sqls[] = $sql; } } - /* - * If we have a PRIMARY KEY constraint: - * 1. Without auto-increment, we can put it back to the list of constraints. - * 2. With auto-increment, we need to later move it to the column definition. - */ - if ( null !== $primary_key_constraint ) { - if ( ! $has_autoincrement ) { - $constraints[] = $primary_key_constraint; - } elseif ( count( $primary_key_constraint->get_descendant_nodes( 'keyPart' ) ) > 1 ) { - throw $this->not_supported_exception( - 'Composite primary key with AUTO_INCREMENT' - ); + // 5. Compose the CREATE TABLE statement. + $sql = sprintf( 'CREATE TABLE "%s" (%s', str_replace( '"', '""', $new_table_name ?? $table_name ), "\n" ); + $sql .= implode( ",\n", $rows ); + $sql .= "\n)"; + return array_merge( array( $sql ), $create_index_sqls ); + } + + private function get_mysql_create_table_statement( string $table_name ): ?string { + // 1. Get table info. + $table_info = $this->execute_sqlite_query( + ' + SELECT * + FROM _mysql_information_schema_tables + WHERE table_type = "BASE TABLE" + AND table_schema = ? + AND table_name = ? + ', + array( $this->db_name, $table_name ) + )->fetch( PDO::FETCH_ASSOC ); + + if ( false === $table_info ) { + return null; + } + + // 2. Get column info. + $column_info = $this->execute_sqlite_query( + 'SELECT * FROM _mysql_information_schema_columns WHERE table_schema = ? AND table_name = ?', + array( $this->db_name, $table_name ) + )->fetchAll( PDO::FETCH_ASSOC ); + + // 3. Get index info, grouped by index name. + $constraint_info = $this->execute_sqlite_query( + 'SELECT * FROM _mysql_information_schema_statistics WHERE table_schema = ? AND table_name = ?', + array( $this->db_name, $table_name ) + )->fetchAll( PDO::FETCH_ASSOC ); + + $grouped_constraints = array(); + foreach ( $constraint_info as $constraint ) { + $name = $constraint['INDEX_NAME']; + $seq = $constraint['SEQ_IN_INDEX']; + $grouped_constraints[ $name ][ $seq ] = $constraint; + } + + // 4. Generate CREATE TABLE statement columns. + $rows = array(); + foreach ( $column_info as $column ) { + $sql = ' '; + // @TODO: Proper identifier escaping. + $sql .= sprintf( '`%s`', str_replace( '`', '``', $column['COLUMN_NAME'] ) ); + + $sql .= ' ' . $column['COLUMN_TYPE']; + if ( 'NO' === $column['IS_NULLABLE'] ) { + $sql .= ' NOT NULL'; + } + if ( 'auto_increment' === $column['EXTRA'] ) { + $sql .= ' AUTO_INCREMENT'; } + if ( null !== $column['COLUMN_DEFAULT'] ) { + // @TODO: Correctly quote based on the data type. + $sql .= ' DEFAULT ' . $this->pdo->quote( $column['COLUMN_DEFAULT'] ); + } + $rows[] = $sql; } - $query_parts = array( 'CREATE' ); - foreach ( $node->get_child_node()->get_children() as $child ) { - if ( $child instanceof WP_Parser_Node && 'tableElementList' === $child->rule_name ) { - $query_parts[] = $this->translate_sequence( array_merge( $columns, $constraints ), ' , ' ); + // 4. Generate CREATE TABLE statement constraints, collect indexes. + $create_index_sqls = array(); + foreach ( $grouped_constraints as $constraint ) { + ksort( $constraint ); + $info = $constraint[1]; + + if ( 'PRIMARY' === $info['INDEX_NAME'] ) { + $sql = ' PRIMARY KEY ('; + $sql .= implode( + ', ', + array_map( + function ( $column ) { + // @TODO: Proper identifier escaping. + return sprintf( '`%s`', str_replace( '`', '``', $column['COLUMN_NAME'] ) ); + }, + $constraint + ) + ); + $sql .= ')'; + $rows[] = $sql; } else { - $part = $this->translate( $child ); - if ( null !== $part ) { - $query_parts[] = $part; - } + $is_unique = '0' === $info['NON_UNIQUE']; + + $sql = sprintf( ' %sKEY', $is_unique ? 'UNIQUE ' : '' ); + // @TODO: Proper identifier escaping. + $sql .= sprintf( ' `%s`', str_replace( '`', '``', $info['INDEX_NAME'] ) ); + $sql .= ' ('; + $sql .= implode( + ', ', + array_map( + function ( $column ) { + // @TODO: Proper identifier escaping. + return sprintf( '`%s`', str_replace( '`', '``', $column['COLUMN_NAME'] ) ); + }, + $constraint + ) + ); + $sql .= ')'; + + $rows[] = $sql; } } - // @TODO: Execute queries for inline index definitions. + // 5. Compose the CREATE TABLE statement. + // @TODO: Proper identifier escaping. + $sql = sprintf( 'CREATE TABLE `%s` (%s', str_replace( '`', '``', $table_name ), "\n" ); + $sql .= implode( ",\n", $rows ); + $sql .= "\n)"; - $this->execute_sqlite_query( implode( ' ', $query_parts ) ); - $this->set_result_from_affected_rows(); + $sql .= sprintf( ' ENGINE=%s', $table_info['ENGINE'] ); + + $collation = $table_info['TABLE_COLLATION']; + $charset = substr( $collation, 0, strpos( $collation, '_' ) ); + $sql .= sprintf( ' DEFAULT CHARSET=%s', $charset ); + $sql .= sprintf( ' COLLATE=%s', $collation ); + return $sql; } private function execute_alter_table_statement( WP_Parser_Node $node ): void { $table_name = $this->translate( $node->get_descendant_node( 'tableRef' ) ); - $actions = $node->get_descendant_nodes( 'alterListItem' ); + $table_name = trim( $table_name, '`"' ); + + // Save all column names from the original table. + $column_names = $this->execute_sqlite_query( + 'SELECT COLUMN_NAME FROM _mysql_information_schema_columns WHERE table_schema = ? AND table_name = ?', + array( $this->db_name, $table_name ) + )->fetchAll( PDO::FETCH_COLUMN ); + + // Track column renames and removals. + $column_map = array_combine( $column_names, $column_names ); + foreach ( $node->get_descendant_nodes( 'alterListItem' ) as $action ) { + $first_token = $action->get_child_token(); + + if ( WP_MySQL_Lexer::DROP_SYMBOL === $first_token->id ) { + $name = $this->translate( $action->get_child_node( 'columnInternalRef' ) ); + if ( null !== $name ) { + $name = trim( $name, '`"' ); + unset( $column_map[ $name ] ); + } + } + + if ( WP_MySQL_Lexer::CHANGE_SYMBOL === $first_token->id ) { + $old_name = $this->translate( $action->get_child_node( 'columnInternalRef' ) ); + $new_name = $this->translate( $action->get_child_node( 'identifier' ) ); + $old_name = trim( $old_name, '`"' ); + $new_name = trim( $new_name, '`"' ); + $column_map[ $old_name ] = $new_name; + } + + if ( WP_MySQL_Lexer::RENAME_SYMBOL === $first_token->id ) { + $column_ref = $action->get_child_node( 'columnInternalRef' ); + if ( null !== $column_ref ) { + $old_name = $this->translate( $column_ref ); + $new_name = $this->translate( $action->get_child_node( 'identifier' ) ); + $old_name = trim( $old_name, '`"' ); + $new_name = trim( $new_name, '`"' ); + $column_map[ $old_name ] = $new_name; + } + } + } + + $this->information_schema_builder->record_alter_table( $node ); + + /* + * See: + * https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes + */ + + // 1. If foreign key constraints are enabled, disable them. + // @TODO + + // 2. Create a new table with the new schema. + $tmp_table_name = "_tmp__{$table_name}_" . uniqid(); + $queries = $this->get_sqlite_create_table_statement( $table_name, $tmp_table_name ); + $create_table_query = $queries[0]; + $constraint_queries = array_slice( $queries, 1 ); + $this->execute_sqlite_query( $create_table_query ); + + // 3. Copy data from the original table to the new table. + $this->execute_sqlite_query( + sprintf( + 'INSERT INTO "%s" (%s) SELECT %s FROM "%s"', + $tmp_table_name, + implode( + ', ', + array_map( + function ( $column ) { + return '"' . $column . '"'; + }, + $column_map + ) + ), + implode( + ', ', + array_map( + function ( $column ) { + return '"' . $column . '"'; + }, + array_keys( $column_map ) + ) + ), + $table_name + ) + ); + + // 4. Drop the original table. + $this->execute_sqlite_query( sprintf( 'DROP TABLE "%s"', $table_name ) ); + + // 5. Rename the new table to the original table name. + $this->execute_sqlite_query( sprintf( 'ALTER TABLE "%s" RENAME TO "%s"', $tmp_table_name, $table_name ) ); + + // 6. Reconstruct indexes, triggers, and views. + foreach ( $constraint_queries as $query ) { + $this->execute_sqlite_query( $query ); + } + + // @TODO: Triggers and views. + + $this->results = 1; + $this->return_value = $this->results; + return; /* * SQLite supports only a small subset of MySQL ALTER TABLE statement. @@ -1008,42 +1368,104 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void { * * @TODO: Address these nuances. */ - foreach ( $actions as $action ) { - $token = $action->get_child_token(); - - // ADD column/constraint. - if ( WP_MySQL_Lexer::ADD_SYMBOL === $token->id ) { - // ADD COLUMN. - $field_definition = $action->get_descendant_node( 'fieldDefinition' ); - if ( null !== $field_definition ) { - $field_name = $this->translate( $action->get_child_node( 'identifier' ) ); - $field = $this->translate( $field_definition ); - $this->execute_sqlite_query( - sprintf( 'ALTER TABLE %s ADD COLUMN %s %s', $table_name, $field_name, $field ) - ); - } + } - // ADD CONSTRAINT. - $constraint = $action->get_descendant_node( 'tableConstraintDef' ); - if ( null !== $constraint ) { - $constraint_name = $this->translate( $constraint->get_child_node( 'identifier' ) ); - $constraint = $this->translate( $constraint ); - $this->execute_sqlite_query( - sprintf( 'ALTER TABLE %s ADD CONSTRAINT %s %s', $table_name, $constraint_name, $constraint ) - ); - } - } elseif ( WP_MySQL_Lexer::DROP_SYMBOL === $token->id ) { - // DROP COLUMN. - $field_name = $this->translate( $action->get_child_node( 'columnInternalRef' ) ); - if ( null !== $field_name ) { - $this->execute_sqlite_query( - sprintf( 'ALTER TABLE %s DROP COLUMN %s', $table_name, $field_name ) - ); + private function execute_describe_statement( WP_Parser_Node $node ): void { + $table_name = $this->translate( $node->get_child_node( 'tableRef' ) ); + $table_name = trim( $table_name, '`"' ); + + $column_info = $this->execute_sqlite_query( + ' + SELECT + column_name AS `Field`, + column_type AS `Type`, + is_nullable AS `Null`, + column_key AS `Key`, + column_default AS `Default`, + extra AS Extra + FROM _mysql_information_schema_columns + WHERE table_schema = ? + AND table_name = ? + ', + array( $this->db_name, $table_name ) + )->fetchAll( PDO::FETCH_OBJ ); + + $this->set_results_from_fetched_data( $column_info ); + } + + private function execute_show_statement( WP_Parser_Node $node ): void { + $tokens = $node->get_child_tokens(); + $keyword1 = $tokens[1]; + $keyword2 = $tokens[2] ?? null; + + switch ( $keyword1->id ) { + case WP_MySQL_Lexer::CREATE_SYMBOL: + if ( WP_MySQL_Lexer::TABLE_SYMBOL === $keyword2->id ) { + $table_name = $this->translate( $node->get_child_node( 'tableRef' ) ); + $table_name = trim( $table_name, '`"' ); + $sql = $this->get_mysql_create_table_statement( $table_name ); + if ( null === $sql ) { + $this->set_results_from_fetched_data( array() ); + } else { + $this->set_results_from_fetched_data( + array( + (object)array( + 'Create Table' => $sql, + ), + ) + ); + } + } elseif ( WP_MySQL_Lexer::PROCEDURE_SYMBOL === $keyword2->id ) { + $this->results = true; + return; } - } + // Fall through to default. + case WP_MySQL_Lexer::INDEX_SYMBOL: + case WP_MySQL_Lexer::INDEXES_SYMBOL: + case WP_MySQL_Lexer::KEYS_SYMBOL: + $table_name = $this->translate( $node->get_child_node( 'tableRef' ) ); + $table_name = trim( $table_name, '`"' ); + $this->execute_show_index_statement( $table_name ); + break; + case WP_MySQL_Lexer::GRANTS_SYMBOL: + $this->set_results_from_fetched_data( + array( + (object) array( + 'Grants for root@localhost' => 'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE, CREATE ROLE, DROP ROLE ON *.* TO `root`@`localhost` WITH GRANT OPTION', + ), + ) + ); + return; } + } - $this->set_result_from_affected_rows(); + private function execute_show_index_statement( string $table_name ): void { + $index_info = $this->execute_sqlite_query( + ' + SELECT + TABLE_NAME AS `Table`, + NON_UNIQUE AS `Non_unique`, + INDEX_NAME AS `Key_name`, + SEQ_IN_INDEX AS `Seq_in_index`, + COLUMN_NAME AS `Column_name`, + COLLATION AS `Collation`, + CARDINALITY AS `Cardinality`, + SUB_PART AS `Sub_part`, + PACKED AS `Packed`, + NULLABLE AS `Null`, + INDEX_TYPE AS `Index_type`, + COMMENT AS `Comment`, + INDEX_COMMENT AS `Index_comment`, + IS_VISIBLE AS `Visible`, + EXPRESSION AS `Expression` + FROM _mysql_information_schema_statistics + WHERE table_schema = ? + AND table_name = ? + ', + array( $this->db_name, $table_name ) + )->fetchAll( PDO::FETCH_OBJ ); + + $this->set_results_from_fetched_data( $index_info ); } private function translate( $ast ) { @@ -1116,64 +1538,17 @@ private function translate( $ast ) { // When we have no value, it's reasonable to use NULL. return 'NULL'; - case 'fieldDefinition': - /* - * In SQLite, there is the a quirk for backward compatibility: - * 1. INTEGER PRIMARY KEY creates an alias of ROWID. - * 2. INT PRIMARY KEY will not alias of ROWID. - * - * Therefore, we want to: - * 1. Use INTEGER PRIMARY KEY for when we have AUTOINCREMENT. - * 2. Use INT PRIMARY KEY otherwise. - */ - $has_primary_key = $ast->get_descendant_token( WP_MySQL_Lexer::KEY_SYMBOL ) !== null; - $has_autoincrement = $ast->get_descendant_token( WP_MySQL_Lexer::AUTO_INCREMENT_SYMBOL ) !== null; - $children = $ast->get_children(); - $data_type_node = array_shift( $children ); - $data_type = $this->translate( $data_type_node ); - if ( $has_primary_key && 'INTEGER' === $data_type ) { - $data_type = $has_autoincrement ? 'INTEGER' : 'INT'; - } - - $attributes = $this->translate_sequence( $children ); - $definition = $data_type . ( null === $attributes ? '' : " $attributes" ); - - /* - * In SQLite, AUTOINCREMENT must always be preceded by PRIMARY KEY. - * Therefore, we remove both PRIMARY KEY and AUTOINCREMENT from - * column attributes, and append them here in SQLite-friendly way. - */ - if ( $has_autoincrement ) { - return $definition . ' PRIMARY KEY AUTOINCREMENT'; - } elseif ( $has_primary_key ) { - return $definition . ' PRIMARY KEY'; - } - return $definition; - case 'columnAttribute': - case 'gcolAttribute': - /* - * Remove PRIMARY KEY and AUTOINCREMENT from the column attributes. - * They are handled in the "fieldDefinition" node. - */ - if ( $ast->has_child_token( WP_MySQL_Lexer::KEY_SYMBOL ) ) { - return null; - } - if ( $ast->has_child_token( WP_MySQL_Lexer::AUTO_INCREMENT_SYMBOL ) ) { - return null; - } - return $this->translate_sequence( $ast->get_children() ); - case 'createTableOptions': - return $this->translate_sequence( $ast->get_children(), ', ' ); - case 'createTableOption': - if ( $ast->get_child_token( WP_MySQL_Lexer::ENGINE_SYMBOL ) ) { - return null; - } - return $this->translate_sequence( $ast->get_children() ); + case 'defaultCollation': + // @TODO: Check and save in information schema. + return null; case 'duplicateAsQueryExpression': // @TODO: How to handle IGNORE/REPLACE? // The "AS" keyword is optional in MySQL, but required in SQLite. return 'AS ' . $this->translate( $ast->get_child_node() ); + case 'indexHint': + case 'indexHintList': + return null; default: return $this->translate_sequence( $ast->get_children() ); }