From 385e708fc0dcf204d598e5188f21d708d228137b Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Thu, 12 Dec 2024 16:41:25 +0100 Subject: [PATCH] wip --- tests/WP_SQLite_Driver_Translation_Tests.php | 194 +++++++++-- .../sqlite-ast/class-wp-sqlite-driver.php | 313 +++++++++++------- ...s-wp-sqlite-information-schema-builder.php | 77 +++-- 3 files changed, 414 insertions(+), 170 deletions(-) diff --git a/tests/WP_SQLite_Driver_Translation_Tests.php b/tests/WP_SQLite_Driver_Translation_Tests.php index 27d559bd..30775eb3 100644 --- a/tests/WP_SQLite_Driver_Translation_Tests.php +++ b/tests/WP_SQLite_Driver_Translation_Tests.php @@ -213,13 +213,16 @@ public function testCreateTable(): void { . " 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)' ); @@ -233,6 +236,9 @@ public function testCreateTableWithMultipleColumns(): void { . " 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'", ) ); } @@ -249,6 +255,11 @@ public function testCreateTableWithBasicConstraints(): void { . " 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'", ) ); } @@ -266,6 +277,9 @@ public function testCreateTableWithEngine(): void { . " 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'", ) ); } @@ -283,6 +297,9 @@ public function testCreateTableWithCollate(): void { . " 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'", ) ); } @@ -298,7 +315,7 @@ public function testCreateTableWithPrimaryKey(): 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)' ); @@ -308,6 +325,11 @@ public function testCreateTableWithPrimaryKey(): void { . " 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'", ) ); } @@ -315,7 +337,7 @@ public function testCreateTableWithPrimaryKey(): void { public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { // With AUTOINCREMENT, we expect "INTEGER". $this->assertQuery( - 'CREATE TABLE "t1" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT )', + 'CREATE TABLE "t1" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT )', 'CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT)' ); @@ -325,12 +347,17 @@ public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { . " 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 "t2" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT )', + 'CREATE TABLE "t2" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT )', 'CREATE TABLE t2 (id INT AUTO_INCREMENT PRIMARY KEY)' ); @@ -340,12 +367,17 @@ public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { . " 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 "t3" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT )', + 'CREATE TABLE "t3" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT )', 'CREATE TABLE t3 (id INT AUTO_INCREMENT, PRIMARY KEY(id))' ); @@ -355,11 +387,17 @@ public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { . " 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, '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', 't3', 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 = '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'", ) ); } - public function testCreateTableWithIfNotExists(): void { + // @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)' @@ -373,6 +411,46 @@ public function testCreateTableWithIfNotExists(): void { . " 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', 'UNI', '', '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)', 'UNI', '', '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', 'id', 1, 'id', 'A', 0, null, null, 'YES', 'BTREE', '', '', 'YES', 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', 'name', 1, 'name', 'A', 0, null, null, 'YES', '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 testCreateTableFromSelectQuery(): void { @@ -390,7 +468,8 @@ public function testCreateTableFromSelectQuery(): void { ); } - public function testCreateTemporaryTable(): void { + // @TODO: CREATE TEMPORARY TABLE + /*public yunction testCreateTemporaryTable(): void { $this->assertQuery( 'CREATE TEMPORARY TABLE "t" ( "id" INTEGER )', 'CREATE TEMPORARY TABLE t (id INT)' @@ -401,7 +480,7 @@ public function testCreateTemporaryTable(): void { 'CREATE TEMPORARY TABLE IF NOT EXISTS "t" ( "id" INTEGER )', 'CREATE TEMPORARY TABLE IF NOT EXISTS t (id INT)' ); - } + }*/ public function testAlterTableAddColumn(): void { $this->assertQuery( @@ -453,7 +532,7 @@ public function testAlterTableAddAndDropColumns(): void { public function testBitDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "i1" INTEGER , "i2" INTEGER )', + 'CREATE TABLE "t" ( "i1" INTEGER, "i2" INTEGER )', 'CREATE TABLE t (i1 BIT, i2 BIT(10))' ); @@ -465,13 +544,16 @@ public function testBitDataTypes(): void { . " 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 testBooleanDataTypes(): void { $this->assertQuery( - 'CREATE TABLE "t" ( "i1" INTEGER , "i2" INTEGER )', + 'CREATE TABLE "t" ( "i1" INTEGER, "i2" INTEGER )', 'CREATE TABLE t (i1 BOOL, i2 BOOLEAN)' ); @@ -483,13 +565,16 @@ public function testBooleanDataTypes(): void { . " 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)' ); @@ -509,13 +594,16 @@ public function testIntegerDataTypes(): void { . " 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)' ); @@ -531,13 +619,16 @@ public function testFloatDataTypes(): void { . " 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)' ); @@ -553,13 +644,16 @@ public function testDecimalTypes(): void { . " 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" TEXT, "c2" TEXT )', 'CREATE TABLE t (c1 CHAR, c2 CHAR(10))' ); @@ -571,13 +665,16 @@ public function testCharDataTypes(): void { . " 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 )', + 'CREATE TABLE "t" ( "c1" TEXT, "c2" TEXT, "c3" TEXT )', 'CREATE TABLE t (c1 VARCHAR(255), c2 CHAR VARYING(255), c3 CHARACTER VARYING(255))' ); @@ -591,13 +688,16 @@ public function testVarcharDataTypes(): void { . " 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 , "c3" TEXT , "c4" TEXT )', + '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))' ); @@ -613,13 +713,16 @@ public function testNationalCharDataTypes(): void { . " 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))' ); @@ -633,13 +736,16 @@ public function testNcharVarcharDataTypes(): void { . " 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))' ); @@ -653,13 +759,16 @@ public function testNationalVarcharDataTypes(): void { . " 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)' ); @@ -675,6 +784,9 @@ public function testTextDataTypes(): void { . " 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'", ) ); } @@ -691,13 +803,16 @@ public function testEnumDataTypes(): void { . " 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)' ); @@ -715,13 +830,16 @@ public function testDateAndTimeDataTypes(): void { . " 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))' ); @@ -733,13 +851,16 @@ public function testBinaryDataTypes(): void { . " 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)' ); @@ -755,13 +876,16 @@ public function testBlobDataTypes(): void { . " 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)' ); @@ -777,13 +901,16 @@ public function testBasicSpatialDataTypes(): void { . " 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)' ); @@ -797,13 +924,16 @@ public function testMultiObjectSpatialDataTypes(): void { . " 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)' ); @@ -815,13 +945,16 @@ public function testGeometryCollectionDataTypes(): void { . " 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)' ); @@ -831,6 +964,9 @@ public function testSerialDataTypes(): void { . " 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'", ) ); } @@ -892,6 +1028,12 @@ function ( $query ) { 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 ); } diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index d96e8797..d882e439 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 ( @@ -902,89 +961,150 @@ 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' ) ), '`"' ); - // 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. + // Save information to information schema tables. + $this->information_schema_builder->create_table( $node ); - foreach ( $element_list->get_descendant_nodes( 'columnDefinition' ) as $child ) { - if ( null !== $child->get_descendant_token( WP_MySQL_Lexer::AUTO_INCREMENT_SYMBOL ) ) { - $has_autoincrement = true; - } - // @TODO: Collect inline index definitions. - $columns[] = $child; + // 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(); + } - foreach ( $element_list->get_descendant_nodes( 'tableConstraintDef' ) as $child ) { - if ( null !== $child->get_descendant_token( WP_MySQL_Lexer::PRIMARY_SYMBOL ) ) { - $primary_key_constraint = $child; - } else { - $constraints[] = $child; - } + private function get_sqlite_create_table_statement( string $table_name ): 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' ); } - /* - * 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' - ); - } + // 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; } - $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 ), ' , ' ); - } else { - $part = $this->translate( $child ); - if ( null !== $part ) { - $query_parts[] = $part; - } + // 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'] ]; + + /* + * 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'; } + if ( null !== $column['COLUMN_DEFAULT'] ) { + // @TODO: Correctly quote based on the data type. + $sql .= ' DEFAULT ' . $this->pdo->quote( $column['COLUMN_DEFAULT'] ); + } + $rows[] = $sql; } - // @TODO: Execute queries for inline index definitions. + // 4. Generate CREATE TABLE statement constraints, collect indexes. + $create_index_sqls = array(); + foreach ( $grouped_constraints as $constraint ) { + ksort( $constraint ); + $info = $constraint[1]; - $this->execute_sqlite_query( implode( ' ', $query_parts ) ); - $this->set_result_from_affected_rows(); + 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 { + $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; + } + } - // Save information to "information_schema" tables. - $this->information_schema_builder->create_table( $node ); + // 5. Compose the CREATE TABLE statement. + $sql = sprintf( 'CREATE TABLE "%s" (%s', str_replace( '"', '""', $table_name ), "\n" ); + $sql .= implode( ",\n", $rows ); + $sql .= "\n)"; + return array_merge( array( $sql ), $create_index_sqls ); } private function execute_alter_table_statement( WP_Parser_Node $node ): void { @@ -1119,59 +1239,6 @@ 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': return null; case 'duplicateAsQueryExpression': diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php index 7ad943f5..58b0cf3e 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php @@ -377,6 +377,9 @@ public function create_table( WP_Parser_Node $node ): void { $datetime_precision = $this->get_column_datetime_precision( $column, $data_type ); $generation_expression = $this->get_column_generation_expression( $column ); + $has_inline_primary_key = null !== $column->get_descendant_token( WP_MySQL_Lexer::KEY_SYMBOL ); + $has_inline_unique_key = null !== $column->get_descendant_token( WP_MySQL_Lexer::UNIQUE_SYMBOL ); + $this->insert_values( '_mysql_information_schema_columns', array( @@ -407,13 +410,51 @@ public function create_table( WP_Parser_Node $node ): void { // Store column info needed for indexes and constraints. $column_info_map[ $name ] = array( - 'nullable' => $nullable, + 'nullable' => $nullable === 'YES', 'data_type' => $data_type, 'character_maximum_length' => $char_length, + 'has_inline_primary_key' => $has_inline_primary_key, + 'has_inline_unique_key' => $has_inline_unique_key, ); } // 3. INFORMATION_SCHEMA.STATISTICS (indexes): + + // Inline PRIMARY KEY and UNIQUE constraints. + foreach ( $column_info_map as $column_name => $column_info ) { + if ( true === $column_info['has_inline_primary_key'] ) { + $index_name = 'PRIMARY'; + } elseif ( true === $column_info['has_inline_unique_key'] ) { + $index_name = $column_name; + } else { + continue; + } + + $this->insert_values( + '_mysql_information_schema_statistics', + array( + 'table_schema' => $this->db_name, + 'table_name' => $table_name, + 'non_unique' => 0, + 'index_schema' => $this->db_name, + 'index_name' => $index_name, + 'seq_in_index' => 1, + 'column_name' => $column_name, + 'collation' => 'A', + 'cardinality' => 0, // not implemented + 'sub_part' => null, + 'packed' => null, // not implemented + 'nullable' => $column_info['nullable'] ? 'YES' : '', + 'index_type' => 'BTREE', + 'comment' => '', // not implemented + 'index_comment' => '', // @TODO + 'is_visible' => 'YES', // @TODO: Save actual visibility value. + 'expression' => null, // @TODO + ) + ); + } + + // Standalone constraint definitions. foreach ( $node->get_descendant_nodes( 'tableConstraintDef' ) as $constraint ) { $child = $constraint->get_child(); if ( $child instanceof WP_Parser_Node ) { @@ -426,7 +467,7 @@ public function create_table( WP_Parser_Node $node ): void { // Get first index column data type (needed for index type). $first_index_part = $constraint->get_descendant_node( 'keyListVariants' ); - $first_column_name = $this->get_index_name( $first_index_part ); + $first_column_name = $this->get_index_column_name( $first_index_part ); $first_column_type = $column_info_map[ $first_column_name ]['data_type'] ?? null; $has_spatial_column = null !== $first_column_type && $this->is_spatial_data_type( $first_column_type ); @@ -438,7 +479,7 @@ public function create_table( WP_Parser_Node $node ): void { foreach ( $constraint->get_descendant_nodes( 'keyListVariants' ) as $key ) { $column_name = $this->get_index_column_name( $key ); $collation = $this->get_index_column_collation( $key, $index_type ); - $nullable = $column_info_map[ $column_name ]['nullable'] ? 'YES' : ''; + $nullable = true === $column_info_map[ $column_name ]['nullable'] ? 'YES' : ''; $sub_part = $this->get_index_column_sub_part( $key, @@ -446,14 +487,6 @@ public function create_table( WP_Parser_Node $node ): void { $has_spatial_column ); - /** - * SUB_PART INTEGER, -- number of indexed chars, NULL for full column - * COMMENT TEXT NOT NULL DEFAULT '', -- not implemented - * INDEX_COMMENT TEXT NOT NULL DEFAULT '', -- index comment - * IS_VISIBLE TEXT NOT NULL DEFAULT 'YES', -- 'NO' if column is hidden, 'YES' otherwise - * EXPRESSION TEXT -- expression for functional indexes - */ - $this->insert_values( '_mysql_information_schema_statistics', array( @@ -471,9 +504,9 @@ public function create_table( WP_Parser_Node $node ): void { 'nullable' => $nullable, 'index_type' => $index_type, 'comment' => '', // not implemented - 'index_comment' => '', - 'is_visible' => 'YES', - 'expression' => null, + 'index_comment' => '', // @TODO + 'is_visible' => 'YES', // @TODO: Save actual visibility value. + 'expression' => null, // @TODO ) ); } @@ -758,7 +791,7 @@ private function get_column_key( ): string { // 1. PRI: Column is a primary key or its any component. if ( - null !== $column_node->get_descendant_token( WP_MySQL_Lexer::PRIMARY_SYMBOL ) + null !== $column_node->get_descendant_token( WP_MySQL_Lexer::KEY_SYMBOL ) ) { return 'PRI'; } @@ -772,9 +805,11 @@ private function get_column_key( $first_in_unique = false; $first_in_index = false; foreach ( $table_node->get_descendant_nodes( 'tableConstraintDef' ) as $constraint ) { - $is_primary = null !== $constraint->get_descendant_token( WP_MySQL_Lexer::KEY_SYMBOL ); + $is_primary = null !== $constraint->get_descendant_token( WP_MySQL_Lexer::PRIMARY_SYMBOL ); $is_unique = null !== $constraint->get_descendant_token( WP_MySQL_Lexer::UNIQUE_SYMBOL ); - $is_index = null !== $constraint->get_descendant_token( WP_MySQL_Lexer::INDEX_SYMBOL ); + $is_index = + null !== $constraint->get_descendant_token( WP_MySQL_Lexer::INDEX_SYMBOL ) + || null !== $constraint->get_descendant_token( WP_MySQL_Lexer::KEY_SYMBOL ); if ( ! $is_primary && ! $is_unique && ! $is_index ) { continue; @@ -1025,7 +1060,7 @@ private function get_index_name( WP_Parser_Node $node ): string { if ( 'exprWithParentheses' === $subnode->rule_name ) { $name = 'functional_index'; } else { - $name = $this->get_value( $subnode ); + $name = $this->get_value( $subnode->get_descendant_node( 'identifier' ) ); } // @TODO: Check if the name is already used. @@ -1070,11 +1105,11 @@ private function get_index_type( } private function get_index_column_name( WP_Parser_Node $node ): ?string { - $key_part = $node->get_child_node( 'keyPart' ); + $key_part = $node->get_descendant_node( 'keyPart' ); if ( null === $key_part ) { return null; } - return $this->get_value( $node->get_descendant_node( 'identifier' ) ); + return $this->get_value( $key_part->get_descendant_node( 'identifier' ) ); } private function get_index_column_collation( WP_Parser_Node $node, string $index_type ): ?string { @@ -1084,7 +1119,7 @@ private function get_index_column_collation( WP_Parser_Node $node, string $index $collate_node = $node->get_descendant_node( 'collationName' ); if ( null === $collate_node ) { - return null; + return 'A'; } $collate = strtoupper( $this->get_value( $collate_node ) ); return 'DESC' === $collate ? 'D' : 'A';