From 6a641f970419430b1c83ab9d439cdf7ed2b8562c Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 15 Apr 2025 16:08:18 +0200 Subject: [PATCH 01/36] Implement a database configurator service for DB initialization and migration --- load.php | 1 + tests/WP_SQLite_Driver_Metadata_Tests.php | 1 + tests/WP_SQLite_Driver_Query_Tests.php | 1 + tests/WP_SQLite_Driver_Tests.php | 1 + tests/WP_SQLite_Driver_Translation_Tests.php | 1 + tests/bootstrap.php | 6 + tests/tools/dump-sqlite-query.php | 1 + .../class-wp-sqlite-configurator.php | 103 ++++++++++++++++++ .../sqlite-ast/class-wp-sqlite-driver.php | 46 +++++++- wp-includes/sqlite/class-wp-sqlite-db.php | 1 + 10 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 wp-includes/sqlite-ast/class-wp-sqlite-configurator.php diff --git a/load.php b/load.php index 4e082ee..d2b647d 100644 --- a/load.php +++ b/load.php @@ -12,6 +12,7 @@ * @package wp-sqlite-integration */ +define( 'SQLITE_DRIVER_VERSION', '2.1.17-alpha' ); define( 'SQLITE_MAIN_FILE', __FILE__ ); require_once __DIR__ . '/php-polyfills.php'; diff --git a/tests/WP_SQLite_Driver_Metadata_Tests.php b/tests/WP_SQLite_Driver_Metadata_Tests.php index 33625b7..65bd157 100644 --- a/tests/WP_SQLite_Driver_Metadata_Tests.php +++ b/tests/WP_SQLite_Driver_Metadata_Tests.php @@ -1,5 +1,6 @@ driver = $driver; + $this->information_schema_builder = $information_schema_builder; + } + + /** + * Ensure that the SQLite database is configured. + * + * This method checks if the database is configured for the latest SQLite + * driver version, and if it is not, it will configure the database. + */ + public function ensure_database_configured(): void { + $version = SQLITE_DRIVER_VERSION; + $db_version = $this->driver->get_saved_driver_version(); + if ( version_compare( $version, $db_version ) > 0 ) { + $this->configure_database(); + } + } + + /** + * Configure the SQLite database. + * + * This method creates tables used for emulating MySQL behaviors in SQLite, + * and populates them with necessary data. When it is used with an already + * configured database, it will update the configuration as per the current + * SQLite driver version and attempt to repair any configuration corruption. + */ + public function configure_database(): void { + $this->ensure_global_variables_table(); + $this->information_schema_builder->ensure_information_schema_tables(); + $this->save_current_driver_version(); + } + + /** + * Ensure that the global variables table exists. + * + * This method configures a database table to store MySQL global variables + * and other internal configuration values. + */ + private function ensure_global_variables_table(): void { + $this->driver->execute_sqlite_query( + sprintf( + 'CREATE TABLE IF NOT EXISTS %s (name TEXT PRIMARY KEY, value TEXT)', + WP_SQLite_Driver::GLOBAL_VARIABLES_TABLE_NAME + ) + ); + } + + /** + * Save the current SQLite driver version. + * + * This method saves the current SQLite driver version to the database. + */ + private function save_current_driver_version(): void { + $this->driver->execute_sqlite_query( + sprintf( + 'INSERT INTO %s (name, value) VALUES (?, ?) ON CONFLICT(name) DO UPDATE SET value = ?', + WP_SQLite_Driver::GLOBAL_VARIABLES_TABLE_NAME + ), + array( + WP_SQLite_Driver::DRIVER_VERSION_VARIABLE_NAME, + SQLITE_DRIVER_VERSION, + SQLITE_DRIVER_VERSION, + ) + ); + } +} diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index 361f278..7b000a8 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -40,6 +40,22 @@ class WP_SQLite_Driver { */ const RESERVED_PREFIX = '_wp_sqlite_'; + /** + * The name of a global variables table. + * + * This special table is used to emulate MySQL global variables and to store + * some internal configuration values. + */ + const GLOBAL_VARIABLES_TABLE_NAME = self::RESERVED_PREFIX . 'global_variables'; + + /** + * The name of the SQLite driver version variable. + * + * This internal variable is used to store the latest version of the SQLite + * driver that was used to initialize and configure the SQLite database. + */ + const DRIVER_VERSION_VARIABLE_NAME = self::RESERVED_PREFIX . 'driver_version'; + /** * A map of MySQL tokens to SQLite data types. * @@ -524,7 +540,10 @@ public function __construct( array $options ) { self::RESERVED_PREFIX, array( $this, 'execute_sqlite_query' ) ); - $this->information_schema_builder->ensure_information_schema_tables(); + + // Ensure that the database is configured. + $migrator = new WP_SQLite_Configurator( $this, $this->information_schema_builder ); + $migrator->ensure_database_configured(); } /** @@ -545,6 +564,31 @@ public function get_sqlite_version(): string { return $this->pdo->query( 'SELECT SQLITE_VERSION()' )->fetchColumn(); } + /** + * Get the SQLite driver version saved in the database. + * + * The saved driver version corresponds to the latest version of the SQLite + * driver that was used to initialize and configure the SQLite database. + * + * @return string SQLite driver version as a string. + * @throws PDOException When the query execution fails. + */ + public function get_saved_driver_version(): string { + $default_version = '0.0.0'; + try { + $stmt = $this->execute_sqlite_query( + sprintf( 'SELECT value FROM %s WHERE name = ?', self::GLOBAL_VARIABLES_TABLE_NAME ), + array( self::DRIVER_VERSION_VARIABLE_NAME ) + ); + return $stmt->fetchColumn() ?? $default_version; + } catch ( PDOException $e ) { + if ( str_contains( $e->getMessage(), 'no such table' ) ) { + return $default_version; + } + throw $e; + } + } + /** * Check if a specific SQL mode is active. * diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/wp-includes/sqlite/class-wp-sqlite-db.php index 914e430..4526c50 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/wp-includes/sqlite/class-wp-sqlite-db.php @@ -300,6 +300,7 @@ public function db_connect( $allow_bail = true ) { require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-token.php'; require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-lexer.php'; require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-parser.php'; + require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-configurator.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php'; From 12d30633016489bcebec88b9f64b92c6fbd8fcdf Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 15 Apr 2025 17:10:18 +0200 Subject: [PATCH 02/36] Implement more robust plugin version definition and CI check --- .github/workflows/verify-version.yml | 35 ++++++++++++++++++++++++++++ load.php | 7 +++++- tests/bootstrap.php | 7 +----- version.php | 8 +++++++ wp-includes/sqlite/db.php | 6 +++++ 5 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/verify-version.yml create mode 100644 version.php diff --git a/.github/workflows/verify-version.yml b/.github/workflows/verify-version.yml new file mode 100644 index 0000000..a405c34 --- /dev/null +++ b/.github/workflows/verify-version.yml @@ -0,0 +1,35 @@ +name: Verify plugin version + +on: + push: + branches: + - main + pull_request: + +jobs: + verify-version: + name: Verify plugin version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Extract version from "load.php" + id: load_version + run: | + VERSION=$(grep "Version:" load.php | sed "s/.*Version: \([^ ]*\).*/\1/") + echo "load_version=$VERSION" >> $GITHUB_OUTPUT + + - name: Extract version from "version.php" + id: const_version + run: | + VERSION=$(php -r "require 'version.php'; echo SQLITE_DRIVER_VERSION;") + echo "const_version=$VERSION" >> $GITHUB_OUTPUT + + - name: Compare versions + run: | + if [ "${{ steps.load_version.outputs.load_version }}" != "${{ steps.const_version.outputs.const_version }}" ]; then + echo "Version mismatch detected!" + echo " load.php version: ${{ steps.load_version.outputs.load_version }}" + echo " version.php constant: ${{ steps.const_version.outputs.const_version }}" + exit 1 + fi diff --git a/load.php b/load.php index d2b647d..b041160 100644 --- a/load.php +++ b/load.php @@ -12,7 +12,12 @@ * @package wp-sqlite-integration */ -define( 'SQLITE_DRIVER_VERSION', '2.1.17-alpha' ); +/** + * Load the "SQLITE_DRIVER_VERSION" constant. + * This constant needs to be updated whenever the plugin version changes! + */ +require_once __DIR__ . '/version.php'; + define( 'SQLITE_MAIN_FILE', __FILE__ ); require_once __DIR__ . '/php-polyfills.php'; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index a553fd1..b3aa234 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,6 +1,7 @@ Date: Wed, 16 Apr 2025 09:27:01 +0200 Subject: [PATCH 03/36] Load AST classes for tests in test bootstrap --- tests/WP_SQLite_Driver_Metadata_Tests.php | 5 ----- tests/WP_SQLite_Driver_Query_Tests.php | 5 ----- tests/WP_SQLite_Driver_Tests.php | 6 ------ tests/WP_SQLite_Driver_Translation_Tests.php | 5 ----- tests/bootstrap.php | 4 ++++ 5 files changed, 4 insertions(+), 21 deletions(-) diff --git a/tests/WP_SQLite_Driver_Metadata_Tests.php b/tests/WP_SQLite_Driver_Metadata_Tests.php index 65bd157..fde9968 100644 --- a/tests/WP_SQLite_Driver_Metadata_Tests.php +++ b/tests/WP_SQLite_Driver_Metadata_Tests.php @@ -1,10 +1,5 @@ Date: Wed, 16 Apr 2025 14:34:51 +0200 Subject: [PATCH 04/36] Implement information schema reconstructor --- ...Information_Schema_Reconstructor_Tests.php | 110 +++++ tests/bootstrap.php | 1 + tests/tools/dump-sqlite-query.php | 1 + .../class-wp-sqlite-configurator.php | 16 +- .../sqlite-ast/class-wp-sqlite-driver.php | 30 +- ...qlite-information-schema-reconstructor.php | 381 ++++++++++++++++++ wp-includes/sqlite/class-wp-sqlite-db.php | 1 + 7 files changed, 529 insertions(+), 11 deletions(-) create mode 100644 tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php create mode 100644 wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php new file mode 100644 index 0000000..baddc81 --- /dev/null +++ b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -0,0 +1,110 @@ +suppress_errors = false; + $GLOBALS['wpdb']->show_errors = true; + } + } + + // Before each test, we create a new database + public function setUp(): void { + $this->sqlite = new PDO( 'sqlite::memory:' ); + $this->engine = new WP_SQLite_Driver( + array( + 'connection' => $this->sqlite, + 'database' => 'wp', + ) + ); + + $builder = new WP_SQLite_Information_Schema_Builder( + 'wp', + WP_SQLite_Driver::RESERVED_PREFIX, + array( $this->engine, 'execute_sqlite_query' ) + ); + + $this->reconstructor = new WP_SQLite_Information_Schema_Reconstructor( + $this->engine, + $builder + ); + } + + public function testReconstructInformationSchemaTable(): void { + $this->engine->get_pdo()->exec( + ' + CREATE TABLE t ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + role TEXT, + score REAL, + priority INTEGER DEFAULT 0, + data BLOB, + UNIQUE (name) + ) + ' + ); + $this->engine->get_pdo()->exec( 'CREATE INDEX idx_score ON t (score)' ); + $this->engine->get_pdo()->exec( 'CREATE INDEX idx_role_score ON t (role, priority)' ); + $result = $this->assertQuery( 'SELECT * FROM information_schema.tables WHERE table_name = "t"' ); + $this->assertEquals( 0, count( $result ) ); + + $this->reconstructor->ensure_correct_information_schema(); + $result = $this->assertQuery( 'SELECT * FROM information_schema.tables WHERE table_name = "t"' ); + $this->assertEquals( 1, count( $result ) ); + + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertSame( + implode( + "\n", + array( + 'CREATE TABLE `t` (', + ' `id` int NOT NULL AUTO_INCREMENT,', + ' `email` text NOT NULL,', + ' `name` text NOT NULL,', + ' `role` text DEFAULT NULL,', + ' `score` float DEFAULT NULL,', + " `priority` int DEFAULT '0',", + ' `data` blob DEFAULT NULL,', + ' PRIMARY KEY (`id`),', + ' KEY `idx_role_score` (`role`(100), `priority`),', + ' KEY `idx_score` (`score`),', + ' UNIQUE KEY `sqlite_autoindex_t_2` (`name`(100)),', + ' UNIQUE KEY `sqlite_autoindex_t_1` (`email`(100))', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci', + ) + ), + $result[0]->{'Create Table'} + ); + } + + private function assertQuery( $sql ) { + $retval = $this->engine->query( $sql ); + $this->assertNotFalse( $retval ); + return $retval; + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 2bf50a8..2f71bb9 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -18,6 +18,7 @@ require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-driver.php'; require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.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-information-schema-reconstructor.php'; /** * Polyfills for WordPress functions diff --git a/tests/tools/dump-sqlite-query.php b/tests/tools/dump-sqlite-query.php index 69daf8c..b0f5453 100644 --- a/tests/tools/dump-sqlite-query.php +++ b/tests/tools/dump-sqlite-query.php @@ -12,6 +12,7 @@ require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.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-information-schema-reconstructor.php'; $driver = new WP_SQLite_Driver( array( diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php b/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php index b7c5a6e..5e0fecc 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php @@ -25,6 +25,13 @@ class WP_SQLite_Configurator { */ private $information_schema_builder; + /** + * A service for reconstructing the MySQL INFORMATION_SCHEMA tables in SQLite. + * + * @var WP_SQLite_Information_Schema_Reconstructor + */ + private $information_schema_reconstructor; + /** * Constructor. * @@ -35,8 +42,12 @@ public function __construct( WP_SQLite_Driver $driver, WP_SQLite_Information_Schema_Builder $information_schema_builder ) { - $this->driver = $driver; - $this->information_schema_builder = $information_schema_builder; + $this->driver = $driver; + $this->information_schema_builder = $information_schema_builder; + $this->information_schema_reconstructor = new WP_SQLite_Information_Schema_Reconstructor( + $driver, + $information_schema_builder + ); } /** @@ -64,6 +75,7 @@ public function ensure_database_configured(): void { public function configure_database(): void { $this->ensure_global_variables_table(); $this->information_schema_builder->ensure_information_schema_tables(); + $this->information_schema_reconstructor->ensure_correct_information_schema(); $this->save_current_driver_version(); } diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index 7b000a8..d7c1a68 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -655,15 +655,7 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo try { // Parse the MySQL query. - $lexer = new WP_MySQL_Lexer( $query ); - $tokens = $lexer->remaining_tokens(); - - $parser = new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); - $ast = $parser->parse(); - - if ( null === $ast ) { - throw $this->new_driver_exception( 'Failed to parse the MySQL query.' ); - } + $ast = $this->parse_query( $query ); // Handle transaction commands. @@ -735,6 +727,26 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo } } + /** + * Parse a MySQL query into an AST. + * + * @param string $query The MySQL query to parse. + * @return WP_Parser_Node The AST representing the parsed query. + * @throws WP_SQLite_Driver_Exception When the query parsing fails. + */ + public function parse_query( string $query ): WP_Parser_Node { + $lexer = new WP_MySQL_Lexer( $query ); + $tokens = $lexer->remaining_tokens(); + + $parser = new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); + $ast = $parser->parse(); + + if ( null === $ast ) { + throw $this->new_driver_exception( 'Failed to parse the MySQL query.' ); + } + return $ast; + } + /** * Get results of the last query. * diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php new file mode 100644 index 0000000..c2b4551 --- /dev/null +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -0,0 +1,381 @@ +driver = $driver; + $this->information_schema_builder = $information_schema_builder; + } + + /** + * Ensure that the MySQL INFORMATION_SCHEMA data in SQLite is correct. + * + * This method checks if the MySQL INFORMATION_SCHEMA data in SQLite is correct, + * and if it is not, it will reconstruct the data. + */ + public function ensure_correct_information_schema(): void { + $tables = $this->get_existing_table_names(); + $information_schema_tables = $this->get_information_schema_table_names(); + + // Reconstruct information schema records for tables that don't have them. + foreach ( $tables as $table ) { + if ( ! in_array( $table, $information_schema_tables, true ) ) { + $sql = $this->generate_create_table_statement( $table ); + $ast = $this->driver->parse_query( $sql ); + $this->information_schema_builder->record_create_table( $ast ); + } + } + + // Remove information schema records for tables that don't exist. + foreach ( $information_schema_tables as $table ) { + if ( ! in_array( $table, $tables, true ) ) { + $sql = sprintf( 'DROP %s', $this->quote_sqlite_identifier( $table ) ); + $ast = $this->driver->parse_query( $sql ); + $this->information_schema_builder->record_drop_table( $ast ); + } + } + } + + /** + * Get the names of all existing tables in the SQLite database. + * + * @return string[] The names of tables in the SQLite database. + */ + private function get_existing_table_names(): array { + $all_tables = $this->driver->execute_sqlite_query( + 'SELECT name FROM sqlite_schema WHERE type = "table" ORDER BY name' + )->fetchAll( PDO::FETCH_COLUMN ); + + // Filter out internal tables. + $tables = array(); + foreach ( $all_tables as $table ) { + if ( str_starts_with( $table, 'sqlite_' ) ) { + continue; + } + if ( str_starts_with( $table, WP_SQLite_Driver::RESERVED_PREFIX ) ) { + continue; + } + $tables[] = $table; + } + return $tables; + } + + /** + * Get the names of all tables recorded in the information schema. + * + * @return string[] The names of tables in the information schema. + */ + private function get_information_schema_table_names(): array { + $tables_table = $this->information_schema_builder->get_table_name( false, 'tables' ); + return $this->driver->execute_sqlite_query( + "SELECT table_name FROM $tables_table ORDER BY table_name" + )->fetchAll( PDO::FETCH_COLUMN ); + } + + /** + * Generate a MySQL CREATE TABLE statement from an SQLite table definition. + * + * This method generates a MySQL CREATE TABLE statement for a given table name. + * It retrieves the column information from the SQLite database and generates + * a CREATE TABLE statement that can be used to create the table in MySQL. + * + * @param string $table_name The name of the table. + * @return string The CREATE TABLE statement. + */ + private function generate_create_table_statement( string $table_name ): string { + // Columns. + $columns = $this->driver->execute_sqlite_query( + sprintf( 'PRAGMA table_xinfo("%s")', $table_name ) + )->fetchAll( PDO::FETCH_ASSOC ); + + $definitions = array(); + $data_types = array(); + foreach ( $columns as $column ) { + $mysql_type = $this->get_cached_mysql_data_type( $table_name, $column['name'] ); + if ( null === $mysql_type ) { + $mysql_type = $this->get_mysql_data_type( $column['type'] ); + } + $definitions[] = $this->get_column_definition( $table_name, $column ); + $data_types[ $column['name'] ] = $mysql_type; + } + + // Primary key. + $pk_columns = array(); + foreach ( $columns as $column ) { + if ( '0' !== $column['pk'] ) { + $pk_columns[ $column['pk'] ] = $column['name']; + } + } + ksort( $pk_columns ); + + if ( count( $pk_columns ) > 0 ) { + $definitions[] = sprintf( + 'PRIMARY KEY (%s)', + implode( ', ', array_map( array( $this, 'quote_sqlite_identifier' ), $pk_columns ) ) + ); + } + + // Indexes and keys. + $keys = $this->driver->execute_sqlite_query( + 'SELECT * FROM pragma_index_list("' . $table_name . '")' + )->fetchAll( PDO::FETCH_ASSOC ); + + foreach ( $keys as $key ) { + $key_columns = $this->driver->execute_sqlite_query( + 'SELECT * FROM pragma_index_info("' . $key['name'] . '")' + )->fetchAll( PDO::FETCH_ASSOC ); + + // If the PK columns are the same as the UK columns, skip the key. + // This is because a primary key is already unique in MySQL. + $key_equals_pk = ! array_diff( $pk_columns, array_column( $key_columns, 'name' ) ); + $is_auto_index = strpos( $key['name'], 'sqlite_autoindex_' ) === 0; + if ( $is_auto_index && $key['unique'] && $key_equals_pk ) { + continue; + } + $definitions[] = $this->get_key_definition( $key, $key_columns, $data_types ); + } + + return sprintf( + "CREATE TABLE %s (\n %s\n)", + $this->quote_sqlite_identifier( $table_name ), + implode( ",\n ", $definitions ) + ); + } + + /** + * Generate a MySQL column definition from an SQLite column information. + * + * This method generates a MySQL column definition from SQLite column data. + * + * @param string $table_name The name of the table. + * @param array $column_info The SQLite column information. + * @return string The MySQL column definition. + */ + private function get_column_definition( string $table_name, array $column_info ): string { + $definition = array(); + $definition[] = $this->quote_sqlite_identifier( $column_info['name'] ); + + // Data type. + $mysql_type = $this->get_cached_mysql_data_type( $table_name, $column_info['name'] ); + if ( null === $mysql_type ) { + $mysql_type = $this->get_mysql_data_type( $column_info['type'] ); + } + $definition[] = $mysql_type; + + // NULL/NOT NULL. + if ( '1' === $column_info['notnull'] ) { + $definition[] = 'NOT NULL'; + } + + // Auto increment. + $is_auto_increment = false; + if ( '0' !== $column_info['pk'] ) { + $is_auto_increment = $this->driver->execute_sqlite_query( + 'SELECT 1 FROM sqlite_schema WHERE tbl_name = ? AND sql LIKE ?', + array( $table_name, '%AUTOINCREMENT%' ) + )->fetchColumn(); + + if ( $is_auto_increment ) { + $definition[] = 'AUTO_INCREMENT'; + } + } + + // Default value. + if ( $this->column_has_default( $mysql_type, $column_info['dflt_value'] ) && ! $is_auto_increment ) { + $definition[] = 'DEFAULT ' . $column_info['dflt_value']; + } + + return implode( ' ', $definition ); + } + + /** + * Generate a MySQL key definition from an SQLite key information. + * + * This method generates a MySQL key definition from SQLite key data. + * + * @param array $key The SQLite key information. + * @param array $key_columns The SQLite key column information. + * @param array $data_types The MySQL data types of the columns. + * @return string The MySQL key definition. + */ + private function get_key_definition( array $key, array $key_columns, array $data_types ): string { + $key_length_limit = 100; + + // Key definition. + $definition = array(); + if ( $key['unique'] ) { + $definition[] = 'UNIQUE'; + } + $definition[] = 'KEY'; + + // Remove the prefix from the index name if there is any. We use __ as a separator. + $index_name = explode( '__', $key['name'], 2 )[1] ?? $key['name']; + $definition[] = $this->quote_sqlite_identifier( $index_name ); + + // Key columns. + $cols = array(); + foreach ( $key_columns as $column ) { + // Get data type and length. + $data_type = strtolower( $data_types[ $column['name'] ] ); + if ( 1 === preg_match( '/^(\w+)\s*\(\s*(\d+)\s*\)/', $data_type, $matches ) ) { + $data_type = $matches[1]; // "varchar" + $data_length = min( $matches[2], $key_length_limit ); // "255" + } + + // Apply max length if needed. + if ( + str_contains( $data_type, 'char' ) + || str_starts_with( $data_type, 'var' ) + || str_ends_with( $data_type, 'text' ) + || str_ends_with( $data_type, 'blob' ) + ) { + $cols[] = sprintf( + '%s(%d)', + $this->quote_sqlite_identifier( $column['name'] ), + $data_length ?? $key_length_limit + ); + } else { + $cols[] = $this->quote_sqlite_identifier( $column['name'] ); + } + } + + $definition[] = '(' . implode( ', ', $cols ) . ')'; + return implode( ' ', $definition ); + } + + /** + * Determine if a column has a default value. + * + * @param string $mysql_type The MySQL data type of the column. + * @param string|null $default_value The default value of the SQLite column. + * @return bool True if the column has a default value, false otherwise. + */ + private function column_has_default( string $mysql_type, ?string $default_value ): bool { + if ( null === $default_value || '' === $default_value ) { + return false; + } + if ( + "''" === $default_value + && in_array( strtolower( $mysql_type ), array( 'datetime', 'date', 'time', 'timestamp', 'year' ), true ) + ) { + return false; + } + return true; + } + + /** + * Get a MySQL column or index data type from legacy data types cache table. + * + * This method retrieves MySQL column or index data types from a special table + * that was used by an old version of the SQLite driver and that is otherwise + * no longer needed. This is more precise than direct inference from SQLite. + * + * @param string $table_name The table name. + * @param string $column_or_index_name The column or index name. + * @return string|null The MySQL definition, or null when not found. + */ + private function get_cached_mysql_data_type( string $table_name, string $column_or_index_name ): ?string { + try { + $mysql_type = $this->driver->execute_sqlite_query( + 'SELECT mysql_type FROM _mysql_data_types_cache WHERE `table` = ? AND column_or_index = ?', + array( $table_name, $column_or_index_name ) + )->fetchColumn(); + } catch ( PDOException $e ) { + if ( str_contains( $e->getMessage(), 'no such table' ) ) { + return null; + } + throw $e; + } + if ( str_ends_with( $mysql_type, ' KEY' ) ) { + $mysql_type = substr( $mysql_type, 0, strlen( $mysql_type ) - strlen( ' KEY' ) ); + } + return $mysql_type; + } + + /** + * Get a MySQL column type from an SQLite column type. + * + * This method converts an SQLite column type to a MySQL column type as per + * the SQLite column type affinity rules: + * https://sqlite.org/datatype3.html#determination_of_column_affinity + * + * @param string $column_type The SQLite column type. + * @return string The MySQL column type. + */ + private function get_mysql_data_type( string $column_type ): string { + $type = strtoupper( $column_type ); + if ( str_contains( $type, 'INT' ) ) { + return 'int'; + } + if ( str_contains( $type, 'TEXT' ) || str_contains( $type, 'CHAR' ) || str_contains( $type, 'CLOB' ) ) { + return 'text'; + } + if ( str_contains( $type, 'BLOB' ) || '' === $type ) { + return 'blob'; + } + if ( str_contains( $type, 'REAL' ) || str_contains( $type, 'FLOA' ) ) { + return 'float'; + } + if ( str_contains( $type, 'DOUB' ) ) { + return 'double'; + } + + /** + * While SQLite defaults to a NUMERIC column affinity, it's better to use + * TEXT in this case, because numeric SQLite columns in non-strict tables + * can contain any text data as well, when it is not a well-formed number. + * + * See: https://sqlite.org/datatype3.html#type_affinity + */ + return 'text'; + } + + /** + * Quote an SQLite identifier. + * + * Wrap the identifier in backticks and escape backtick values within. + * + * @param string $unquoted_identifier The unquoted identifier value. + * @return string The quoted identifier value. + */ + private function quote_sqlite_identifier( string $unquoted_identifier ): string { + return '`' . str_replace( '`', '``', $unquoted_identifier ) . '`'; + } +} diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/wp-includes/sqlite/class-wp-sqlite-db.php index 4526c50..3dc28ac 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/wp-includes/sqlite/class-wp-sqlite-db.php @@ -304,6 +304,7 @@ public function db_connect( $allow_bail = true ) { require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.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-information-schema-reconstructor.php'; $this->ensure_database_directory( FQDB ); try { From 23331b36c3b0753478318968fbf9c80b382475d1 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Thu, 17 Apr 2025 11:56:34 +0200 Subject: [PATCH 05/36] Reconstruct WordPress tables using "wp_get_db_schema" function --- ...Information_Schema_Reconstructor_Tests.php | 68 +++++++++++++++++++ ...qlite-information-schema-reconstructor.php | 48 ++++++++++++- 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php index baddc81..9609452 100644 --- a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php +++ b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -29,6 +29,23 @@ public static function setUpBeforeClass(): void { $GLOBALS['wpdb']->suppress_errors = false; $GLOBALS['wpdb']->show_errors = true; } + + // Mock symols that are used for WordPress table reconstruction. + if ( ! defined( 'ABSPATH' ) ) { + define( 'ABSPATH', __DIR__ ); + } + if ( ! function_exists( 'wp_installing' ) ) { + function wp_installing() { + return false; + } + } + if ( ! function_exists( 'wp_get_db_schema' ) ) { + function wp_get_db_schema() { + // Output from "wp_get_db_schema" as of WordPress 6.8.0. + // See: https://github.com/WordPress/wordpress-develop/blob/6.8.0/src/wp-admin/includes/schema.php#L36 + return "CREATE TABLE wp_users ( ID bigint(20) unsigned NOT NULL auto_increment, user_login varchar(60) NOT NULL default '', user_pass varchar(255) NOT NULL default '', user_nicename varchar(50) NOT NULL default '', user_email varchar(100) NOT NULL default '', user_url varchar(100) NOT NULL default '', user_registered datetime NOT NULL default '0000-00-00 00:00:00', user_activation_key varchar(255) NOT NULL default '', user_status int(11) NOT NULL default '0', display_name varchar(250) NOT NULL default '', PRIMARY KEY (ID), KEY user_login_key (user_login), KEY user_nicename (user_nicename), KEY user_email (user_email) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_usermeta ( umeta_id bigint(20) unsigned NOT NULL auto_increment, user_id bigint(20) unsigned NOT NULL default '0', meta_key varchar(255) default NULL, meta_value longtext, PRIMARY KEY (umeta_id), KEY user_id (user_id), KEY meta_key (meta_key(191)) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_termmeta ( meta_id bigint(20) unsigned NOT NULL auto_increment, term_id bigint(20) unsigned NOT NULL default '0', meta_key varchar(255) default NULL, meta_value longtext, PRIMARY KEY (meta_id), KEY term_id (term_id), KEY meta_key (meta_key(191)) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_terms ( term_id bigint(20) unsigned NOT NULL auto_increment, name varchar(200) NOT NULL default '', slug varchar(200) NOT NULL default '', term_group bigint(10) NOT NULL default 0, PRIMARY KEY (term_id), KEY slug (slug(191)), KEY name (name(191)) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_term_taxonomy ( term_taxonomy_id bigint(20) unsigned NOT NULL auto_increment, term_id bigint(20) unsigned NOT NULL default 0, taxonomy varchar(32) NOT NULL default '', description longtext NOT NULL, parent bigint(20) unsigned NOT NULL default 0, count bigint(20) NOT NULL default 0, PRIMARY KEY (term_taxonomy_id), UNIQUE KEY term_id_taxonomy (term_id,taxonomy), KEY taxonomy (taxonomy) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_term_relationships ( object_id bigint(20) unsigned NOT NULL default 0, term_taxonomy_id bigint(20) unsigned NOT NULL default 0, term_order int(11) NOT NULL default 0, PRIMARY KEY (object_id,term_taxonomy_id), KEY term_taxonomy_id (term_taxonomy_id) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_commentmeta ( meta_id bigint(20) unsigned NOT NULL auto_increment, comment_id bigint(20) unsigned NOT NULL default '0', meta_key varchar(255) default NULL, meta_value longtext, PRIMARY KEY (meta_id), KEY comment_id (comment_id), KEY meta_key (meta_key(191)) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_comments ( comment_ID bigint(20) unsigned NOT NULL auto_increment, comment_post_ID bigint(20) unsigned NOT NULL default '0', comment_author tinytext NOT NULL, comment_author_email varchar(100) NOT NULL default '', comment_author_url varchar(200) NOT NULL default '', comment_author_IP varchar(100) NOT NULL default '', comment_date datetime NOT NULL default '0000-00-00 00:00:00', comment_date_gmt datetime NOT NULL default '0000-00-00 00:00:00', comment_content text NOT NULL, comment_karma int(11) NOT NULL default '0', comment_approved varchar(20) NOT NULL default '1', comment_agent varchar(255) NOT NULL default '', comment_type varchar(20) NOT NULL default 'comment', comment_parent bigint(20) unsigned NOT NULL default '0', user_id bigint(20) unsigned NOT NULL default '0', PRIMARY KEY (comment_ID), KEY comment_post_ID (comment_post_ID), KEY comment_approved_date_gmt (comment_approved,comment_date_gmt), KEY comment_date_gmt (comment_date_gmt), KEY comment_parent (comment_parent), KEY comment_author_email (comment_author_email(10)) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_links ( link_id bigint(20) unsigned NOT NULL auto_increment, link_url varchar(255) NOT NULL default '', link_name varchar(255) NOT NULL default '', link_image varchar(255) NOT NULL default '', link_target varchar(25) NOT NULL default '', link_description varchar(255) NOT NULL default '', link_visible varchar(20) NOT NULL default 'Y', link_owner bigint(20) unsigned NOT NULL default '1', link_rating int(11) NOT NULL default '0', link_updated datetime NOT NULL default '0000-00-00 00:00:00', link_rel varchar(255) NOT NULL default '', link_notes mediumtext NOT NULL, link_rss varchar(255) NOT NULL default '', PRIMARY KEY (link_id), KEY link_visible (link_visible) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_options ( option_id bigint(20) unsigned NOT NULL auto_increment, option_name varchar(191) NOT NULL default '', option_value longtext NOT NULL, autoload varchar(20) NOT NULL default 'yes', PRIMARY KEY (option_id), UNIQUE KEY option_name (option_name), KEY autoload (autoload) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_postmeta ( meta_id bigint(20) unsigned NOT NULL auto_increment, post_id bigint(20) unsigned NOT NULL default '0', meta_key varchar(255) default NULL, meta_value longtext, PRIMARY KEY (meta_id), KEY post_id (post_id), KEY meta_key (meta_key(191)) ) DEFAULT CHARACTER SET utf8mb4; CREATE TABLE wp_posts ( ID bigint(20) unsigned NOT NULL auto_increment, post_author bigint(20) unsigned NOT NULL default '0', post_date datetime NOT NULL default '0000-00-00 00:00:00', post_date_gmt datetime NOT NULL default '0000-00-00 00:00:00', post_content longtext NOT NULL, post_title text NOT NULL, post_excerpt text NOT NULL, post_status varchar(20) NOT NULL default 'publish', comment_status varchar(20) NOT NULL default 'open', ping_status varchar(20) NOT NULL default 'open', post_password varchar(255) NOT NULL default '', post_name varchar(200) NOT NULL default '', to_ping text NOT NULL, pinged text NOT NULL, post_modified datetime NOT NULL default '0000-00-00 00:00:00', post_modified_gmt datetime NOT NULL default '0000-00-00 00:00:00', post_content_filtered longtext NOT NULL, post_parent bigint(20) unsigned NOT NULL default '0', guid varchar(255) NOT NULL default '', menu_order int(11) NOT NULL default '0', post_type varchar(20) NOT NULL default 'post', post_mime_type varchar(100) NOT NULL default '', comment_count bigint(20) NOT NULL default '0', PRIMARY KEY (ID), KEY post_name (post_name(191)), KEY type_status_date (post_type,post_status,post_date,ID), KEY post_parent (post_parent), KEY post_author (post_author) ) DEFAULT CHARACTER SET utf8mb4;"; + } + } } // Before each test, we create a new database @@ -102,6 +119,57 @@ public function testReconstructInformationSchemaTable(): void { ); } + public function testReconstructInformationSchemaTableWithWpTables(): void { + // Create a WP table with any columns. + $this->engine->get_pdo()->exec( 'CREATE TABLE wp_posts ( id INTEGER )' ); + + // Reconstruct the information schema. + $this->reconstructor->ensure_correct_information_schema(); + $result = $this->assertQuery( 'SELECT * FROM information_schema.tables WHERE table_name = "wp_posts"' ); + $this->assertEquals( 1, count( $result ) ); + + // The reconstructed schema should correspond to the original WP table definition. + $result = $this->assertQuery( 'SHOW CREATE TABLE wp_posts' ); + $this->assertSame( + implode( + "\n", + array( + 'CREATE TABLE `wp_posts` (', + ' `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,', + " `post_author` bigint(20) unsigned NOT NULL DEFAULT '0',", + " `post_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',", + " `post_date_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',", + ' `post_content` longtext NOT NULL,', + ' `post_title` text NOT NULL,', + ' `post_excerpt` text NOT NULL,', + " `post_status` varchar(20) NOT NULL DEFAULT 'publish',", + " `comment_status` varchar(20) NOT NULL DEFAULT 'open',", + " `ping_status` varchar(20) NOT NULL DEFAULT 'open',", + " `post_password` varchar(255) NOT NULL DEFAULT '',", + " `post_name` varchar(200) NOT NULL DEFAULT '',", + ' `to_ping` text NOT NULL,', + ' `pinged` text NOT NULL,', + " `post_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',", + " `post_modified_gmt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',", + ' `post_content_filtered` longtext NOT NULL,', + " `post_parent` bigint(20) unsigned NOT NULL DEFAULT '0',", + " `guid` varchar(255) NOT NULL DEFAULT '',", + " `menu_order` int(11) NOT NULL DEFAULT '0',", + " `post_type` varchar(20) NOT NULL DEFAULT 'post',", + " `post_mime_type` varchar(100) NOT NULL DEFAULT '',", + " `comment_count` bigint(20) NOT NULL DEFAULT '0',", + ' PRIMARY KEY (`ID`),', + ' KEY `post_name` (`post_name`(191)),', + ' KEY `type_status_date` (`post_type`, `post_status`, `post_date`, `ID`),', + ' KEY `post_parent` (`post_parent`),', + ' KEY `post_author` (`post_author`)', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci', + ) + ), + $result[0]->{'Create Table'} + ); + } + private function assertQuery( $sql ) { $retval = $this->engine->query( $sql ); $this->assertNotFalse( $retval ); diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index c2b4551..5d7cbc3 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -54,10 +54,38 @@ public function ensure_correct_information_schema(): void { $tables = $this->get_existing_table_names(); $information_schema_tables = $this->get_information_schema_table_names(); + // In WordPress, use "wp_get_db_schema()" to reconstruct WordPress tables. + $wp_tables = array(); + if ( defined( 'ABSPATH' ) ) { + if ( wp_installing() ) { + // Avoid interfering with WordPress installation. + return; + } + if ( file_exists( ABSPATH . 'wp-admin/includes/schema.php' ) ) { + require_once ABSPATH . 'wp-admin/includes/schema.php'; + } + if ( function_exists( 'wp_get_db_schema' ) ) { + $schema = wp_get_db_schema(); + $parts = preg_split( '/(CREATE\s+TABLE)/', $schema, -1, PREG_SPLIT_NO_EMPTY ); + foreach ( $parts as $part ) { + $name = $this->unquote_mysql_identifier( + preg_split( '/\s+/', $part, 2, PREG_SPLIT_NO_EMPTY )[0] + ); + $wp_tables[ $name ] = 'CREATE TABLE' . $part; + } + } + } + // Reconstruct information schema records for tables that don't have them. foreach ( $tables as $table ) { if ( ! in_array( $table, $information_schema_tables, true ) ) { - $sql = $this->generate_create_table_statement( $table ); + if ( isset( $wp_tables[ $table ] ) ) { + // WordPress table. + $sql = $wp_tables[ $table ]; + } else { + // Non-WordPress table. + $sql = $this->generate_create_table_statement( $table ); + } $ast = $this->driver->parse_query( $sql ); $this->information_schema_builder->record_create_table( $ast ); } @@ -378,4 +406,22 @@ private function get_mysql_data_type( string $column_type ): string { private function quote_sqlite_identifier( string $unquoted_identifier ): string { return '`' . str_replace( '`', '``', $unquoted_identifier ) . '`'; } + + /** + * Unquote a quoted MySQL identifier. + * + * Remove bounding quotes and replace escaped quotes with their values. + * + * @param string $quoted_identifier The quoted identifier value. + * @return string The unquoted identifier value. + */ + private function unquote_mysql_identifier( string $quoted_identifier ): string { + $first_byte = $quoted_identifier[0] ?? null; + if ( '"' === $first_byte || '`' === $first_byte ) { + $unquoted = substr( $quoted_identifier, 1, -1 ); + } else { + $unquoted = $quoted_identifier; + } + return str_replace( $first_byte . $first_byte, $first_byte, $unquoted ); + } } From ed959fb299654f7455a4b11d565f7ae48f63e568 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Thu, 17 Apr 2025 15:42:34 +0200 Subject: [PATCH 06/36] Avoid race conditions when configuring the SQLite database --- .../class-wp-sqlite-configurator.php | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php b/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php index 5e0fecc..4cc9477 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php @@ -57,11 +57,20 @@ public function __construct( * driver version, and if it is not, it will configure the database. */ public function ensure_database_configured(): void { - $version = SQLITE_DRIVER_VERSION; - $db_version = $this->driver->get_saved_driver_version(); - if ( version_compare( $version, $db_version ) > 0 ) { - $this->configure_database(); + // Use an EXCLUSIVE transaction to prevent multiple connections + // from attempting to configure the database at the same time. + $this->driver->execute_sqlite_query( 'BEGIN EXCLUSIVE TRANSACTION' ); + try { + $version = SQLITE_DRIVER_VERSION; + $db_version = $this->driver->get_saved_driver_version(); + if ( version_compare( $version, $db_version ) > 0 ) { + $this->run_database_configuration(); + } + } catch ( Throwable $e ) { + $this->driver->execute_sqlite_query( 'ROLLBACK' ); + throw $e; } + $this->driver->execute_sqlite_query( 'COMMIT' ); } /** @@ -73,6 +82,25 @@ public function ensure_database_configured(): void { * SQLite driver version and attempt to repair any configuration corruption. */ public function configure_database(): void { + // Use an EXCLUSIVE transaction to prevent multiple connections + // from attempting to configure the database at the same time. + $this->driver->execute_sqlite_query( 'BEGIN EXCLUSIVE TRANSACTION' ); + try { + $this->run_database_configuration(); + } catch ( Throwable $e ) { + $this->driver->execute_sqlite_query( 'ROLLBACK' ); + throw $e; + } + $this->driver->execute_sqlite_query( 'COMMIT' ); + } + + /** + * Run the SQLite database configuration. + * + * This method executes the database configuration steps, ensuring that all + * tables required for MySQL emulation in SQLite are created and populated. + */ + private function run_database_configuration(): void { $this->ensure_global_variables_table(); $this->information_schema_builder->ensure_information_schema_tables(); $this->information_schema_reconstructor->ensure_correct_information_schema(); From 287f4f43fc995cf4b69586e33a24f6783a11edb1 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 22 Apr 2025 10:13:13 +0200 Subject: [PATCH 07/36] Improve docs --- .github/workflows/verify-version.yml | 2 +- load.php | 2 +- .../WP_SQLite_Information_Schema_Reconstructor_Tests.php | 5 +---- version.php | 2 +- .../class-wp-sqlite-information-schema-reconstructor.php | 8 ++------ wp-includes/sqlite/db.php | 1 - 6 files changed, 6 insertions(+), 14 deletions(-) diff --git a/.github/workflows/verify-version.yml b/.github/workflows/verify-version.yml index a405c34..c3ff28e 100644 --- a/.github/workflows/verify-version.yml +++ b/.github/workflows/verify-version.yml @@ -8,7 +8,7 @@ on: jobs: verify-version: - name: Verify plugin version + name: Verify that plugin version matches SQLITE_DRIVER_VERSION constant runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/load.php b/load.php index b041160..c9c95e1 100644 --- a/load.php +++ b/load.php @@ -14,7 +14,7 @@ /** * Load the "SQLITE_DRIVER_VERSION" constant. - * This constant needs to be updated whenever the plugin version changes! + * This constant needs to be updated on plugin release! */ require_once __DIR__ . '/version.php'; diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php index 9609452..892e71a 100644 --- a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php +++ b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -13,9 +13,6 @@ class WP_SQLite_Information_Schema_Reconstructor_Tests extends TestCase { private $sqlite; public static function setUpBeforeClass(): void { - // if ( ! defined( 'PDO_DEBUG' )) { - // define( 'PDO_DEBUG', true ); - // } if ( ! defined( 'FQDB' ) ) { define( 'FQDB', ':memory:' ); define( 'FQDBDIR', __DIR__ . '/../testdb' ); @@ -30,7 +27,7 @@ public static function setUpBeforeClass(): void { $GLOBALS['wpdb']->show_errors = true; } - // Mock symols that are used for WordPress table reconstruction. + // Mock symbols that are used for WordPress table reconstruction. if ( ! defined( 'ABSPATH' ) ) { define( 'ABSPATH', __DIR__ ); } diff --git a/version.php b/version.php index baa809f..b933657 100644 --- a/version.php +++ b/version.php @@ -3,6 +3,6 @@ /** * The version of the SQLite driver. * - * This constant needs to be updated whenever the plugin version changes! + * This constant needs to be updated on plugin release! */ define( 'SQLITE_DRIVER_VERSION', '2.1.17-alpha' ); diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 5d7cbc3..9dd0378 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -80,10 +80,10 @@ public function ensure_correct_information_schema(): void { foreach ( $tables as $table ) { if ( ! in_array( $table, $information_schema_tables, true ) ) { if ( isset( $wp_tables[ $table ] ) ) { - // WordPress table. + // WordPress core table (as returned by "wp_get_db_schema()"). $sql = $wp_tables[ $table ]; } else { - // Non-WordPress table. + // Other table (a WordPress plugin or unrelated to WordPress). $sql = $this->generate_create_table_statement( $table ); } $ast = $this->driver->parse_query( $sql ); @@ -140,10 +140,6 @@ private function get_information_schema_table_names(): array { /** * Generate a MySQL CREATE TABLE statement from an SQLite table definition. * - * This method generates a MySQL CREATE TABLE statement for a given table name. - * It retrieves the column information from the SQLite database and generates - * a CREATE TABLE statement that can be used to create the table in MySQL. - * * @param string $table_name The name of the table. * @return string The CREATE TABLE statement. */ diff --git a/wp-includes/sqlite/db.php b/wp-includes/sqlite/db.php index ba53174..bb00c33 100644 --- a/wp-includes/sqlite/db.php +++ b/wp-includes/sqlite/db.php @@ -8,7 +8,6 @@ /** * Load the "SQLITE_DRIVER_VERSION" constant. - * This constant needs to be updated whenever the plugin version changes! */ require_once dirname( __DIR__, 2 ) . '/version.php'; From 907be69765fc6e91b396f577c425c0804bc4836d Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 22 Apr 2025 10:19:38 +0200 Subject: [PATCH 08/36] Fix identifier unquoting --- wp-includes/sqlite-ast/class-wp-sqlite-driver.php | 5 ++--- .../class-wp-sqlite-information-schema-reconstructor.php | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index d7c1a68..6c55f53 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -3537,10 +3537,9 @@ private function unquote_sqlite_identifier( string $quoted_identifier ): string $first_byte = $quoted_identifier[0] ?? null; if ( '"' === $first_byte || '`' === $first_byte ) { $unquoted = substr( $quoted_identifier, 1, -1 ); - } else { - $unquoted = $quoted_identifier; + return str_replace( $first_byte . $first_byte, $first_byte, $unquoted ); } - return str_replace( $first_byte . $first_byte, $first_byte, $unquoted ); + return $quoted_identifier; } /** diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 9dd0378..5541477 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -415,9 +415,8 @@ private function unquote_mysql_identifier( string $quoted_identifier ): string { $first_byte = $quoted_identifier[0] ?? null; if ( '"' === $first_byte || '`' === $first_byte ) { $unquoted = substr( $quoted_identifier, 1, -1 ); - } else { - $unquoted = $quoted_identifier; + return str_replace( $first_byte . $first_byte, $first_byte, $unquoted ); } - return str_replace( $first_byte . $first_byte, $first_byte, $unquoted ); + return $quoted_identifier; } } From b178b9709d0d3b7f89f771039066cba93506dcae Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 22 Apr 2025 10:26:38 +0200 Subject: [PATCH 09/36] In WordPress, make sure the "wp_get_db_schema()" function is defined --- ...qlite-information-schema-reconstructor.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 5541477..54963f9 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -64,15 +64,16 @@ public function ensure_correct_information_schema(): void { if ( file_exists( ABSPATH . 'wp-admin/includes/schema.php' ) ) { require_once ABSPATH . 'wp-admin/includes/schema.php'; } - if ( function_exists( 'wp_get_db_schema' ) ) { - $schema = wp_get_db_schema(); - $parts = preg_split( '/(CREATE\s+TABLE)/', $schema, -1, PREG_SPLIT_NO_EMPTY ); - foreach ( $parts as $part ) { - $name = $this->unquote_mysql_identifier( - preg_split( '/\s+/', $part, 2, PREG_SPLIT_NO_EMPTY )[0] - ); - $wp_tables[ $name ] = 'CREATE TABLE' . $part; - } + if ( ! function_exists( 'wp_get_db_schema' ) ) { + throw new Exception( 'The "wp_get_db_schema()" function was not defined.' ); + } + $schema = wp_get_db_schema(); + $parts = preg_split( '/(CREATE\s+TABLE)/', $schema, -1, PREG_SPLIT_NO_EMPTY ); + foreach ( $parts as $part ) { + $name = $this->unquote_mysql_identifier( + preg_split( '/\s+/', $part, 2, PREG_SPLIT_NO_EMPTY )[0] + ); + $wp_tables[ $name ] = 'CREATE TABLE' . $part; } } From 5edd7f72aebfcd1c0aac43589aa1eb0d2b64b71b Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 22 Apr 2025 13:59:52 +0200 Subject: [PATCH 10/36] Filter out reserved tables using LIKE --- ...qlite-information-schema-reconstructor.php | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 54963f9..93b97a8 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -108,22 +108,20 @@ public function ensure_correct_information_schema(): void { * @return string[] The names of tables in the SQLite database. */ private function get_existing_table_names(): array { - $all_tables = $this->driver->execute_sqlite_query( - 'SELECT name FROM sqlite_schema WHERE type = "table" ORDER BY name' + return $this->driver->execute_sqlite_query( + " + SELECT name + FROM sqlite_schema + WHERE type = 'table' + AND name NOT LIKE ? ESCAPE '\' + AND name NOT LIKE ? ESCAPE '\' + ORDER BY name + ", + array( + 'sqlite\_%', + str_replace( '_', '\_', WP_SQLite_Driver::RESERVED_PREFIX ) . '%', + ) )->fetchAll( PDO::FETCH_COLUMN ); - - // Filter out internal tables. - $tables = array(); - foreach ( $all_tables as $table ) { - if ( str_starts_with( $table, 'sqlite_' ) ) { - continue; - } - if ( str_starts_with( $table, WP_SQLite_Driver::RESERVED_PREFIX ) ) { - continue; - } - $tables[] = $table; - } - return $tables; } /** From 5dddd50e7376bf94e3eade631a7945f9bdee535a Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 22 Apr 2025 14:05:05 +0200 Subject: [PATCH 11/36] Improve the clarity of primary key reconstruction --- ...sqlite-information-schema-reconstructor.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 93b97a8..042103d 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -162,17 +162,23 @@ private function generate_create_table_statement( string $table_name ): string { // Primary key. $pk_columns = array(); foreach ( $columns as $column ) { - if ( '0' !== $column['pk'] ) { - $pk_columns[ $column['pk'] ] = $column['name']; + // A position of the column in the primary key, starting from index 1. + // A value of 0 means that the column is not part of the primary key. + $pk_position = (int) $column['pk']; + if ( 0 !== $pk_position ) { + $pk_columns[ $pk_position ] = $column['name']; } } + + // Sort the columns by their position in the primary key. ksort( $pk_columns ); if ( count( $pk_columns ) > 0 ) { - $definitions[] = sprintf( - 'PRIMARY KEY (%s)', - implode( ', ', array_map( array( $this, 'quote_sqlite_identifier' ), $pk_columns ) ) - ); + $quoted_pk_columns = array(); + foreach ( $pk_columns as $pk_column ) { + $quoted_pk_columns[] = $this->quote_sqlite_identifier( $pk_column ); + } + $definitions[] = sprintf( 'PRIMARY KEY (%s)', implode( ', ', $quoted_pk_columns ) ); } // Indexes and keys. From 95b1a198e74252768332052385e43b914cb5ada9 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 22 Apr 2025 15:37:21 +0200 Subject: [PATCH 12/36] Document SQLite column affinity inline --- ...qlite-information-schema-reconstructor.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 042103d..6033e25 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -370,15 +370,32 @@ private function get_cached_mysql_data_type( string $table_name, string $column_ */ private function get_mysql_data_type( string $column_type ): string { $type = strtoupper( $column_type ); + + /* + * Following the rules of column affinity: + * https://sqlite.org/datatype3.html#determination_of_column_affinity + */ + + // 1. If the declared type contains the string "INT" then it is assigned + // INTEGER affinity. if ( str_contains( $type, 'INT' ) ) { return 'int'; } + + // 2. If the declared type of the column contains any of the strings + // "CHAR", "CLOB", or "TEXT" then that column has TEXT affinity. if ( str_contains( $type, 'TEXT' ) || str_contains( $type, 'CHAR' ) || str_contains( $type, 'CLOB' ) ) { return 'text'; } + + // 3. If the declared type for a column contains the string "BLOB" or + // if no type is specified then the column has affinity BLOB. if ( str_contains( $type, 'BLOB' ) || '' === $type ) { return 'blob'; } + + // 4. If the declared type for a column contains any of the strings + // "REAL", "FLOA", or "DOUB" then the column has REAL affinity. if ( str_contains( $type, 'REAL' ) || str_contains( $type, 'FLOA' ) ) { return 'float'; } @@ -387,6 +404,8 @@ private function get_mysql_data_type( string $column_type ): string { } /** + * 5. Otherwise, the affinity is NUMERIC. + * * While SQLite defaults to a NUMERIC column affinity, it's better to use * TEXT in this case, because numeric SQLite columns in non-strict tables * can contain any text data as well, when it is not a well-formed number. From 51b22fa12669e78a6403e08c54da0e8823df0bc8 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 22 Apr 2025 15:41:23 +0200 Subject: [PATCH 13/36] Do not acquire an exclusive lock for every version check, lock only configuration --- .../class-wp-sqlite-configurator.php | 35 +++++-------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php b/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php index 4cc9477..072e2e4 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php @@ -57,20 +57,11 @@ public function __construct( * driver version, and if it is not, it will configure the database. */ public function ensure_database_configured(): void { - // Use an EXCLUSIVE transaction to prevent multiple connections - // from attempting to configure the database at the same time. - $this->driver->execute_sqlite_query( 'BEGIN EXCLUSIVE TRANSACTION' ); - try { - $version = SQLITE_DRIVER_VERSION; - $db_version = $this->driver->get_saved_driver_version(); - if ( version_compare( $version, $db_version ) > 0 ) { - $this->run_database_configuration(); - } - } catch ( Throwable $e ) { - $this->driver->execute_sqlite_query( 'ROLLBACK' ); - throw $e; + $version = SQLITE_DRIVER_VERSION; + $db_version = $this->driver->get_saved_driver_version(); + if ( version_compare( $version, $db_version ) > 0 ) { + $this->configure_database(); } - $this->driver->execute_sqlite_query( 'COMMIT' ); } /** @@ -86,7 +77,10 @@ public function configure_database(): void { // from attempting to configure the database at the same time. $this->driver->execute_sqlite_query( 'BEGIN EXCLUSIVE TRANSACTION' ); try { - $this->run_database_configuration(); + $this->ensure_global_variables_table(); + $this->information_schema_builder->ensure_information_schema_tables(); + $this->information_schema_reconstructor->ensure_correct_information_schema(); + $this->save_current_driver_version(); } catch ( Throwable $e ) { $this->driver->execute_sqlite_query( 'ROLLBACK' ); throw $e; @@ -94,19 +88,6 @@ public function configure_database(): void { $this->driver->execute_sqlite_query( 'COMMIT' ); } - /** - * Run the SQLite database configuration. - * - * This method executes the database configuration steps, ensuring that all - * tables required for MySQL emulation in SQLite are created and populated. - */ - private function run_database_configuration(): void { - $this->ensure_global_variables_table(); - $this->information_schema_builder->ensure_information_schema_tables(); - $this->information_schema_reconstructor->ensure_correct_information_schema(); - $this->save_current_driver_version(); - } - /** * Ensure that the global variables table exists. * From d9040642417e10bd999dd6cdcd470cc122fa4f0f Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Wed, 23 Apr 2025 10:29:50 +0200 Subject: [PATCH 14/36] Fix $wpdb dependency loop when using schema reconstructor in WP --- ...-wp-sqlite-information-schema-reconstructor.php | 4 ---- wp-includes/sqlite/class-wp-sqlite-db.php | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 6033e25..de6e92e 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -57,10 +57,6 @@ public function ensure_correct_information_schema(): void { // In WordPress, use "wp_get_db_schema()" to reconstruct WordPress tables. $wp_tables = array(); if ( defined( 'ABSPATH' ) ) { - if ( wp_installing() ) { - // Avoid interfering with WordPress installation. - return; - } if ( file_exists( ABSPATH . 'wp-admin/includes/schema.php' ) ) { require_once ABSPATH . 'wp-admin/includes/schema.php'; } diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/wp-includes/sqlite/class-wp-sqlite-db.php index 3dc28ac..c625708 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/wp-includes/sqlite/class-wp-sqlite-db.php @@ -38,6 +38,20 @@ class WP_SQLite_DB extends wpdb { * @param string $dbname Database name. */ public function __construct( $dbname ) { + /** + * We need to initialize the "$wpdb" global early, so that the SQLite + * driver can configure the database. The call stack goes like this: + * + * 1. The "parent::__construct()" call executes "$this->db_connect()". + * 2. The database connection call initializes the SQLite driver. + * 3. The SQLite driver initializes and runs "WP_SQLite_Configurator". + * 4. The configurator uses "WP_SQLite_Information_Schema_Reconstructor", + * which requires "wp-admin/includes/schema.php" when in WordPress. + * 5. The "wp-admin/includes/schema.php" requires the "$wpdb" global, + * which creates a circular dependency. + */ + $GLOBALS['wpdb'] = $this; + parent::__construct( '', '', $dbname, '' ); $this->charset = 'utf8mb4'; } From 69381d8c75f35ee67ca989828640e9c1668889c4 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Wed, 23 Apr 2025 11:34:41 +0200 Subject: [PATCH 15/36] Add support for multi-query parsing --- wp-includes/parser/class-wp-parser.php | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/wp-includes/parser/class-wp-parser.php b/wp-includes/parser/class-wp-parser.php index f266cc7..8592dad 100644 --- a/wp-includes/parser/class-wp-parser.php +++ b/wp-includes/parser/class-wp-parser.php @@ -13,6 +13,13 @@ class WP_Parser { private $tokens; private $position; + /** + * The current AST. + * + * @var WP_Parser_Node|null + */ + private $current_ast; + public function __construct( WP_Parser_Grammar $grammar, array $tokens ) { $this->grammar = $grammar; $this->tokens = $tokens; @@ -26,6 +33,37 @@ public function parse() { return false === $ast ? null : $ast; } + /** + * Parse the next AST from the input tokens. + * + * This method reads tokens from the input until an AST is successfully parsed. + * It starts from "$this->tokens[ $this->position ]", advances the number of + * tokens read, and returns a boolean indicating whether an AST was successfully + * parsed. When the end of the input is reached or a query could not be parsed, + * the method returns false. + * + * @return bool Whether an AST was successfully parsed. + */ + public function next_ast(): bool { + if ( $this->position >= count( $this->tokens ) ) { + return false; + } + $this->current_ast = $this->parse(); + return true; + } + + /** + * Get the current AST. + * + * When no AST has been parsed yet, the parsing failed, or the end of the + * input was reached, this method returns null. + * + * @return WP_Parser_Node|null The current AST, or null if no AST was parsed. + */ + public function get_ast(): ?WP_Parser_Node { + return $this->current_ast; + } + private function parse_recursive( $rule_id ) { $is_terminal = $rule_id <= $this->grammar->highest_terminal_id; if ( $is_terminal ) { From cde7da8641a39ce937684459ed899b37a205e3f8 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Wed, 23 Apr 2025 11:35:16 +0200 Subject: [PATCH 16/36] Store and expose byte start and length in tokens and nodes --- wp-includes/mysql/class-wp-mysql-lexer.php | 7 ++++- wp-includes/parser/class-wp-parser-node.php | 21 +++++++++++++ wp-includes/parser/class-wp-parser-token.php | 33 +++++++++++++++++--- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/wp-includes/mysql/class-wp-mysql-lexer.php b/wp-includes/mysql/class-wp-mysql-lexer.php index f2174ea..813ae71 100644 --- a/wp-includes/mysql/class-wp-mysql-lexer.php +++ b/wp-includes/mysql/class-wp-mysql-lexer.php @@ -2247,7 +2247,12 @@ public function get_token(): ?WP_MySQL_Token { if ( null === $this->token_type ) { return null; } - return new WP_MySQL_Token( $this->token_type, $this->get_current_token_bytes() ); + return new WP_MySQL_Token( + $this->token_type, + $this->get_current_token_bytes(), + $this->token_starts_at, + $this->bytes_already_read - $this->token_starts_at + ); } /** diff --git a/wp-includes/parser/class-wp-parser-node.php b/wp-includes/parser/class-wp-parser-node.php index 9d66233..6a68ee6 100644 --- a/wp-includes/parser/class-wp-parser-node.php +++ b/wp-includes/parser/class-wp-parser-node.php @@ -263,6 +263,27 @@ public function get_descendant_tokens( ?int $token_id = null ): array { return $all_descendants; } + /** + * Get the byte offset in the input where the node begins. + * + * @return int + */ + public function get_start(): int { + return $this->get_first_descendant_token()->start; + } + + /** + * Get the byte length of the node in the input. + * + * @return int + */ + public function get_length(): int { + $tokens = $this->get_descendant_tokens(); + $last_token = end( $tokens ); + $start = $this->get_start(); + return $last_token->start + $last_token->length - $start; + } + /* * @TODO: Let's implement a more powerful AST-querying API. * See: https://github.com/WordPress/sqlite-database-integration/pull/164#discussion_r1855230501 diff --git a/wp-includes/parser/class-wp-parser-token.php b/wp-includes/parser/class-wp-parser-token.php index 1148995..2eca1db 100644 --- a/wp-includes/parser/class-wp-parser-token.php +++ b/wp-includes/parser/class-wp-parser-token.php @@ -23,14 +23,37 @@ class WP_Parser_Token { */ public $value; + /** + * Byte offset in the input where the token begins. + * + * @var int + */ + public $start; + + /** + * Byte length of the token in the input. + * + * @var int + */ + public $length; + /** * Constructor. * - * @param int $id Token type. - * @param string $value Token value. + * @param int $id Token type. + * @param string $value Token value. + * @param int $start Byte offset in the input where the token begins. + * @param int $length Byte length of the token in the input. */ - public function __construct( int $id, string $value ) { - $this->id = $id; - $this->value = $value; + public function __construct( + int $id, + string $value, + int $start, + int $length + ) { + $this->id = $id; + $this->value = $value; + $this->start = $start; + $this->length = $length; } } From 02838843cb813e5b4b3f5f653313fa420eff6fa5 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Wed, 23 Apr 2025 11:36:10 +0200 Subject: [PATCH 17/36] Parse queries from "wp_get_db_schema()" --- .../sqlite-ast/class-wp-sqlite-driver.php | 21 +++++++++--------- ...qlite-information-schema-reconstructor.php | 22 +++++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index 6c55f53..733b396 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -655,7 +655,7 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo try { // Parse the MySQL query. - $ast = $this->parse_query( $query ); + $ast = $this->parse_query( $query )->current(); // Handle transaction commands. @@ -728,23 +728,24 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo } /** - * Parse a MySQL query into an AST. + * Parse a MySQL query into an array of ASTs. * - * @param string $query The MySQL query to parse. - * @return WP_Parser_Node The AST representing the parsed query. + * @param string $query The MySQL query to parse. + * @return Generator A generator of ASTs representing the queries parsed from the input. * @throws WP_SQLite_Driver_Exception When the query parsing fails. */ - public function parse_query( string $query ): WP_Parser_Node { + public function parse_query( string $query ): Generator { $lexer = new WP_MySQL_Lexer( $query ); $tokens = $lexer->remaining_tokens(); $parser = new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); - $ast = $parser->parse(); - - if ( null === $ast ) { - throw $this->new_driver_exception( 'Failed to parse the MySQL query.' ); + while ( $parser->next_ast() ) { + $ast = $parser->get_ast(); + if ( null === $ast ) { + throw $this->new_driver_exception( 'Failed to parse the MySQL query.' ); + } + yield $ast; } - return $ast; } /** diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index de6e92e..ecad63c 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -64,12 +64,16 @@ public function ensure_correct_information_schema(): void { throw new Exception( 'The "wp_get_db_schema()" function was not defined.' ); } $schema = wp_get_db_schema(); - $parts = preg_split( '/(CREATE\s+TABLE)/', $schema, -1, PREG_SPLIT_NO_EMPTY ); - foreach ( $parts as $part ) { - $name = $this->unquote_mysql_identifier( - preg_split( '/\s+/', $part, 2, PREG_SPLIT_NO_EMPTY )[0] - ); - $wp_tables[ $name ] = 'CREATE TABLE' . $part; + foreach ( $this->driver->parse_query( $schema ) as $query ) { + $create_node = $query->get_first_descendant_node( 'createStatement' ); + if ( $create_node && $create_node->has_child_node( 'createTable' ) ) { + $name_node = $create_node->get_first_descendant_node( 'tableName' ); + $name = $this->unquote_mysql_identifier( + substr( $schema, $name_node->get_start(), $name_node->get_length() ) + ); + + $wp_tables[ $name ] = $create_node; + } } } @@ -78,12 +82,12 @@ public function ensure_correct_information_schema(): void { if ( ! in_array( $table, $information_schema_tables, true ) ) { if ( isset( $wp_tables[ $table ] ) ) { // WordPress core table (as returned by "wp_get_db_schema()"). - $sql = $wp_tables[ $table ]; + $ast = $wp_tables[ $table ]; } else { // Other table (a WordPress plugin or unrelated to WordPress). $sql = $this->generate_create_table_statement( $table ); + $ast = $this->driver->parse_query( $sql )->current(); } - $ast = $this->driver->parse_query( $sql ); $this->information_schema_builder->record_create_table( $ast ); } } @@ -92,7 +96,7 @@ public function ensure_correct_information_schema(): void { foreach ( $information_schema_tables as $table ) { if ( ! in_array( $table, $tables, true ) ) { $sql = sprintf( 'DROP %s', $this->quote_sqlite_identifier( $table ) ); - $ast = $this->driver->parse_query( $sql ); + $ast = $this->driver->parse_query( $sql )->current(); $this->information_schema_builder->record_drop_table( $ast ); } } From e657ad0e8367bb980d43191e135d028258f1956e Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Wed, 23 Apr 2025 15:37:05 +0200 Subject: [PATCH 18/36] Pass input in tokens and get token values lazily --- wp-includes/mysql/class-wp-mysql-lexer.php | 4 +-- wp-includes/mysql/class-wp-mysql-token.php | 2 +- wp-includes/parser/class-wp-parser-token.php | 31 ++++++++++++------- .../sqlite-ast/class-wp-sqlite-driver.php | 22 ++++++------- ...s-wp-sqlite-information-schema-builder.php | 16 +++++----- 5 files changed, 42 insertions(+), 33 deletions(-) diff --git a/wp-includes/mysql/class-wp-mysql-lexer.php b/wp-includes/mysql/class-wp-mysql-lexer.php index 813ae71..94da6f2 100644 --- a/wp-includes/mysql/class-wp-mysql-lexer.php +++ b/wp-includes/mysql/class-wp-mysql-lexer.php @@ -2249,9 +2249,9 @@ public function get_token(): ?WP_MySQL_Token { } return new WP_MySQL_Token( $this->token_type, - $this->get_current_token_bytes(), $this->token_starts_at, - $this->bytes_already_read - $this->token_starts_at + $this->bytes_already_read - $this->token_starts_at, + $this->sql ); } diff --git a/wp-includes/mysql/class-wp-mysql-token.php b/wp-includes/mysql/class-wp-mysql-token.php index c812288..b0e6a1b 100644 --- a/wp-includes/mysql/class-wp-mysql-token.php +++ b/wp-includes/mysql/class-wp-mysql-token.php @@ -34,6 +34,6 @@ public function get_name(): string { * @return string */ public function __toString(): string { - return $this->value . '<' . $this->id . ',' . $this->get_name() . '>'; + return $this->get_value() . '<' . $this->id . ',' . $this->get_name() . '>'; } } diff --git a/wp-includes/parser/class-wp-parser-token.php b/wp-includes/parser/class-wp-parser-token.php index 2eca1db..7e411be 100644 --- a/wp-includes/parser/class-wp-parser-token.php +++ b/wp-includes/parser/class-wp-parser-token.php @@ -16,13 +16,6 @@ class WP_Parser_Token { */ public $id; - /** - * Token value in its original raw form. - * - * @var string - */ - public $value; - /** * Byte offset in the input where the token begins. * @@ -37,23 +30,39 @@ class WP_Parser_Token { */ public $length; + /** + * Input bytes from which the token was parsed. + * + * @var string + */ + private $input; + /** * Constructor. * * @param int $id Token type. - * @param string $value Token value. * @param int $start Byte offset in the input where the token begins. * @param int $length Byte length of the token in the input. + * @param string $input Input bytes from which the token was parsed. */ public function __construct( int $id, - string $value, int $start, - int $length + int $length, + string $input ) { $this->id = $id; - $this->value = $value; $this->start = $start; $this->length = $length; + $this->input = $input; + } + + /** + * Get the token value as raw bytes from the input. + * + * @return string The token value. + */ + public function get_value(): string { + return substr( $this->input, $this->start, $this->length ); } } diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index 733b396..3a6e1e1 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -1525,7 +1525,7 @@ private function execute_show_statement( WP_Parser_Node $node ): void { sprintf( 'statement type: "%s" > "%s"', $node->rule_name, - $keyword1->value + $keyword1->get_value() ) ); } @@ -2045,7 +2045,7 @@ private function execute_administration_statement( WP_Parser_Node $node ): void sprintf( 'statement type: "%s" > "%s"', $node->rule_name, - $first_token->value + $first_token->get_value() ) ); } @@ -2057,7 +2057,7 @@ private function execute_administration_statement( WP_Parser_Node $node ): void } } - $operation = strtolower( $first_token->value ); + $operation = strtolower( $first_token->get_value() ); foreach ( $errors as $error ) { $results[] = (object) array( 'Table' => $this->db_name . '.' . $table_name, @@ -2181,7 +2181,7 @@ private function translate( $node ): ?string { // @TODO: Handle SET and JSON. throw $this->new_not_supported_exception( - sprintf( 'data type: %s', $child->value ) + sprintf( 'data type: %s', $child->get_value() ) ); case 'fromClause': // FROM DUAL is MySQL-specific syntax that means "FROM no tables" @@ -2239,7 +2239,7 @@ private function translate( $node ): ?string { '%s AS %s', $value, $this->quote_sqlite_identifier( - '@@' . ( $type_token ? "$type_token->value." : '' ) . $original_name + '@@' . ( $type_token ? "{$type_token->get_value()}." : '' ) . $original_name ) ); case 'castType': @@ -2290,7 +2290,7 @@ private function translate_token( WP_MySQL_Token $token ): ?string { */ return null; default: - return $token->value; + return $token->get_value(); } } @@ -2333,8 +2333,8 @@ private function translate_string_literal( WP_Parser_Node $node ): string { /* * 1. Remove bounding quotes. */ - $quote = $token->value[0]; - $value = substr( $token->value, 1, -1 ); + $quote = $token->get_value()[0]; + $value = substr( $token->get_value(), 1, -1 ); /* * 2. Normalize escaping of "%" and "_" characters. @@ -2420,13 +2420,13 @@ private function translate_pure_identifier( WP_Parser_Node $node ): string { $token = $node->get_first_child_token(); if ( WP_MySQL_Lexer::DOUBLE_QUOTED_TEXT === $token->id ) { - $value = substr( $token->value, 1, -1 ); + $value = substr( $token->get_value(), 1, -1 ); $value = str_replace( '""', '"', $value ); } elseif ( WP_MySQL_Lexer::BACK_TICK_QUOTED_ID === $token->id ) { - $value = substr( $token->value, 1, -1 ); + $value = substr( $token->get_value(), 1, -1 ); $value = str_replace( '``', '`', $value ); } else { - $value = $token->value; + $value = $token->get_value(); } return '`' . str_replace( '`', '``', $value ) . '`'; 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 4f8cc96..6ddc96e 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 @@ -568,7 +568,7 @@ public function record_alter_table( WP_Parser_Node $node ): void { continue; } - throw new \Exception( sprintf( 'Unsupported ALTER TABLE ADD action: %s', $first_token->value ) ); + throw new \Exception( sprintf( 'Unsupported ALTER TABLE ADD action: %s', $first_token->get_value() ) ); } // CHANGE [COLUMN] @@ -1353,7 +1353,7 @@ private function get_column_data_types( WP_Parser_Node $node ): array { ) { $type = 'mediumtext'; } else { - throw new \RuntimeException( 'Unknown data type: ' . $token->value ); + throw new \RuntimeException( 'Unknown data type: ' . $token->get_value() ); } // Get full type. @@ -1619,8 +1619,8 @@ private function get_column_numeric_attributes( WP_Parser_Node $node, string $da $precision_node = $node->get_first_descendant_node( 'precision' ); if ( null !== $precision_node ) { $values = $precision_node->get_descendant_tokens( WP_MySQL_Lexer::INT_NUMBER ); - $precision = (int) $values[0]->value; - $scale = (int) $values[1]->value; + $precision = (int) $values[0]->get_value(); + $scale = (int) $values[1]->get_value(); } if ( 'float' === $data_type ) { @@ -1865,20 +1865,20 @@ private function get_value( WP_Parser_Node $node ): string { if ( $child instanceof WP_Parser_Node ) { $value = $this->get_value( $child ); } elseif ( WP_MySQL_Lexer::BACK_TICK_QUOTED_ID === $child->id ) { - $value = substr( $child->value, 1, -1 ); + $value = substr( $child->get_value(), 1, -1 ); $value = str_replace( '``', '`', $value ); } elseif ( WP_MySQL_Lexer::SINGLE_QUOTED_TEXT === $child->id ) { - $value = $child->value; + $value = $child->get_value(); $value = substr( $value, 1, -1 ); $value = str_replace( '\"', '"', $value ); $value = str_replace( '""', '"', $value ); } elseif ( WP_MySQL_Lexer::DOUBLE_QUOTED_TEXT === $child->id ) { - $value = $child->value; + $value = $child->get_value(); $value = substr( $value, 1, -1 ); $value = str_replace( '\"', '"', $value ); $value = str_replace( '""', '"', $value ); } else { - $value = $child->value; + $value = $child->get_value(); } $full_value .= $value; } From 30c82125e4fd0c0adec431593bca75a0626e6c03 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Wed, 23 Apr 2025 16:08:55 +0200 Subject: [PATCH 19/36] Move multi-query parsing to WP_MySQL_Parser, improve naming and docs --- wp-includes/mysql/class-wp-mysql-parser.php | 51 +++++++++++++++++++ wp-includes/parser/class-wp-parser-node.php | 4 +- wp-includes/parser/class-wp-parser.php | 44 ++-------------- .../sqlite-ast/class-wp-sqlite-driver.php | 4 +- 4 files changed, 58 insertions(+), 45 deletions(-) diff --git a/wp-includes/mysql/class-wp-mysql-parser.php b/wp-includes/mysql/class-wp-mysql-parser.php index c96978e..d0cfaaf 100644 --- a/wp-includes/mysql/class-wp-mysql-parser.php +++ b/wp-includes/mysql/class-wp-mysql-parser.php @@ -1,4 +1,55 @@ next_query() ) { + * $ast = $parser->get_query_ast(); + * if ( ! $ast ) { + * // The parsing failed. + * } + * // The query was successfully parsed. + * } + * + * @return bool Whether a query was successfully parsed. + */ + public function next_query(): bool { + if ( $this->position >= count( $this->tokens ) ) { + return false; + } + $this->current_ast = $this->parse(); + if ( ! $this->current_ast ) { + return false; + } + return true; + } + + /** + * Get the current query AST. + * + * When no query has been parsed yet, the parsing failed, or the end of the + * input was reached, this method returns null. + * + * @see WP_MySQL_Parser::next_query() for usage example. + * + * @return WP_Parser_Node|null The current query AST, or null if no query was parsed. + */ + public function get_query_ast(): ?WP_Parser_Node { + return $this->current_ast; + } } diff --git a/wp-includes/parser/class-wp-parser-node.php b/wp-includes/parser/class-wp-parser-node.php index 6a68ee6..aa207bf 100644 --- a/wp-includes/parser/class-wp-parser-node.php +++ b/wp-includes/parser/class-wp-parser-node.php @@ -264,7 +264,7 @@ public function get_descendant_tokens( ?int $token_id = null ): array { } /** - * Get the byte offset in the input where the node begins. + * Get the byte offset in the input SQL string where this node begins. * * @return int */ @@ -273,7 +273,7 @@ public function get_start(): int { } /** - * Get the byte length of the node in the input. + * Get the byte length of this node in the input SQL string. * * @return int */ diff --git a/wp-includes/parser/class-wp-parser.php b/wp-includes/parser/class-wp-parser.php index 8592dad..4436892 100644 --- a/wp-includes/parser/class-wp-parser.php +++ b/wp-includes/parser/class-wp-parser.php @@ -9,16 +9,9 @@ * satisfy in order to be supported by this parser (e.g., no left recursion). */ class WP_Parser { - private $grammar; - private $tokens; - private $position; - - /** - * The current AST. - * - * @var WP_Parser_Node|null - */ - private $current_ast; + protected $grammar; + protected $tokens; + protected $position; public function __construct( WP_Parser_Grammar $grammar, array $tokens ) { $this->grammar = $grammar; @@ -33,37 +26,6 @@ public function parse() { return false === $ast ? null : $ast; } - /** - * Parse the next AST from the input tokens. - * - * This method reads tokens from the input until an AST is successfully parsed. - * It starts from "$this->tokens[ $this->position ]", advances the number of - * tokens read, and returns a boolean indicating whether an AST was successfully - * parsed. When the end of the input is reached or a query could not be parsed, - * the method returns false. - * - * @return bool Whether an AST was successfully parsed. - */ - public function next_ast(): bool { - if ( $this->position >= count( $this->tokens ) ) { - return false; - } - $this->current_ast = $this->parse(); - return true; - } - - /** - * Get the current AST. - * - * When no AST has been parsed yet, the parsing failed, or the end of the - * input was reached, this method returns null. - * - * @return WP_Parser_Node|null The current AST, or null if no AST was parsed. - */ - public function get_ast(): ?WP_Parser_Node { - return $this->current_ast; - } - private function parse_recursive( $rule_id ) { $is_terminal = $rule_id <= $this->grammar->highest_terminal_id; if ( $is_terminal ) { diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index 3a6e1e1..be7734a 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -739,8 +739,8 @@ public function parse_query( string $query ): Generator { $tokens = $lexer->remaining_tokens(); $parser = new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); - while ( $parser->next_ast() ) { - $ast = $parser->get_ast(); + while ( $parser->next_query() ) { + $ast = $parser->get_query_ast(); if ( null === $ast ) { throw $this->new_driver_exception( 'Failed to parse the MySQL query.' ); } From 6a3b370b2406defeb5cc6f8ebb0197a31b2caa7c Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Wed, 23 Apr 2025 16:30:53 +0200 Subject: [PATCH 20/36] Return parser instance rather than a generator --- .../sqlite-ast/class-wp-sqlite-driver.php | 27 +++++++++---------- ...qlite-information-schema-reconstructor.php | 20 +++++++++++--- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index be7734a..eae96b2 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -655,7 +655,13 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo try { // Parse the MySQL query. - $ast = $this->parse_query( $query )->current(); + // TODO: Translate and execute all queries in the SQL input string. + $parser = $this->create_parser( $query ); + $parser->next_query(); + $ast = $parser->get_query_ast(); + if ( null === $ast ) { + throw $this->new_driver_exception( 'Failed to parse the MySQL query.' ); + } // Handle transaction commands. @@ -728,24 +734,15 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo } /** - * Parse a MySQL query into an array of ASTs. + * Tokenize a MySQL query and initialize a parser. * - * @param string $query The MySQL query to parse. - * @return Generator A generator of ASTs representing the queries parsed from the input. - * @throws WP_SQLite_Driver_Exception When the query parsing fails. + * @param string $query The MySQL query to parse. + * @return WP_MySQL_Parser A parser initialized for the MySQL query. */ - public function parse_query( string $query ): Generator { + public function create_parser( string $query ): WP_MySQL_Parser { $lexer = new WP_MySQL_Lexer( $query ); $tokens = $lexer->remaining_tokens(); - - $parser = new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); - while ( $parser->next_query() ) { - $ast = $parser->get_query_ast(); - if ( null === $ast ) { - throw $this->new_driver_exception( 'Failed to parse the MySQL query.' ); - } - yield $ast; - } + return new WP_MySQL_Parser( self::$mysql_grammar, $tokens ); } /** diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index ecad63c..7cc53c8 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -64,8 +64,14 @@ public function ensure_correct_information_schema(): void { throw new Exception( 'The "wp_get_db_schema()" function was not defined.' ); } $schema = wp_get_db_schema(); - foreach ( $this->driver->parse_query( $schema ) as $query ) { - $create_node = $query->get_first_descendant_node( 'createStatement' ); + $parser = $this->driver->create_parser( $schema ); + while ( $parser->next_query() ) { + $ast = $parser->get_query_ast(); + if ( null === $ast ) { + throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' ); + } + + $create_node = $ast->get_first_descendant_node( 'createStatement' ); if ( $create_node && $create_node->has_child_node( 'createTable' ) ) { $name_node = $create_node->get_first_descendant_node( 'tableName' ); $name = $this->unquote_mysql_identifier( @@ -86,7 +92,10 @@ public function ensure_correct_information_schema(): void { } else { // Other table (a WordPress plugin or unrelated to WordPress). $sql = $this->generate_create_table_statement( $table ); - $ast = $this->driver->parse_query( $sql )->current(); + $ast = $this->driver->create_parser( $sql )->parse(); + if ( null === $ast ) { + throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' ); + } } $this->information_schema_builder->record_create_table( $ast ); } @@ -96,7 +105,10 @@ public function ensure_correct_information_schema(): void { foreach ( $information_schema_tables as $table ) { if ( ! in_array( $table, $tables, true ) ) { $sql = sprintf( 'DROP %s', $this->quote_sqlite_identifier( $table ) ); - $ast = $this->driver->parse_query( $sql )->current(); + $ast = $this->driver->create_parser( $sql )->parse(); + if ( null === $ast ) { + throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' ); + } $this->information_schema_builder->record_drop_table( $ast ); } } From 1121fccfd6dac98ed261e642503dc2ac9d82b122 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Thu, 24 Apr 2025 10:57:54 +0200 Subject: [PATCH 21/36] Refactor and document key column definition inference --- ...qlite-information-schema-reconstructor.php | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 7cc53c8..c0c8707 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -277,8 +277,6 @@ private function get_column_definition( string $table_name, array $column_info ) * @return string The MySQL key definition. */ private function get_key_definition( array $key, array $key_columns, array $data_types ): string { - $key_length_limit = 100; - // Key definition. $definition = array(); if ( $key['unique'] ) { @@ -293,24 +291,40 @@ private function get_key_definition( array $key, array $key_columns, array $data // Key columns. $cols = array(); foreach ( $key_columns as $column ) { - // Get data type and length. - $data_type = strtolower( $data_types[ $column['name'] ] ); - if ( 1 === preg_match( '/^(\w+)\s*\(\s*(\d+)\s*\)/', $data_type, $matches ) ) { - $data_type = $matches[1]; // "varchar" - $data_length = min( $matches[2], $key_length_limit ); // "255" - } - - // Apply max length if needed. + /* + * Extract type and length from column data type definition. + * + * This is required when the column data type is inferred from the + * '_mysql_data_types_cache' table, which stores the data type in + * the format "type(length)", such as "varchar(255)". + */ + $max_prefix_length = 100; + $type = strtolower( $data_types[ $column['name'] ] ); + $parts = explode( '(', $type ); + $column_type = $parts[0]; + $column_length = isset( $parts[1] ) ? (int) $parts[1] : null; + + /* + * Add an index column prefix length, if needed. + * + * This is required for "text" and "blob" types for columns inferred + * directly from the SQLite schema, and for the following types for + * columns inferred from the '_mysql_data_types_cache' table: + * char, varchar + * text, tinytext, mediumtext, longtext + * blob, tinyblob, mediumblob, longblob + * varbinary + */ if ( - str_contains( $data_type, 'char' ) - || str_starts_with( $data_type, 'var' ) - || str_ends_with( $data_type, 'text' ) - || str_ends_with( $data_type, 'blob' ) + str_ends_with( $column_type, 'char' ) + || str_ends_with( $column_type, 'text' ) + || str_ends_with( $column_type, 'blob' ) + || str_starts_with( $column_type, 'var' ) ) { $cols[] = sprintf( '%s(%d)', $this->quote_sqlite_identifier( $column['name'] ), - $data_length ?? $key_length_limit + min( $column_length ?? $max_prefix_length, $max_prefix_length ) ); } else { $cols[] = $this->quote_sqlite_identifier( $column['name'] ); From 231bab22e3428922832bab5b40b375e3ac531338 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Thu, 24 Apr 2025 14:23:16 +0200 Subject: [PATCH 22/36] Refactor and document key definition inference --- ...Information_Schema_Reconstructor_Tests.php | 60 ++++++++++++- .../sqlite-ast/class-wp-sqlite-driver.php | 7 +- ...qlite-information-schema-reconstructor.php | 89 +++++++++++++------ 3 files changed, 125 insertions(+), 31 deletions(-) diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php index 892e71a..a330d57 100644 --- a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php +++ b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -3,6 +3,14 @@ use PHPUnit\Framework\TestCase; class WP_SQLite_Information_Schema_Reconstructor_Tests extends TestCase { + const CREATE_DATA_TYPES_CACHE_TABLE_SQL = ' + CREATE TABLE _mysql_data_types_cache ( + `table` TEXT NOT NULL, + `column_or_index` TEXT NOT NULL, + `mysql_type` TEXT NOT NULL, + PRIMARY KEY(`table`, `column_or_index`) + )'; + /** @var WP_SQLite_Driver */ private $engine; @@ -107,8 +115,8 @@ public function testReconstructInformationSchemaTable(): void { ' PRIMARY KEY (`id`),', ' KEY `idx_role_score` (`role`(100), `priority`),', ' KEY `idx_score` (`score`),', - ' UNIQUE KEY `sqlite_autoindex_t_2` (`name`(100)),', - ' UNIQUE KEY `sqlite_autoindex_t_1` (`email`(100))', + ' UNIQUE KEY `name` (`name`(100)),', + ' UNIQUE KEY `email` (`email`(100))', ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci', ) ), @@ -167,6 +175,54 @@ public function testReconstructInformationSchemaTableWithWpTables(): void { ); } + public function testReconstructInformationSchemaFromMysqlDataTypesCache(): void { + $pdo = $this->engine->get_pdo(); + + $pdo->exec( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL ); + $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'id', 'int unsigned')" ); + $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'name', 'varchar(255)')" ); + $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'description', 'text')" ); + $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'shape', 'geomcollection')" ); + $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_name', 'KEY')" ); + $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_description', 'FULLTEXT')" ); + $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_shape', 'SPATIAL')" ); + + $this->engine->get_pdo()->exec( + ' + CREATE TABLE t ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + description TEXT, + shape TEXT NOT NULL + ) + ' + ); + $this->engine->get_pdo()->exec( 'CREATE INDEX t__idx_name ON t (name)' ); + $this->engine->get_pdo()->exec( 'CREATE INDEX t__idx_description ON t (description)' ); + $this->engine->get_pdo()->exec( 'CREATE INDEX t__idx_shape ON t (shape)' ); + + $this->reconstructor->ensure_correct_information_schema(); + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertSame( + implode( + "\n", + array( + 'CREATE TABLE `t` (', + ' `id` int unsigned NOT NULL AUTO_INCREMENT,', + ' `name` varchar(255) DEFAULT NULL,', + ' `description` text DEFAULT NULL,', + ' `shape` geomcollection NOT NULL,', + ' PRIMARY KEY (`id`),', + ' SPATIAL KEY `idx_shape` (`shape`(32)),', + ' FULLTEXT KEY `idx_description` (`description`(100)),', + ' KEY `idx_name` (`name`(100))', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci', + ) + ), + $result[0]->{'Create Table'} + ); + } + private function assertQuery( $sql ) { $retval = $this->engine->query( $sql ); $this->assertNotFalse( $retval ); diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index eae96b2..d30ea44 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -3459,7 +3459,12 @@ function ( $column ) { } else { $is_unique = '0' === $info['NON_UNIQUE']; - $sql = sprintf( ' %sKEY ', $is_unique ? 'UNIQUE ' : '' ); + $sql = sprintf( + ' %s%s%sKEY ', + $is_unique ? 'UNIQUE ' : '', + 'FULLTEXT' === $info['INDEX_TYPE'] ? 'FULLTEXT ' : '', + 'SPATIAL' === $info['INDEX_TYPE'] ? 'SPATIAL ' : '' + ); $sql .= $this->quote_mysql_identifier( $info['INDEX_NAME'] ); $sql .= ' ('; $sql .= implode( diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index c0c8707..d283efa 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -125,11 +125,13 @@ private function get_existing_table_names(): array { SELECT name FROM sqlite_schema WHERE type = 'table' + AND name != ? AND name NOT LIKE ? ESCAPE '\' AND name NOT LIKE ? ESCAPE '\' ORDER BY name ", array( + '_mysql_data_types_cache', 'sqlite\_%', str_replace( '_', '\_', WP_SQLite_Driver::RESERVED_PREFIX ) . '%', ) @@ -160,15 +162,15 @@ private function generate_create_table_statement( string $table_name ): string { sprintf( 'PRAGMA table_xinfo("%s")', $table_name ) )->fetchAll( PDO::FETCH_ASSOC ); - $definitions = array(); - $data_types = array(); + $definitions = array(); + $column_types = array(); foreach ( $columns as $column ) { $mysql_type = $this->get_cached_mysql_data_type( $table_name, $column['name'] ); if ( null === $mysql_type ) { $mysql_type = $this->get_mysql_data_type( $column['type'] ); } - $definitions[] = $this->get_column_definition( $table_name, $column ); - $data_types[ $column['name'] ] = $mysql_type; + $definitions[] = $this->get_column_definition( $table_name, $column ); + $column_types[ $column['name'] ] = $mysql_type; } // Primary key. @@ -199,18 +201,12 @@ private function generate_create_table_statement( string $table_name ): string { )->fetchAll( PDO::FETCH_ASSOC ); foreach ( $keys as $key ) { - $key_columns = $this->driver->execute_sqlite_query( - 'SELECT * FROM pragma_index_info("' . $key['name'] . '")' - )->fetchAll( PDO::FETCH_ASSOC ); - - // If the PK columns are the same as the UK columns, skip the key. - // This is because a primary key is already unique in MySQL. - $key_equals_pk = ! array_diff( $pk_columns, array_column( $key_columns, 'name' ) ); - $is_auto_index = strpos( $key['name'], 'sqlite_autoindex_' ) === 0; - if ( $is_auto_index && $key['unique'] && $key_equals_pk ) { + // Skip the internal index that SQLite may create for a primary key. + // In MySQL, no explicit index needs to be defined for a primary key. + if ( 'pk' === $key['origin'] ) { continue; } - $definitions[] = $this->get_key_definition( $key, $key_columns, $data_types ); + $definitions[] = $this->get_key_definition( $table_name, $key, $column_types ); } return sprintf( @@ -271,25 +267,52 @@ private function get_column_definition( string $table_name, array $column_info ) * * This method generates a MySQL key definition from SQLite key data. * - * @param array $key The SQLite key information. - * @param array $key_columns The SQLite key column information. - * @param array $data_types The MySQL data types of the columns. - * @return string The MySQL key definition. + * @param string $table_name The name of the table. + * @param array $key The SQLite key information. + * @param array $column_types The MySQL data types of the columns. + * @return string The MySQL key definition. */ - private function get_key_definition( array $key, array $key_columns, array $data_types ): string { - // Key definition. + private function get_key_definition( string $table_name, array $key, array $column_types ): string { $definition = array(); - if ( $key['unique'] ) { - $definition[] = 'UNIQUE'; + + // Key type. + $cached_type = $this->get_cached_mysql_data_type( $table_name, $key['name'] ); + if ( 'FULLTEXT' === $cached_type ) { + $definition[] = 'FULLTEXT KEY'; + } elseif ( 'SPATIAL' === $cached_type ) { + $definition[] = 'SPATIAL KEY'; + } elseif ( 'UNIQUE' === $cached_type || '1' === $key['unique'] ) { + $definition[] = 'UNIQUE KEY'; + } else { + $definition[] = 'KEY'; } - $definition[] = 'KEY'; - // Remove the prefix from the index name if there is any. We use __ as a separator. - $index_name = explode( '__', $key['name'], 2 )[1] ?? $key['name']; - $definition[] = $this->quote_sqlite_identifier( $index_name ); + // Key name. + $name = $key['name']; + + /* + * The SQLite driver prefixes index names with "{$table_name}__" to avoid + * naming conflicts among tables in SQLite. We need to remove the prefix. + */ + if ( str_starts_with( $name, "{$table_name}__" ) ) { + $name = substr( $name, strlen( "{$table_name}__" ) ); + } + + /** + * SQLite creates automatic internal indexes for primary and unique keys, + * naming them in format "sqlite_autoindex_{$table_name}_{$index_id}". + * For these internal indexes, we need to skip their name, so that in + * the generated MySQL definition, they follow implicit MySQL naming. + */ + if ( ! str_starts_with( $name, 'sqlite_autoindex_' ) ) { + $definition[] = $this->quote_sqlite_identifier( $name ); + } // Key columns. - $cols = array(); + $key_columns = $this->driver->execute_sqlite_query( + 'SELECT * FROM pragma_index_info("' . $key['name'] . '")' + )->fetchAll( PDO::FETCH_ASSOC ); + $cols = array(); foreach ( $key_columns as $column ) { /* * Extract type and length from column data type definition. @@ -299,7 +322,7 @@ private function get_key_definition( array $key, array $key_columns, array $data * the format "type(length)", such as "varchar(255)". */ $max_prefix_length = 100; - $type = strtolower( $data_types[ $column['name'] ] ); + $type = strtolower( $column_types[ $column['name'] ] ); $parts = explode( '(', $type ); $column_type = $parts[0]; $column_length = isset( $parts[1] ) ? (int) $parts[1] : null; @@ -362,6 +385,12 @@ private function column_has_default( string $mysql_type, ?string $default_value * that was used by an old version of the SQLite driver and that is otherwise * no longer needed. This is more precise than direct inference from SQLite. * + * For columns, it returns full column type, including prefix length, e.g.: + * int(11), bigint(20) unsigned, varchar(255), longtext + * + * For indexes, it returns one of: + * KEY, PRIMARY, UNIQUE, FULLTEXT, SPATIAL + * * @param string $table_name The table name. * @param string $column_or_index_name The column or index name. * @return string|null The MySQL definition, or null when not found. @@ -378,6 +407,10 @@ private function get_cached_mysql_data_type( string $table_name, string $column_ } throw $e; } + + // Normalize index type for backward compatibility. Some older versions + // of the SQLite driver stored index types with a " KEY" suffix, e.g., + // "PRIMARY KEY" or "UNIQUE KEY". More recent versions omit the suffix. if ( str_ends_with( $mysql_type, ' KEY' ) ) { $mysql_type = substr( $mysql_type, 0, strlen( $mysql_type ) - strlen( ' KEY' ) ); } From be2841d1d7462dce21b7cabbf4218db476ff25b4 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Thu, 24 Apr 2025 18:09:01 +0200 Subject: [PATCH 23/36] Improve and document default column value detection --- ...qlite-information-schema-reconstructor.php | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index d283efa..cbc5907 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -369,9 +369,27 @@ private function column_has_default( string $mysql_type, ?string $default_value if ( null === $default_value || '' === $default_value ) { return false; } + $mysql_type = strtolower( $mysql_type ); + + /* + * In MySQL, geometry columns can't have a default value. + * + * Geometry columns are saved as TEXT in SQLite, and in an older version + * of the SQLite driver, TEXT columns were assigned a default value of ''. + */ + if ( 'geomcollection' === $mysql_type || 'geometrycollection' === $mysql_type ) { + return false; + } + + /* + * In MySQL, date/time columns can't have a default value of ''. + * + * Date/time columns are saved as TEXT in SQLite, and in an older version + * of the SQLite driver, TEXT columns were assigned a default value of ''. + */ if ( "''" === $default_value - && in_array( strtolower( $mysql_type ), array( 'datetime', 'date', 'time', 'timestamp', 'year' ), true ) + && in_array( $mysql_type, array( 'datetime', 'date', 'time', 'timestamp', 'year' ), true ) ) { return false; } From fd55809fe0dacde60028446f85a90ae45c6ebe75 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Fri, 25 Apr 2025 08:49:10 +0200 Subject: [PATCH 24/36] Extract WP schema to a method, improve naming and docs --- ...Information_Schema_Reconstructor_Tests.php | 6 +- .../class-wp-sqlite-configurator.php | 22 ++-- ...qlite-information-schema-reconstructor.php | 123 ++++++++++-------- 3 files changed, 84 insertions(+), 67 deletions(-) diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php index a330d57..b47d1dc 100644 --- a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php +++ b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -75,7 +75,7 @@ public function setUp(): void { ); } - public function testReconstructInformationSchemaTable(): void { + public function testReconstructTable(): void { $this->engine->get_pdo()->exec( ' CREATE TABLE t ( @@ -124,7 +124,7 @@ public function testReconstructInformationSchemaTable(): void { ); } - public function testReconstructInformationSchemaTableWithWpTables(): void { + public function testReconstructWpTable(): void { // Create a WP table with any columns. $this->engine->get_pdo()->exec( 'CREATE TABLE wp_posts ( id INTEGER )' ); @@ -175,7 +175,7 @@ public function testReconstructInformationSchemaTableWithWpTables(): void { ); } - public function testReconstructInformationSchemaFromMysqlDataTypesCache(): void { + public function testReconstructTableFromMysqlDataTypesCache(): void { $pdo = $this->engine->get_pdo(); $pdo->exec( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL ); diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php b/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php index 072e2e4..0bf1fcf 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-configurator.php @@ -23,30 +23,30 @@ class WP_SQLite_Configurator { * * @var WP_SQLite_Information_Schema_Builder */ - private $information_schema_builder; + private $schema_builder; /** * A service for reconstructing the MySQL INFORMATION_SCHEMA tables in SQLite. * * @var WP_SQLite_Information_Schema_Reconstructor */ - private $information_schema_reconstructor; + private $schema_reconstructor; /** * Constructor. * - * @param WP_SQLite_Driver $driver The SQLite driver instance. - * @param WP_SQLite_Information_Schema_Builder $information_schema_builder The information schema builder instance. + * @param WP_SQLite_Driver $driver The SQLite driver instance. + * @param WP_SQLite_Information_Schema_Builder $schema_builder The information schema builder instance. */ public function __construct( WP_SQLite_Driver $driver, - WP_SQLite_Information_Schema_Builder $information_schema_builder + WP_SQLite_Information_Schema_Builder $schema_builder ) { - $this->driver = $driver; - $this->information_schema_builder = $information_schema_builder; - $this->information_schema_reconstructor = new WP_SQLite_Information_Schema_Reconstructor( + $this->driver = $driver; + $this->schema_builder = $schema_builder; + $this->schema_reconstructor = new WP_SQLite_Information_Schema_Reconstructor( $driver, - $information_schema_builder + $schema_builder ); } @@ -78,8 +78,8 @@ public function configure_database(): void { $this->driver->execute_sqlite_query( 'BEGIN EXCLUSIVE TRANSACTION' ); try { $this->ensure_global_variables_table(); - $this->information_schema_builder->ensure_information_schema_tables(); - $this->information_schema_reconstructor->ensure_correct_information_schema(); + $this->schema_builder->ensure_information_schema_tables(); + $this->schema_reconstructor->ensure_correct_information_schema(); $this->save_current_driver_version(); } catch ( Throwable $e ) { $this->driver->execute_sqlite_query( 'ROLLBACK' ); diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index cbc5907..0dfefca 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -28,63 +28,37 @@ class WP_SQLite_Information_Schema_Reconstructor { * * @var WP_SQLite_Information_Schema_Builder */ - private $information_schema_builder; + private $schema_builder; /** * Constructor. * - * @param WP_SQLite_Driver $driver The SQLite driver instance. - * @param WP_SQLite_Information_Schema_Builder $information_schema_builder The information schema builder instance. + * @param WP_SQLite_Driver $driver The SQLite driver instance. + * @param WP_SQLite_Information_Schema_Builder $schema_builder The information schema builder instance. */ public function __construct( WP_SQLite_Driver $driver, - WP_SQLite_Information_Schema_Builder $information_schema_builder + WP_SQLite_Information_Schema_Builder $schema_builder ) { - $this->driver = $driver; - $this->information_schema_builder = $information_schema_builder; + $this->driver = $driver; + $this->schema_builder = $schema_builder; } /** * Ensure that the MySQL INFORMATION_SCHEMA data in SQLite is correct. * * This method checks if the MySQL INFORMATION_SCHEMA data in SQLite is correct, - * and if it is not, it will reconstruct the data. + * and if it is not, it will reconstruct missing data and remove stale values. */ public function ensure_correct_information_schema(): void { - $tables = $this->get_existing_table_names(); + $sqlite_tables = $this->get_sqlite_table_names(); $information_schema_tables = $this->get_information_schema_table_names(); // In WordPress, use "wp_get_db_schema()" to reconstruct WordPress tables. - $wp_tables = array(); - if ( defined( 'ABSPATH' ) ) { - if ( file_exists( ABSPATH . 'wp-admin/includes/schema.php' ) ) { - require_once ABSPATH . 'wp-admin/includes/schema.php'; - } - if ( ! function_exists( 'wp_get_db_schema' ) ) { - throw new Exception( 'The "wp_get_db_schema()" function was not defined.' ); - } - $schema = wp_get_db_schema(); - $parser = $this->driver->create_parser( $schema ); - while ( $parser->next_query() ) { - $ast = $parser->get_query_ast(); - if ( null === $ast ) { - throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' ); - } - - $create_node = $ast->get_first_descendant_node( 'createStatement' ); - if ( $create_node && $create_node->has_child_node( 'createTable' ) ) { - $name_node = $create_node->get_first_descendant_node( 'tableName' ); - $name = $this->unquote_mysql_identifier( - substr( $schema, $name_node->get_start(), $name_node->get_length() ) - ); - - $wp_tables[ $name ] = $create_node; - } - } - } + $wp_tables = $this->get_wp_create_table_statements(); // Reconstruct information schema records for tables that don't have them. - foreach ( $tables as $table ) { + foreach ( $sqlite_tables as $table ) { if ( ! in_array( $table, $information_schema_tables, true ) ) { if ( isset( $wp_tables[ $table ] ) ) { // WordPress core table (as returned by "wp_get_db_schema()"). @@ -97,19 +71,19 @@ public function ensure_correct_information_schema(): void { throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' ); } } - $this->information_schema_builder->record_create_table( $ast ); + $this->schema_builder->record_create_table( $ast ); } } // Remove information schema records for tables that don't exist. foreach ( $information_schema_tables as $table ) { - if ( ! in_array( $table, $tables, true ) ) { + if ( ! in_array( $table, $sqlite_tables, true ) ) { $sql = sprintf( 'DROP %s', $this->quote_sqlite_identifier( $table ) ); $ast = $this->driver->create_parser( $sql )->parse(); if ( null === $ast ) { throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' ); } - $this->information_schema_builder->record_drop_table( $ast ); + $this->schema_builder->record_drop_table( $ast ); } } } @@ -119,7 +93,7 @@ public function ensure_correct_information_schema(): void { * * @return string[] The names of tables in the SQLite database. */ - private function get_existing_table_names(): array { + private function get_sqlite_table_names(): array { return $this->driver->execute_sqlite_query( " SELECT name @@ -144,12 +118,55 @@ private function get_existing_table_names(): array { * @return string[] The names of tables in the information schema. */ private function get_information_schema_table_names(): array { - $tables_table = $this->information_schema_builder->get_table_name( false, 'tables' ); + $tables_table = $this->schema_builder->get_table_name( false, 'tables' ); return $this->driver->execute_sqlite_query( "SELECT table_name FROM $tables_table ORDER BY table_name" )->fetchAll( PDO::FETCH_COLUMN ); } + /** + * Get a map of parsed CREATE TABLE statements for WordPress tables. + * + * When reconstructing the information schema data for WordPress tables, we + * can use the "wp_get_db_schema()" function to get accurate CREATE TABLE + * statements. This method parses the result of "wp_get_db_schema()" into + * an array of parsed CREATE TABLE statements indexed by the table names. + * + * @return array The WordPress CREATE TABLE statements. + */ + private function get_wp_create_table_statements(): array { + if ( ! defined( 'ABSPATH' ) ) { + return array(); + } + if ( file_exists( ABSPATH . 'wp-admin/includes/schema.php' ) ) { + require_once ABSPATH . 'wp-admin/includes/schema.php'; + } + if ( ! function_exists( 'wp_get_db_schema' ) ) { + throw new Exception( 'The "wp_get_db_schema()" function was not defined.' ); + } + + $schema = wp_get_db_schema(); + $parser = $this->driver->create_parser( $schema ); + $wp_tables = array(); + while ( $parser->next_query() ) { + $ast = $parser->get_query_ast(); + if ( null === $ast ) { + throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' ); + } + + $create_node = $ast->get_first_descendant_node( 'createStatement' ); + if ( $create_node && $create_node->has_child_node( 'createTable' ) ) { + $name_node = $create_node->get_first_descendant_node( 'tableName' ); + $name = $this->unquote_mysql_identifier( + substr( $schema, $name_node->get_start(), $name_node->get_length() ) + ); + + $wp_tables[ $name ] = $create_node; + } + } + return $wp_tables; + } + /** * Generate a MySQL CREATE TABLE statement from an SQLite table definition. * @@ -167,9 +184,9 @@ private function generate_create_table_statement( string $table_name ): string { foreach ( $columns as $column ) { $mysql_type = $this->get_cached_mysql_data_type( $table_name, $column['name'] ); if ( null === $mysql_type ) { - $mysql_type = $this->get_mysql_data_type( $column['type'] ); + $mysql_type = $this->get_mysql_column_type( $column['type'] ); } - $definitions[] = $this->get_column_definition( $table_name, $column ); + $definitions[] = $this->generate_column_definition( $table_name, $column ); $column_types[ $column['name'] ] = $mysql_type; } @@ -206,7 +223,7 @@ private function generate_create_table_statement( string $table_name ): string { if ( 'pk' === $key['origin'] ) { continue; } - $definitions[] = $this->get_key_definition( $table_name, $key, $column_types ); + $definitions[] = $this->generate_key_definition( $table_name, $key, $column_types ); } return sprintf( @@ -225,14 +242,14 @@ private function generate_create_table_statement( string $table_name ): string { * @param array $column_info The SQLite column information. * @return string The MySQL column definition. */ - private function get_column_definition( string $table_name, array $column_info ): string { + private function generate_column_definition( string $table_name, array $column_info ): string { $definition = array(); $definition[] = $this->quote_sqlite_identifier( $column_info['name'] ); // Data type. $mysql_type = $this->get_cached_mysql_data_type( $table_name, $column_info['name'] ); if ( null === $mysql_type ) { - $mysql_type = $this->get_mysql_data_type( $column_info['type'] ); + $mysql_type = $this->get_mysql_column_type( $column_info['type'] ); } $definition[] = $mysql_type; @@ -268,27 +285,27 @@ private function get_column_definition( string $table_name, array $column_info ) * This method generates a MySQL key definition from SQLite key data. * * @param string $table_name The name of the table. - * @param array $key The SQLite key information. + * @param array $key_info The SQLite key information. * @param array $column_types The MySQL data types of the columns. * @return string The MySQL key definition. */ - private function get_key_definition( string $table_name, array $key, array $column_types ): string { + private function generate_key_definition( string $table_name, array $key_info, array $column_types ): string { $definition = array(); // Key type. - $cached_type = $this->get_cached_mysql_data_type( $table_name, $key['name'] ); + $cached_type = $this->get_cached_mysql_data_type( $table_name, $key_info['name'] ); if ( 'FULLTEXT' === $cached_type ) { $definition[] = 'FULLTEXT KEY'; } elseif ( 'SPATIAL' === $cached_type ) { $definition[] = 'SPATIAL KEY'; - } elseif ( 'UNIQUE' === $cached_type || '1' === $key['unique'] ) { + } elseif ( 'UNIQUE' === $cached_type || '1' === $key_info['unique'] ) { $definition[] = 'UNIQUE KEY'; } else { $definition[] = 'KEY'; } // Key name. - $name = $key['name']; + $name = $key_info['name']; /* * The SQLite driver prefixes index names with "{$table_name}__" to avoid @@ -310,7 +327,7 @@ private function get_key_definition( string $table_name, array $key, array $colu // Key columns. $key_columns = $this->driver->execute_sqlite_query( - 'SELECT * FROM pragma_index_info("' . $key['name'] . '")' + 'SELECT * FROM pragma_index_info("' . $key_info['name'] . '")' )->fetchAll( PDO::FETCH_ASSOC ); $cols = array(); foreach ( $key_columns as $column ) { @@ -445,7 +462,7 @@ private function get_cached_mysql_data_type( string $table_name, string $column_ * @param string $column_type The SQLite column type. * @return string The MySQL column type. */ - private function get_mysql_data_type( string $column_type ): string { + private function get_mysql_column_type( string $column_type ): string { $type = strtoupper( $column_type ); /* From baa22c3fb607ddb8c395d28ec48f480f9491d9bb Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Fri, 25 Apr 2025 08:52:27 +0200 Subject: [PATCH 25/36] For multisite installs, reconstruct WP schema for all sites --- ..._Information_Schema_Reconstructor_Tests.php | 4 ++-- ...sqlite-information-schema-reconstructor.php | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php index b47d1dc..8a86901 100644 --- a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php +++ b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -39,8 +39,8 @@ public static function setUpBeforeClass(): void { if ( ! defined( 'ABSPATH' ) ) { define( 'ABSPATH', __DIR__ ); } - if ( ! function_exists( 'wp_installing' ) ) { - function wp_installing() { + if ( ! function_exists( 'is_multisite' ) ) { + function is_multisite() { return false; } } diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 0dfefca..a8638ef 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -145,7 +145,23 @@ private function get_wp_create_table_statements(): array { throw new Exception( 'The "wp_get_db_schema()" function was not defined.' ); } - $schema = wp_get_db_schema(); + // Get schema for global tables and the main site. + $schema = wp_get_db_schema(); + + // For multisite installs, add schema definitions for all sites. + if ( is_multisite() ) { + $site_ids = get_sites( + array( + 'fields' => 'ids', + 'number' => PHP_INT_MAX, + ) + ); + foreach ( $site_ids as $site_id ) { + $schema .= wp_get_db_schema( 'blog', $site_id ); + } + } + + // Parse the schema. $parser = $this->driver->create_parser( $schema ); $wp_tables = array(); while ( $parser->next_query() ) { From c67f6c4e57ab5f0b89bf2d4f3e8d6685092e72f1 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Fri, 25 Apr 2025 12:49:32 +0200 Subject: [PATCH 26/36] Refactor default value handling, add escaping and tests --- ...Information_Schema_Reconstructor_Tests.php | 58 ++++++++++ ...qlite-information-schema-reconstructor.php | 106 ++++++++++++++++-- 2 files changed, 155 insertions(+), 9 deletions(-) diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php index 8a86901..e0d2314 100644 --- a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php +++ b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -223,6 +223,64 @@ public function testReconstructTableFromMysqlDataTypesCache(): void { ); } + public function testDefaultValues(): void { + $this->engine->get_pdo()->exec( + " + CREATE TABLE t ( + col1 text DEFAULT abc, + col2 text DEFAULT 'abc', + col3 text DEFAULT \"abc\", + col4 text DEFAULT NULL, + col5 int DEFAULT TRUE, + col6 int DEFAULT FALSE, + col7 int DEFAULT 123, + col8 real DEFAULT 1.23, + col9 real DEFAULT -1.23, + col10 real DEFAULT 1e3, + col11 real DEFAULT 1.2e-3, + col12 int DEFAULT 0x1a2f, + col13 int DEFAULT 0X1A2f, + col14 blob DEFAULT x'4142432E', + col15 blob DEFAULT x'4142432E', + col16 text DEFAULT CURRENT_TIMESTAMP, + col17 text DEFAULT CURRENT_DATE, + col18 text DEFAULT CURRENT_TIME + ) + " + ); + + $this->reconstructor->ensure_correct_information_schema(); + $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); + $this->assertSame( + implode( + "\n", + array( + 'CREATE TABLE `t` (', + " `col1` varchar(65535) DEFAULT 'abc',", + " `col2` varchar(65535) DEFAULT 'abc',", + " `col3` varchar(65535) DEFAULT 'abc',", + ' `col4` text DEFAULT NULL,', + " `col5` int DEFAULT '1',", + " `col6` int DEFAULT '0',", + " `col7` int DEFAULT '123',", + " `col8` float DEFAULT '1.23',", + " `col9` float DEFAULT '-1.23',", + " `col10` float DEFAULT '1e3',", + " `col11` float DEFAULT '1.2e-3',", + " `col12` int DEFAULT '6703',", + " `col13` int DEFAULT '6703',", + " `col14` varbinary(65535) DEFAULT 'ABC.',", + " `col15` varbinary(65535) DEFAULT 'ABC.',", + ' `col16` datetime DEFAULT CURRENT_TIMESTAMP,', + ' `col17` text DEFAULT NULL,', + ' `col18` text DEFAULT NULL', + ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci', + ) + ), + $result[0]->{'Create Table'} + ); + } + private function assertQuery( $sql ) { $retval = $this->engine->query( $sql ); $this->assertNotFalse( $retval ); diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index a8638ef..3ef4e14 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -267,6 +267,23 @@ private function generate_column_definition( string $table_name, array $column_i if ( null === $mysql_type ) { $mysql_type = $this->get_mysql_column_type( $column_info['type'] ); } + + /** + * Correct some column types based on their default values: + * 1. In MySQL, non-datetime columns can't have a timestamp default. + * Let's use DATETIME when default is set to CURRENT_TIMESTAMP. + * 2. In MySQL, TEXT and BLOB columns can't have a default value. + * Let's use VARCHAR(65535) and VARBINARY(65535) when default is set. + */ + $default = $this->generate_column_default( $mysql_type, $column_info['dflt_value'] ); + if ( 'CURRENT_TIMESTAMP' === $default ) { + $mysql_type = 'datetime'; + } elseif ( 'text' === $mysql_type && null !== $default ) { + $mysql_type = 'varchar(65535)'; + } elseif ( 'blob' === $mysql_type && null !== $default ) { + $mysql_type = 'varbinary(65535)'; + } + $definition[] = $mysql_type; // NULL/NOT NULL. @@ -288,8 +305,8 @@ private function generate_column_definition( string $table_name, array $column_i } // Default value. - if ( $this->column_has_default( $mysql_type, $column_info['dflt_value'] ) && ! $is_auto_increment ) { - $definition[] = 'DEFAULT ' . $column_info['dflt_value']; + if ( null !== $default && ! $is_auto_increment ) { + $definition[] = 'DEFAULT ' . $default; } return implode( ' ', $definition ); @@ -392,15 +409,15 @@ private function generate_key_definition( string $table_name, array $key_info, a } /** - * Determine if a column has a default value. + * Generate a MySQL default value from an SQLite default value. * * @param string $mysql_type The MySQL data type of the column. * @param string|null $default_value The default value of the SQLite column. - * @return bool True if the column has a default value, false otherwise. + * @return string|null The default value, or null if the column has no default value. */ - private function column_has_default( string $mysql_type, ?string $default_value ): bool { + private function generate_column_default( string $mysql_type, ?string $default_value ): ?string { if ( null === $default_value || '' === $default_value ) { - return false; + return null; } $mysql_type = strtolower( $mysql_type ); @@ -411,7 +428,7 @@ private function column_has_default( string $mysql_type, ?string $default_value * of the SQLite driver, TEXT columns were assigned a default value of ''. */ if ( 'geomcollection' === $mysql_type || 'geometrycollection' === $mysql_type ) { - return false; + return null; } /* @@ -424,9 +441,69 @@ private function column_has_default( string $mysql_type, ?string $default_value "''" === $default_value && in_array( $mysql_type, array( 'datetime', 'date', 'time', 'timestamp', 'year' ), true ) ) { - return false; + return null; + } + + /** + * Convert SQLite default values to MySQL default values. + * + * See: + * - https://www.sqlite.org/syntax/column-constraint.html + * - https://www.sqlite.org/syntax/literal-value.html + * - https://www.sqlite.org/lang_expr.html#literal_values_constants_ + */ + + // Quoted string literal. E.g.: 'abc', "abc", `abc` + $first_byte = $default_value[0] ?? null; + if ( '"' === $first_byte || "'" === $first_byte || '`' === $first_byte ) { + return $this->escape_mysql_string_literal( substr( $default_value, 1, -1 ) ); + } + + // Normalize the default value to for easier comparison. + $uppercase_default_value = strtoupper( $default_value ); + + // NULL, TRUE, FALSE. + if ( 'NULL' === $uppercase_default_value ) { + // DEFAULT NULL is the same as no default value. + return null; + } elseif ( 'TRUE' === $uppercase_default_value ) { + return '1'; + } elseif ( 'FALSE' === $uppercase_default_value ) { + return '0'; + } + + // Date/time values. + if ( 'CURRENT_TIMESTAMP' === $uppercase_default_value ) { + return 'CURRENT_TIMESTAMP'; + } elseif ( 'CURRENT_DATE' === $uppercase_default_value ) { + return null; // Not supported in MySQL. + } elseif ( 'CURRENT_TIME' === $uppercase_default_value ) { + return null; // Not supported in MySQL. + } + + // SQLite supports underscores in all numeric literals. + $no_underscore_default_value = str_replace( '_', '', $default_value ); + + // Numeric literals. E.g.: 123, 1.23, -1.23, 1e3, 1.2e-3 + if ( is_numeric( $no_underscore_default_value ) ) { + return $no_underscore_default_value; + } + + // HEX literals (numeric). E.g.: 0x1a2f, 0X1A2F + $value = filter_var( $no_underscore_default_value, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX ); + if ( false !== $value ) { + return $value; + } + + // BLOB literals (string). E.g.: x'1a2f', X'1A2F' + // Checking the prefix is enough as SQLite doesn't allow malformed values. + if ( str_starts_with( $uppercase_default_value, "X'" ) ) { + // Convert the hex string to ASCII bytes. + return "'" . pack( 'H*', substr( $default_value, 2, -1 ) ) . "'"; } - return true; + + // Unquoted string literal. E.g.: abc + return $this->escape_mysql_string_literal( $default_value ); } /** @@ -525,6 +602,17 @@ private function get_mysql_column_type( string $column_type ): string { return 'text'; } + /** + * Escape a string literal for MySQL DEFAULT values. + * + * @param string $literal The string literal to escape. + * @return string The escaped string literal. + */ + private function escape_mysql_string_literal( string $literal ): string { + // See: https://www.php.net/manual/en/mysqli.real-escape-string.php + return "'" . addcslashes( $literal, "\0\n\r'\"\Z" ) . "'"; + } + /** * Quote an SQLite identifier. * From 959505ee184eb2b7c60978ceb66c4edf9d19b5de Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Fri, 25 Apr 2025 21:14:44 +0200 Subject: [PATCH 27/36] Fix schema inference for WordPress tables --- ...Information_Schema_Reconstructor_Tests.php | 6 ++-- ...qlite-information-schema-reconstructor.php | 33 +++++++++++++------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php index e0d2314..94565e4 100644 --- a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php +++ b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -30,9 +30,9 @@ public static function setUpBeforeClass(): void { $GLOBALS['table_prefix'] = 'wptests_'; } if ( ! isset( $GLOBALS['wpdb'] ) ) { - $GLOBALS['wpdb'] = new stdClass(); - $GLOBALS['wpdb']->suppress_errors = false; - $GLOBALS['wpdb']->show_errors = true; + $GLOBALS['wpdb'] = new class() { + public function set_prefix( string $prefix ): void {} + }; } // Mock symbols that are used for WordPress table reconstruction. diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 3ef4e14..02868fa 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -145,20 +145,33 @@ private function get_wp_create_table_statements(): array { throw new Exception( 'The "wp_get_db_schema()" function was not defined.' ); } - // Get schema for global tables and the main site. - $schema = wp_get_db_schema(); + /* + * At this point, WPDB may not yet be initialized, as we're configuring + * the database connection. Let's only populate the table names using + * the "$table_prefix" global so we can get correct table names. + */ + global $wpdb, $table_prefix; + $wpdb->set_prefix( $table_prefix ); + + // Get schema for global tables. + $schema = wp_get_db_schema( 'global' ); // For multisite installs, add schema definitions for all sites. if ( is_multisite() ) { - $site_ids = get_sites( - array( - 'fields' => 'ids', - 'number' => PHP_INT_MAX, - ) - ); - foreach ( $site_ids as $site_id ) { - $schema .= wp_get_db_schema( 'blog', $site_id ); + /* + * We need to use a database query over the "get_sites()" function, + * as WPDB may not yet initialized. Moreover, we need to get the IDs + * of all existing blogs, independent of any filters and actions that + * could possibly alter the results of a "get_sites()" call. + */ + $stmt = $this->driver->execute_sqlite_query( "SELECT blog_id FROM {$wpdb->blogs}" ); + $blog_ids = $stmt->fetchAll( PDO::FETCH_COLUMN ); + foreach ( $blog_ids as $blog_id ) { + $schema .= wp_get_db_schema( 'blog', (int) $blog_id ); } + } else { + // For single site installs, add schema for the main site. + $schema .= wp_get_db_schema( 'blog' ); } // Parse the schema. From d1dfd2c863fdc7bb8a01b7b6d758435ef76b5394 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Fri, 25 Apr 2025 21:15:23 +0200 Subject: [PATCH 28/36] Fix removing schema records for non-existent tables --- .../class-wp-sqlite-information-schema-reconstructor.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 02868fa..2dc9214 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -78,12 +78,14 @@ public function ensure_correct_information_schema(): void { // Remove information schema records for tables that don't exist. foreach ( $information_schema_tables as $table ) { if ( ! in_array( $table, $sqlite_tables, true ) ) { - $sql = sprintf( 'DROP %s', $this->quote_sqlite_identifier( $table ) ); + $sql = sprintf( 'DROP TABLE %s', $this->quote_sqlite_identifier( $table ) ); $ast = $this->driver->create_parser( $sql )->parse(); if ( null === $ast ) { throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' ); } - $this->schema_builder->record_drop_table( $ast ); + $this->schema_builder->record_drop_table( + $ast->get_first_descendant_node( 'dropStatement' ) + ); } } } From 859fb9f76ea3a2e93795e79ed247b9e89be28a25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jake=C5=A1?= Date: Sun, 27 Apr 2025 17:53:54 +0200 Subject: [PATCH 29/36] Improve CI job name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Adam Zieliński --- .github/workflows/verify-version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify-version.yml b/.github/workflows/verify-version.yml index c3ff28e..9bc9a61 100644 --- a/.github/workflows/verify-version.yml +++ b/.github/workflows/verify-version.yml @@ -8,7 +8,7 @@ on: jobs: verify-version: - name: Verify that plugin version matches SQLITE_DRIVER_VERSION constant + name: Assert the WordPress plugin header declares the same version as the SQLITE_DRIVER_VERSION constant runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 94e582cf508a1ffc0775e2179ae438dade2d89ad Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Mon, 28 Apr 2025 09:32:49 +0200 Subject: [PATCH 30/36] Always quote identifiers in driver and reconstructor --- tests/WP_SQLite_Driver_Tests.php | 4 +- .../sqlite-ast/class-wp-sqlite-driver.php | 124 ++++++++++++------ ...qlite-information-schema-reconstructor.php | 20 ++- 3 files changed, 101 insertions(+), 47 deletions(-) diff --git a/tests/WP_SQLite_Driver_Tests.php b/tests/WP_SQLite_Driver_Tests.php index 9ad2db4..cb6d46e 100644 --- a/tests/WP_SQLite_Driver_Tests.php +++ b/tests/WP_SQLite_Driver_Tests.php @@ -1229,14 +1229,14 @@ public function testColumnWithOnUpdate() { 'name' => '_wp_sqlite__tmp_table_created_at_on_update', 'tbl_name' => '_tmp_table', 'rootpage' => '0', - 'sql' => "CREATE TRIGGER \"_wp_sqlite__tmp_table_created_at_on_update\"\n\t\t\tAFTER UPDATE ON \"_tmp_table\"\n\t\t\tFOR EACH ROW\n\t\t\tBEGIN\n\t\t\t UPDATE \"_tmp_table\" SET \"created_at\" = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid;\n\t\t\tEND", + 'sql' => "CREATE TRIGGER `_wp_sqlite__tmp_table_created_at_on_update`\n\t\t\t\tAFTER UPDATE ON `_tmp_table`\n\t\t\t\tFOR EACH ROW\n\t\t\t\tBEGIN\n\t\t\t\t UPDATE `_tmp_table` SET `created_at` = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid;\n\t\t\t\tEND", ), (object) array( 'type' => 'trigger', 'name' => '_wp_sqlite__tmp_table_updated_at_on_update', 'tbl_name' => '_tmp_table', 'rootpage' => '0', - 'sql' => "CREATE TRIGGER \"_wp_sqlite__tmp_table_updated_at_on_update\"\n\t\t\tAFTER UPDATE ON \"_tmp_table\"\n\t\t\tFOR EACH ROW\n\t\t\tBEGIN\n\t\t\t UPDATE \"_tmp_table\" SET \"updated_at\" = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid;\n\t\t\tEND", + 'sql' => "CREATE TRIGGER `_wp_sqlite__tmp_table_updated_at_on_update`\n\t\t\t\tAFTER UPDATE ON `_tmp_table`\n\t\t\t\tFOR EACH ROW\n\t\t\t\tBEGIN\n\t\t\t\t UPDATE `_tmp_table` SET `updated_at` = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid;\n\t\t\t\tEND", ), ), $results diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index d30ea44..a990327 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -577,7 +577,10 @@ public function get_saved_driver_version(): string { $default_version = '0.0.0'; try { $stmt = $this->execute_sqlite_query( - sprintf( 'SELECT value FROM %s WHERE name = ?', self::GLOBAL_VARIABLES_TABLE_NAME ), + sprintf( + 'SELECT value FROM %s WHERE name = ?', + $this->quote_sqlite_identifier( self::GLOBAL_VARIABLES_TABLE_NAME ) + ), array( self::DRIVER_VERSION_VARIABLE_NAME ) ); return $stmt->fetchColumn() ?? $default_version; @@ -1212,7 +1215,11 @@ private function execute_delete_statement( WP_Parser_Node $node ): void { $select_list = array(); foreach ( $table_aliases as $table ) { - $select_list[] = "\"$table\".rowid AS \"{$table}_rowid\""; + $select_list[] = sprintf( + '%s.rowid AS %s', + $this->quote_sqlite_identifier( $table ), + $this->quote_sqlite_identifier( $table . '_rowid' ) + ); } $ids = $this->execute_sqlite_query( @@ -1288,7 +1295,10 @@ private function execute_create_table_statement( WP_Parser_Node $node ): void { if ( $subnode->has_child_node( 'ifNotExists' ) ) { $tables_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'tables' ); $table_exists = $this->execute_sqlite_query( - "SELECT 1 FROM $tables_table WHERE table_schema = ? AND table_name = ?", + sprintf( + 'SELECT 1 FROM %s WHERE table_schema = ? AND table_name = ?', + $this->quote_sqlite_identifier( $tables_table ) + ), array( $this->db_name, $table_name ) )->fetchColumn(); @@ -1329,7 +1339,10 @@ private function execute_alter_table_statement( WP_Parser_Node $node ): void { // Save all column names from the original table. $columns_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' ); $column_names = $this->execute_sqlite_query( - "SELECT COLUMN_NAME FROM $columns_table WHERE table_schema = ? AND table_name = ?", + sprintf( + 'SELECT COLUMN_NAME FROM %s WHERE table_schema = ? AND table_name = ?', + $this->quote_sqlite_identifier( $columns_table ) + ), array( $this->db_name, $table_name ) )->fetchAll( PDO::FETCH_COLUMN ); @@ -1437,9 +1450,9 @@ private function execute_truncate_table_statement( WP_Parser_Node $node ): void $this->translate( $node->get_first_child_node( 'tableRef' ) ) ); - $quoted_table_name = $this->quote_sqlite_identifier( $table_name ); - - $this->execute_sqlite_query( "DELETE FROM $quoted_table_name" ); + $this->execute_sqlite_query( + sprintf( 'DELETE FROM %s', $this->quote_sqlite_identifier( $table_name ) ) + ); try { $this->execute_sqlite_query( 'DELETE FROM sqlite_sequence WHERE name = ?', array( $table_name ) ); } catch ( PDOException $e ) { @@ -1576,7 +1589,7 @@ private function execute_show_index_statement( string $table_name ): void { INDEX_COMMENT AS `Index_comment`, IS_VISIBLE AS `Visible`, EXPRESSION AS `Expression` - FROM ' . $statistics_table . ' + FROM ' . $this->quote_sqlite_identifier( $statistics_table ) . ' WHERE table_schema = ? AND table_name = ? ORDER BY @@ -1620,7 +1633,8 @@ private function execute_show_table_status_statement( WP_Parser_Node $node ): vo ); $table_info = $this->execute_sqlite_query( sprintf( - "SELECT * FROM $tables_tables WHERE table_schema = ? %s", + 'SELECT * FROM %s WHERE table_schema = ? %s', + $this->quote_sqlite_identifier( $tables_tables ), $condition ?? '' ), array( $database ) @@ -1688,7 +1702,8 @@ private function execute_show_tables_statement( WP_Parser_Node $node ): void { ); $table_info = $this->execute_sqlite_query( sprintf( - "SELECT * FROM $table_tables WHERE table_schema = ? %s", + 'SELECT * FROM %s WHERE table_schema = ? %s', + $this->quote_sqlite_identifier( $table_tables ), $condition ?? '' ), array( $database ) @@ -1745,7 +1760,10 @@ private function execute_show_columns_statement( WP_Parser_Node $node ): void { // Check if the table exists. $tables_tables = $this->information_schema_builder->get_table_name( $table_is_temporary, 'tables' ); $table_exists = $this->execute_sqlite_query( - "SELECT 1 FROM $tables_tables WHERE table_schema = ? AND table_name = ?", + sprintf( + 'SELECT 1 FROM %s WHERE table_schema = ? AND table_name = ?', + $this->quote_sqlite_identifier( $tables_tables ) + ), array( $this->db_name, $table_name ) )->fetchColumn(); @@ -1763,10 +1781,11 @@ private function execute_show_columns_statement( WP_Parser_Node $node ): void { } // Fetch column information. - $column_info = $this->execute_sqlite_query( + $columns_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' ); + $column_info = $this->execute_sqlite_query( sprintf( 'SELECT * FROM %s WHERE table_schema = ? AND table_name = ? %s', - $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' ), + $this->quote_sqlite_identifier( $columns_table ), $condition ?? '' ), array( $database, $table_name ) @@ -1808,7 +1827,7 @@ private function execute_describe_statement( WP_Parser_Node $node ): void { $columns_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' ); $column_info = $this->execute_sqlite_query( - " + ' SELECT column_name AS `Field`, column_type AS `Type`, @@ -1816,10 +1835,10 @@ private function execute_describe_statement( WP_Parser_Node $node ): void { column_key AS `Key`, column_default AS `Default`, extra AS Extra - FROM $columns_table + FROM ' . $this->quote_sqlite_identifier( $columns_table ) . ' WHERE table_schema = ? AND table_name = ? - ", + ', array( $this->db_name, $table_name ) )->fetchAll( PDO::FETCH_OBJ ); @@ -2015,11 +2034,13 @@ private function execute_administration_statement( WP_Parser_Node $node ): void try { switch ( $first_token->id ) { case WP_MySQL_Lexer::ANALYZE_SYMBOL: - $stmt = $this->execute_sqlite_query( "ANALYZE $quoted_table_name" ); + $stmt = $this->execute_sqlite_query( sprintf( 'ANALYZE %s', $quoted_table_name ) ); $errors = $stmt->fetchAll( PDO::FETCH_COLUMN ); break; case WP_MySQL_Lexer::CHECK_SYMBOL: - $stmt = $this->execute_sqlite_query( "PRAGMA integrity_check($quoted_table_name)" ); + $stmt = $this->execute_sqlite_query( + sprintf( 'PRAGMA integrity_check(%s)', $quoted_table_name ) + ); $errors = $stmt->fetchAll( PDO::FETCH_COLUMN ); if ( 'ok' === $errors[0] ) { array_shift( $errors ); @@ -2842,7 +2863,10 @@ private function recreate_table_from_information_schema( if ( null === $column_map ) { $columns_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' ); $column_names = $this->execute_sqlite_query( - "SELECT COLUMN_NAME FROM $columns_table WHERE table_schema = ? AND table_name = ?", + sprintf( + 'SELECT COLUMN_NAME FROM %s WHERE table_schema = ? AND table_name = ?', + $this->quote_sqlite_identifier( $columns_table ) + ), array( $this->db_name, $table_name ) )->fetchAll( PDO::FETCH_COLUMN ); $column_map = array_combine( $column_names, $column_names ); @@ -2997,13 +3021,13 @@ private function translate_insert_or_replace_body_in_non_strict_mode( $is_temporary = $this->information_schema_builder->temporary_table_exists( $table_name ); $columns_table = $this->information_schema_builder->get_table_name( $is_temporary, 'columns' ); $columns = $this->execute_sqlite_query( - " + ' SELECT column_name, is_nullable, column_default, data_type, extra - FROM $columns_table + FROM ' . $this->quote_sqlite_identifier( $columns_table ) . ' WHERE table_schema = ? AND table_name = ? ORDER BY ordinal_position - ", + ', array( $this->db_name, $table_name ) )->fetchAll( PDO::FETCH_ASSOC ); @@ -3113,12 +3137,12 @@ private function translate_update_list_in_non_strict_mode( string $table_name, W $is_temporary = $this->information_schema_builder->temporary_table_exists( $table_name ); $columns_table = $this->information_schema_builder->get_table_name( $is_temporary, 'columns' ); $columns = $this->execute_sqlite_query( - " + ' SELECT column_name, is_nullable, data_type, column_default - FROM $columns_table + FROM ' . $this->quote_sqlite_identifier( $columns_table ) . ' WHERE table_schema = ? AND table_name = ? - ", + ', array( $this->db_name, $table_name ) )->fetchAll( PDO::FETCH_ASSOC ); $column_map = array_combine( array_column( $columns, 'COLUMN_NAME' ), $columns ); @@ -3176,9 +3200,9 @@ private function get_sqlite_create_table_statement( // 1. Get table info. $tables_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'tables' ); $table_info = $this->execute_sqlite_query( - " + ' SELECT * - FROM $tables_table + FROM ' . $this->quote_sqlite_identifier( $tables_table ) . " WHERE table_type = 'BASE TABLE' AND table_schema = ? AND table_name = ? @@ -3196,14 +3220,20 @@ private function get_sqlite_create_table_statement( // 2. Get column info. $columns_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' ); $column_info = $this->execute_sqlite_query( - "SELECT * FROM $columns_table WHERE table_schema = ? AND table_name = ?", + sprintf( + 'SELECT * FROM %s WHERE table_schema = ? AND table_name = ?', + $this->quote_sqlite_identifier( $columns_table ) + ), array( $this->db_name, $table_name ) )->fetchAll( PDO::FETCH_ASSOC ); // 3. Get index info, grouped by index name. $statistics_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'statistics' ); $constraint_info = $this->execute_sqlite_query( - "SELECT * FROM $statistics_table WHERE table_schema = ? AND table_name = ?", + sprintf( + 'SELECT * FROM %s WHERE table_schema = ? AND table_name = ?', + $this->quote_sqlite_identifier( $statistics_table ) + ), array( $this->db_name, $table_name ) )->fetchAll( PDO::FETCH_ASSOC ); @@ -3366,9 +3396,9 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str // 1. Get table info. $tables_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'tables' ); $table_info = $this->execute_sqlite_query( - " + ' SELECT * - FROM $tables_table + FROM ' . $this->quote_sqlite_identifier( $tables_table ) . " WHERE table_type = 'BASE TABLE' AND table_schema = ? AND table_name = ? @@ -3383,14 +3413,20 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str // 2. Get column info. $columns_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'columns' ); $column_info = $this->execute_sqlite_query( - "SELECT * FROM $columns_table WHERE table_schema = ? AND table_name = ?", + sprintf( + 'SELECT * FROM %s WHERE table_schema = ? AND table_name = ?', + $this->quote_sqlite_identifier( $columns_table ) + ), array( $this->db_name, $table_name ) )->fetchAll( PDO::FETCH_ASSOC ); // 3. Get index info, grouped by index name. $statistics_table = $this->information_schema_builder->get_table_name( $table_is_temporary, 'statistics' ); $constraint_info = $this->execute_sqlite_query( - "SELECT * FROM $statistics_table WHERE table_schema = ? AND table_name = ?", + sprintf( + 'SELECT * FROM %s WHERE table_schema = ? AND table_name = ?', + $this->quote_sqlite_identifier( $statistics_table ) + ), array( $this->db_name, $table_name ) )->fetchAll( PDO::FETCH_ASSOC ); @@ -3518,14 +3554,20 @@ private function get_column_on_update_trigger_query( string $table, string $colu // but currently that can't happen as we're not creating such tables. // See: https://www.sqlite.org/rowidtable.html $trigger_name = self::RESERVED_PREFIX . "{$table}_{$column}_on_update"; - return " - CREATE TRIGGER \"$trigger_name\" - AFTER UPDATE ON \"$table\" - FOR EACH ROW - BEGIN - UPDATE \"$table\" SET \"$column\" = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid; - END - "; + return sprintf( + ' + CREATE TRIGGER %s + AFTER UPDATE ON %s + FOR EACH ROW + BEGIN + UPDATE %s SET %s = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid; + END + ', + $this->quote_sqlite_identifier( $trigger_name ), + $this->quote_sqlite_identifier( $table ), + $this->quote_sqlite_identifier( $table ), + $this->quote_sqlite_identifier( $column ) + ); } /** diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index 2dc9214..af5ae3a 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -122,7 +122,10 @@ private function get_sqlite_table_names(): array { private function get_information_schema_table_names(): array { $tables_table = $this->schema_builder->get_table_name( false, 'tables' ); return $this->driver->execute_sqlite_query( - "SELECT table_name FROM $tables_table ORDER BY table_name" + sprintf( + 'SELECT table_name FROM %s ORDER BY table_name', + $this->quote_sqlite_identifier( $tables_table ) + ) )->fetchAll( PDO::FETCH_COLUMN ); } @@ -207,7 +210,10 @@ private function get_wp_create_table_statements(): array { private function generate_create_table_statement( string $table_name ): string { // Columns. $columns = $this->driver->execute_sqlite_query( - sprintf( 'PRAGMA table_xinfo("%s")', $table_name ) + sprintf( + 'PRAGMA table_xinfo(%s)', + $this->quote_sqlite_identifier( $table_name ) + ) )->fetchAll( PDO::FETCH_ASSOC ); $definitions = array(); @@ -245,7 +251,10 @@ private function generate_create_table_statement( string $table_name ): string { // Indexes and keys. $keys = $this->driver->execute_sqlite_query( - 'SELECT * FROM pragma_index_list("' . $table_name . '")' + sprintf( + 'PRAGMA index_list(%s)', + $this->quote_sqlite_identifier( $table_name ) + ) )->fetchAll( PDO::FETCH_ASSOC ); foreach ( $keys as $key ) { @@ -375,7 +384,10 @@ private function generate_key_definition( string $table_name, array $key_info, a // Key columns. $key_columns = $this->driver->execute_sqlite_query( - 'SELECT * FROM pragma_index_info("' . $key_info['name'] . '")' + sprintf( + 'PRAGMA index_info(%s)', + $this->quote_sqlite_identifier( $key_info['name'] ) + ) )->fetchAll( PDO::FETCH_ASSOC ); $cols = array(); foreach ( $key_columns as $column ) { From b0009d6ecf4bc3bae0775ec4c5350c85f000fa8c Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Mon, 28 Apr 2025 20:12:32 +0200 Subject: [PATCH 31/36] Extract SQLite connection management to WP_SQLite_Connection --- tests/WP_SQLite_Driver_Metadata_Tests.php | 6 +- tests/WP_SQLite_Driver_Query_Tests.php | 10 +- tests/WP_SQLite_Driver_Tests.php | 6 +- tests/WP_SQLite_Driver_Translation_Tests.php | 260 +++++++++--------- ...Information_Schema_Reconstructor_Tests.php | 44 ++- tests/bootstrap.php | 1 + tests/tools/dump-sqlite-query.php | 7 +- .../sqlite-ast/class-wp-sqlite-connection.php | 157 +++++++++++ .../sqlite-ast/class-wp-sqlite-driver.php | 133 +++------ ...s-wp-sqlite-information-schema-builder.php | 49 ++-- wp-includes/sqlite/class-wp-sqlite-db.php | 16 +- wp-includes/sqlite/install-functions.php | 6 +- 12 files changed, 381 insertions(+), 314 deletions(-) create mode 100644 wp-includes/sqlite-ast/class-wp-sqlite-connection.php diff --git a/tests/WP_SQLite_Driver_Metadata_Tests.php b/tests/WP_SQLite_Driver_Metadata_Tests.php index fde9968..41a212b 100644 --- a/tests/WP_SQLite_Driver_Metadata_Tests.php +++ b/tests/WP_SQLite_Driver_Metadata_Tests.php @@ -31,10 +31,8 @@ public static function setUpBeforeClass(): void { public function setUp(): void { $this->sqlite = new PDO( 'sqlite::memory:' ); $this->engine = new WP_SQLite_Driver( - array( - 'connection' => $this->sqlite, - 'database' => 'wp', - ) + new WP_SQLite_Connection( array( 'pdo' => $this->sqlite ) ), + 'wp' ); } diff --git a/tests/WP_SQLite_Driver_Query_Tests.php b/tests/WP_SQLite_Driver_Query_Tests.php index 54b0f5a..032926d 100644 --- a/tests/WP_SQLite_Driver_Query_Tests.php +++ b/tests/WP_SQLite_Driver_Query_Tests.php @@ -43,10 +43,8 @@ public function setUp(): void { $this->sqlite = new PDO( 'sqlite::memory:' ); $this->engine = new WP_SQLite_Driver( - array( - 'connection' => $this->sqlite, - 'database' => 'wp', - ) + new WP_SQLite_Connection( array( 'pdo' => $this->sqlite ) ), + 'wp' ); $translator = $this->engine; @@ -458,7 +456,7 @@ public function testRecoverSerialized() { ); $option_name = 'serialized_option'; $option_value = serialize( $obj ); - $option_value_escaped = $this->engine->get_pdo()->quote( $option_value ); + $option_value_escaped = $this->engine->get_connection()->quote( $option_value ); /* Note well: this is heredoc not nowdoc */ $insert = <<engine->get_pdo()->quote( $option_value ); + $option_value_escaped = $this->engine->get_connection()->quote( $option_value ); /* Note well: this is heredoc not nowdoc */ $insert = <<sqlite = new PDO( 'sqlite::memory:' ); $this->engine = new WP_SQLite_Driver( - array( - 'connection' => $this->sqlite, - 'database' => 'wp', - ) + new WP_SQLite_Connection( array( 'pdo' => $this->sqlite ) ), + 'wp' ); $this->engine->query( "CREATE TABLE _options ( diff --git a/tests/WP_SQLite_Driver_Translation_Tests.php b/tests/WP_SQLite_Driver_Translation_Tests.php index 86299e0..0b6f306 100644 --- a/tests/WP_SQLite_Driver_Translation_Tests.php +++ b/tests/WP_SQLite_Driver_Translation_Tests.php @@ -21,10 +21,8 @@ public static function setUpBeforeClass(): void { public function setUp(): void { $this->driver = new WP_SQLite_Driver( - array( - 'path' => ':memory:', - 'database' => 'wp', - ) + new WP_SQLite_Connection( array( 'path' => ':memory:' ) ), + 'wp' ); } @@ -212,9 +210,9 @@ public function testCreateTable(): void { . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'Dynamic', 'utf8mb4_general_ci')", 'INSERT INTO _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -235,9 +233,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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -256,9 +254,9 @@ public function testCreateTableWithBasicConstraints(): void { . " 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -276,9 +274,9 @@ public function testCreateTableWithEngine(): void { . " VALUES ('wp', 't', 'BASE TABLE', 'MyISAM', 'Fixed', 'utf8mb4_general_ci')", 'INSERT INTO _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -296,9 +294,9 @@ public function testCreateTableWithCollate(): void { . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'Dynamic', 'utf8mb4_czech_ci')", 'INSERT INTO _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -326,9 +324,9 @@ public function testCreateTableWithPrimaryKey(): void { . " VALUES ('wp', 't', 'id', 1, null, 'NO', 'int', null, null, 10, 0, null, null, null, 'int', 'PRI', '', 'select,insert,update,references', '', '', null)", 'INSERT INTO _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -348,9 +346,9 @@ public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { . " 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't1'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't1'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't1'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't1'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't1'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't1'", ) ); @@ -368,9 +366,9 @@ public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { . " 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't2'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't2'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't2'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't2'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't2'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't2'", ) ); @@ -390,9 +388,9 @@ public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { 'INSERT INTO _wp_sqlite_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)", "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't3' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't3' AND s.column_name = c.column_name", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't3'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't3'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't3'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't3'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't3'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't3'", ) ); } @@ -419,9 +417,9 @@ public function testCreateTableWithInlineUniqueIndexes(): void { . " VALUES ('wp', 't', 'name', 2, null, 'YES', 'text', 65535, 65535, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'text', 'UNI', '', 'select,insert,update,references', '', '', null)", 'INSERT INTO _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -452,9 +450,9 @@ public function testCreateTableWithStandaloneUniqueIndexes(): void { 'INSERT INTO _wp_sqlite_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)", "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -516,13 +514,13 @@ public function testAlterTableAddColumn(): void { $this->assertExecutedInformationSchemaQueries( array( - "SELECT COLUMN_NAME FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", 'INSERT INTO _wp_sqlite_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', 'a', 2, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -545,13 +543,13 @@ public function testAlterTableAddColumnWithNotNull(): void { $this->assertExecutedInformationSchemaQueries( array( - "SELECT COLUMN_NAME FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", 'INSERT INTO _wp_sqlite_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', 'a', 2, null, 'NO', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -574,13 +572,13 @@ public function testAlterTableAddColumnWithDefault(): void { $this->assertExecutedInformationSchemaQueries( array( - "SELECT COLUMN_NAME FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", 'INSERT INTO _wp_sqlite_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', 'a', 2, '0', 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -603,13 +601,13 @@ public function testAlterTableAddColumnWithNotNullAndDefault(): void { $this->assertExecutedInformationSchemaQueries( array( - "SELECT COLUMN_NAME FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", 'INSERT INTO _wp_sqlite_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', 'a', 2, '0', 'NO', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -632,7 +630,7 @@ public function testAlterTableAddMultipleColumns(): void { $this->assertExecutedInformationSchemaQueries( array( - "SELECT COLUMN_NAME FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", 'INSERT INTO _wp_sqlite_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', 'a', 2, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", @@ -642,9 +640,9 @@ public function testAlterTableAddMultipleColumns(): void { "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", 'INSERT INTO _wp_sqlite_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', 'c', 4, null, 'YES', 'tinyint', null, null, 3, 0, null, null, null, 'tinyint(1)', '', '', 'select,insert,update,references', '', '', null)", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -667,13 +665,13 @@ public function testAlterTableDropColumn(): void { $this->assertExecutedInformationSchemaQueries( array( - "SELECT COLUMN_NAME FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "DELETE FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", "DELETE FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -695,16 +693,16 @@ public function testAlterTableDropMultipleColumns(): void { $this->assertExecutedInformationSchemaQueries( array( - "SELECT COLUMN_NAME FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "DELETE FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", "DELETE FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", "DELETE FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'b'", "DELETE FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'b'", "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -727,16 +725,16 @@ public function testAlterTableAddAndDropColumns(): void { $this->assertExecutedInformationSchemaQueries( array( - "SELECT COLUMN_NAME FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", 'INSERT INTO _wp_sqlite_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', 2, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", "DELETE FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", "DELETE FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -759,16 +757,16 @@ public function testAlterTableDropAndAddSingleColumn(): void { $this->assertExecutedInformationSchemaQueries( array( - "SELECT COLUMN_NAME FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "DELETE FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", "DELETE FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", 'INSERT INTO _wp_sqlite_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', 'a', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", - "SELECT * FROM _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -787,9 +785,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -808,9 +806,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -837,9 +835,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -862,9 +860,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -887,9 +885,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -908,9 +906,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -931,9 +929,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -956,9 +954,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -979,9 +977,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1002,9 +1000,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1027,9 +1025,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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1046,9 +1044,9 @@ public function testEnumDataTypes(): void { . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'Dynamic', 'utf8mb4_general_ci')", 'INSERT INTO _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1073,9 +1071,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1094,9 +1092,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1119,9 +1117,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1144,9 +1142,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1167,9 +1165,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1188,9 +1186,9 @@ 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 _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1207,9 +1205,9 @@ public function testSerialDataTypes(): void { . " VALUES ('wp', 't', 'BASE TABLE', 'InnoDB', 'Dynamic', 'utf8mb4_general_ci')", 'INSERT INTO _wp_sqlite_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 _wp_sqlite_mysql_information_schema_tables WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT * FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", ) ); } @@ -1389,7 +1387,7 @@ function ( $param ) { return 'null'; } if ( is_string( $param ) ) { - return $this->driver->get_pdo()->quote( $param ); + return $this->driver->get_connection()->quote( $param ); } return $param; }, diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php index 94565e4..4d09198 100644 --- a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php +++ b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -57,16 +57,14 @@ function wp_get_db_schema() { public function setUp(): void { $this->sqlite = new PDO( 'sqlite::memory:' ); $this->engine = new WP_SQLite_Driver( - array( - 'connection' => $this->sqlite, - 'database' => 'wp', - ) + new WP_SQLite_Connection( array( 'pdo' => $this->sqlite ) ), + 'wp' ); $builder = new WP_SQLite_Information_Schema_Builder( 'wp', WP_SQLite_Driver::RESERVED_PREFIX, - array( $this->engine, 'execute_sqlite_query' ) + $this->engine->get_connection() ); $this->reconstructor = new WP_SQLite_Information_Schema_Reconstructor( @@ -76,7 +74,7 @@ public function setUp(): void { } public function testReconstructTable(): void { - $this->engine->get_pdo()->exec( + $this->engine->get_connection()->query( ' CREATE TABLE t ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -90,8 +88,8 @@ public function testReconstructTable(): void { ) ' ); - $this->engine->get_pdo()->exec( 'CREATE INDEX idx_score ON t (score)' ); - $this->engine->get_pdo()->exec( 'CREATE INDEX idx_role_score ON t (role, priority)' ); + $this->engine->get_connection()->query( 'CREATE INDEX idx_score ON t (score)' ); + $this->engine->get_connection()->query( 'CREATE INDEX idx_role_score ON t (role, priority)' ); $result = $this->assertQuery( 'SELECT * FROM information_schema.tables WHERE table_name = "t"' ); $this->assertEquals( 0, count( $result ) ); @@ -126,7 +124,7 @@ public function testReconstructTable(): void { public function testReconstructWpTable(): void { // Create a WP table with any columns. - $this->engine->get_pdo()->exec( 'CREATE TABLE wp_posts ( id INTEGER )' ); + $this->engine->get_connection()->query( 'CREATE TABLE wp_posts ( id INTEGER )' ); // Reconstruct the information schema. $this->reconstructor->ensure_correct_information_schema(); @@ -176,18 +174,18 @@ public function testReconstructWpTable(): void { } public function testReconstructTableFromMysqlDataTypesCache(): void { - $pdo = $this->engine->get_pdo(); + $connection = $this->engine->get_connection(); - $pdo->exec( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL ); - $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'id', 'int unsigned')" ); - $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'name', 'varchar(255)')" ); - $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'description', 'text')" ); - $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'shape', 'geomcollection')" ); - $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_name', 'KEY')" ); - $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_description', 'FULLTEXT')" ); - $pdo->exec( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_shape', 'SPATIAL')" ); + $connection->query( self::CREATE_DATA_TYPES_CACHE_TABLE_SQL ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'id', 'int unsigned')" ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'name', 'varchar(255)')" ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'description', 'text')" ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 'shape', 'geomcollection')" ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_name', 'KEY')" ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_description', 'FULLTEXT')" ); + $connection->query( "INSERT INTO _mysql_data_types_cache (`table`, column_or_index, mysql_type) VALUES ('t', 't__idx_shape', 'SPATIAL')" ); - $this->engine->get_pdo()->exec( + $connection->query( ' CREATE TABLE t ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -197,9 +195,9 @@ public function testReconstructTableFromMysqlDataTypesCache(): void { ) ' ); - $this->engine->get_pdo()->exec( 'CREATE INDEX t__idx_name ON t (name)' ); - $this->engine->get_pdo()->exec( 'CREATE INDEX t__idx_description ON t (description)' ); - $this->engine->get_pdo()->exec( 'CREATE INDEX t__idx_shape ON t (shape)' ); + $connection->query( 'CREATE INDEX t__idx_name ON t (name)' ); + $connection->query( 'CREATE INDEX t__idx_description ON t (description)' ); + $connection->query( 'CREATE INDEX t__idx_shape ON t (shape)' ); $this->reconstructor->ensure_correct_information_schema(); $result = $this->assertQuery( 'SHOW CREATE TABLE t' ); @@ -224,7 +222,7 @@ public function testReconstructTableFromMysqlDataTypesCache(): void { } public function testDefaultValues(): void { - $this->engine->get_pdo()->exec( + $this->engine->get_connection()->query( " CREATE TABLE t ( col1 text DEFAULT abc, diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 2f71bb9..0f0f59a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -14,6 +14,7 @@ require_once __DIR__ . '/../wp-includes/sqlite/class-wp-sqlite-token.php'; require_once __DIR__ . '/../wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php'; require_once __DIR__ . '/../wp-includes/sqlite/class-wp-sqlite-translator.php'; +require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-connection.php'; require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-configurator.php'; require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-driver.php'; require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php'; diff --git a/tests/tools/dump-sqlite-query.php b/tests/tools/dump-sqlite-query.php index b0f5453..bc02a64 100644 --- a/tests/tools/dump-sqlite-query.php +++ b/tests/tools/dump-sqlite-query.php @@ -8,6 +8,7 @@ require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-token.php'; require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-parser.php'; require_once __DIR__ . '/../../wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php'; +require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-connection.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-configurator.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php'; @@ -15,10 +16,8 @@ require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php'; $driver = new WP_SQLite_Driver( - array( - 'path' => ':memory:', - 'database' => 'wp', - ) + new WP_SQLite_Connection( array( 'path' => ':memory:' ) ), + 'wp' ); $query = "SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.t1_id WHERE t1.name = 'abc'"; diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-connection.php b/wp-includes/sqlite-ast/class-wp-sqlite-connection.php new file mode 100644 index 0000000..5082a49 --- /dev/null +++ b/wp-includes/sqlite-ast/class-wp-sqlite-connection.php @@ -0,0 +1,157 @@ +pdo = $options['pdo']; + } else { + if ( ! isset( $options['path'] ) || ! is_string( $options['path'] ) ) { + throw new InvalidArgumentException( 'Option "path" is required when "connection" is not provided.' ); + } + $this->pdo = new PDO( 'sqlite:' . $options['path'] ); + } + + // Throw exceptions on error. + $this->pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + + // Configure SQLite timeout. + if ( isset( $options['timeout'] ) && is_int( $options['timeout'] ) ) { + $timeout = $options['timeout']; + } else { + $timeout = self::DEFAULT_SQLITE_TIMEOUT; + } + $this->pdo->setAttribute( PDO::ATTR_TIMEOUT, $timeout ); + + // Return all values (except null) as strings. + $this->pdo->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true ); + + // Configure SQLite journal mode. + $journal_mode = $options['journal_mode'] ?? null; + if ( $journal_mode && in_array( $journal_mode, self::SQLITE_JOURNAL_MODES, true ) ) { + $this->query( 'PRAGMA journal_mode = ' . $journal_mode ); + } + } + + /** + * Execute a query in SQLite. + * + * @param string $sql The query to execute. + * @param array $params The query parameters. + * @throws PDOException When the query execution fails. + * @return PDOStatement The PDO statement object. + */ + public function query( string $sql, array $params = array() ): PDOStatement { + if ( $this->query_logger ) { + ( $this->query_logger )( $sql, $params ); + } + $stmt = $this->pdo->prepare( $sql ); + $stmt->execute( $params ); + return $stmt; + } + + /** + * Returns the ID of the last inserted row. + * + * @return string The ID of the last inserted row. + */ + public function get_last_insert_id(): string { + return $this->pdo->lastInsertId(); + } + + /** + * Quote a value for use in a query. + * + * @param mixed $value The value to quote. + * @param int $type The type of the value. + * @return string The quoted value. + */ + public function quote( $value, int $type = PDO::PARAM_STR ): string { + return $this->pdo->quote( $value, $type ); + } + + /** + * Get the PDO object. + * + * @return PDO + */ + public function get_pdo(): PDO { + return $this->pdo; + } + + /** + * Set a logger for the queries. + * + * @param callable(string, array): void $logger A query logger callback. + */ + public function set_query_logger( callable $logger ): void { + $this->query_logger = $logger; + } +} diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index a990327..44cbc9d 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -28,11 +28,6 @@ class WP_SQLite_Driver { */ const MINIMUM_SQLITE_VERSION = '3.37.0'; - /** - * The default timeout in seconds for SQLite to wait for a writable lock. - */ - const DEFAULT_SQLITE_TIMEOUT = 10; - /** * An identifier prefix for internal database objects. * @@ -342,11 +337,11 @@ class WP_SQLite_Driver { private $db_name; /** - * An instance of the PDO object. + * An instance of the SQLite connection. * - * @var PDO + * @var WP_SQLite_Connection */ - private $pdo; + private $connection; /** * A service for managing MySQL INFORMATION_SCHEMA tables in SQLite. @@ -435,66 +430,15 @@ class WP_SQLite_Driver { * * Set up an SQLite connection and the MySQL-on-SQLite driver. * - * @param array $options { - * An array of options. - * - * @type string $database Database name. - * The name of the emulated MySQL database. - * @type string|null $path Optional. SQLite database path. - * For in-memory database, use ':memory:'. - * Must be set when PDO instance is not provided. - * @type PDO|null $connection Optional. PDO instance with SQLite connection. - * If not provided, a new PDO instance will be created. - * @type int|null $timeout Optional. SQLite timeout in seconds. - * The time to wait for a writable lock. - * @type string|null $sqlite_journal_mode Optional. SQLite journal mode. - * } + * @param WP_SQLite_Connection $connection A SQLite database connection. + * @param string $database The database name. * * @throws WP_SQLite_Driver_Exception When the driver initialization fails. */ - public function __construct( array $options ) { - // Database name. - if ( ! isset( $options['database'] ) || ! is_string( $options['database'] ) ) { - throw $this->new_driver_exception( 'Option "database" is required.' ); - } - $this->main_db_name = $options['database']; - $this->db_name = $this->main_db_name; - - // Database connection. - if ( isset( $options['connection'] ) && $options['connection'] instanceof PDO ) { - $this->pdo = $options['connection']; - } - - // Create a PDO connection if it is not provided. - if ( ! $this->pdo ) { - if ( ! isset( $options['path'] ) || ! is_string( $options['path'] ) ) { - throw $this->new_driver_exception( - 'Option "path" is required when "connection" is not provided.' - ); - } - $path = $options['path']; - - try { - $this->pdo = new PDO( 'sqlite:' . $path ); - } catch ( PDOException $e ) { - $code = $e->getCode(); - throw $this->new_driver_exception( $e->getMessage(), is_int( $code ) ? $code : 0, $e ); - } - } - - // Throw exceptions on error. - $this->pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); - - // Configure SQLite timeout. - if ( isset( $options['timeout'] ) && is_int( $options['timeout'] ) ) { - $timeout = $options['timeout']; - } else { - $timeout = self::DEFAULT_SQLITE_TIMEOUT; - } - $this->pdo->setAttribute( PDO::ATTR_TIMEOUT, $timeout ); - - // Return all values (except null) as strings. - $this->pdo->setAttribute( PDO::ATTR_STRINGIFY_FETCHES, true ); + public function __construct( WP_SQLite_Connection $connection, string $database ) { + $this->connection = $connection; + $this->main_db_name = $database; + $this->db_name = $database; // Check the SQLite version. $sqlite_version = $this->get_sqlite_version(); @@ -512,22 +456,10 @@ public function __construct( array $options ) { $this->client_info = $sqlite_version; // Enable foreign keys. By default, they are off. - $this->pdo->query( 'PRAGMA foreign_keys = ON' ); - - // Configure SQLite journal mode. - if ( - isset( $options['sqlite_journal_mode'] ) - && in_array( - $options['sqlite_journal_mode'], - array( 'DELETE', 'TRUNCATE', 'PERSIST', 'MEMORY', 'WAL', 'OFF' ), - true - ) - ) { - $this->pdo->query( 'PRAGMA journal_mode = ' . $options['sqlite_journal_mode'] ); - } + $this->connection->query( 'PRAGMA foreign_keys = ON' ); // Register SQLite functions. - WP_SQLite_PDO_User_Defined_Functions::register_for( $this->pdo ); + WP_SQLite_PDO_User_Defined_Functions::register_for( $this->connection->get_pdo() ); // Load MySQL grammar. if ( null === self::$mysql_grammar ) { @@ -538,21 +470,30 @@ public function __construct( array $options ) { $this->information_schema_builder = new WP_SQLite_Information_Schema_Builder( $this->main_db_name, self::RESERVED_PREFIX, - array( $this, 'execute_sqlite_query' ) + $this->connection ); // Ensure that the database is configured. $migrator = new WP_SQLite_Configurator( $this, $this->information_schema_builder ); $migrator->ensure_database_configured(); + + $this->connection->set_query_logger( + function ( string $sql, array $params ) { + $this->last_sqlite_queries[] = array( + 'sql' => $sql, + 'params' => $params, + ); + } + ); } /** - * Get the PDO object. + * Get the SQLite connection instance. * - * @return PDO + * @return WP_SQLite_Connection */ - public function get_pdo(): PDO { - return $this->pdo; + public function get_connection(): WP_SQLite_Connection { + return $this->connection; } /** @@ -561,7 +502,7 @@ public function get_pdo(): PDO { * @return string SQLite engine version as a string. */ public function get_sqlite_version(): string { - return $this->pdo->query( 'SELECT SQLITE_VERSION()' )->fetchColumn(); + return $this->connection->query( 'SELECT SQLITE_VERSION()' )->fetchColumn(); } /** @@ -626,7 +567,7 @@ public function get_last_sqlite_queries(): array { * @return int|string */ public function get_insert_id() { - $last_insert_id = $this->pdo->lastInsertId(); + $last_insert_id = $this->connection->get_last_insert_id(); if ( is_numeric( $last_insert_id ) ) { $last_insert_id = (int) $last_insert_id; } @@ -775,13 +716,7 @@ public function get_last_return_value() { * @return PDOStatement The PDO statement object. */ public function execute_sqlite_query( string $sql, array $params = array() ): PDOStatement { - $this->last_sqlite_queries[] = array( - 'sql' => $sql, - 'params' => $params, - ); - $stmt = $this->pdo->prepare( $sql ); - $stmt->execute( $params ); - return $stmt; + return $this->connection->query( $sql, $params ); } /** @@ -2238,7 +2173,7 @@ private function translate( $node ): ?string { $name = strtolower( $original_name ); $type = $type_token ? $type_token->id : WP_MySQL_Lexer::SESSION_SYMBOL; if ( 'sql_mode' === $name ) { - $value = $this->pdo->quote( implode( ',', $this->active_sql_modes ) ); + $value = $this->connection->quote( implode( ',', $this->active_sql_modes ) ); } else { // When we have no value, it's reasonable to use NULL. $value = 'NULL'; @@ -3089,7 +3024,7 @@ private function translate_insert_or_replace_body_in_non_strict_mode( if ( null === $default && ! $is_nullable && ! $is_auto_inc ) { $default = self::DATA_TYPE_IMPLICIT_DEFAULT_MAP[ $column['DATA_TYPE'] ] ?? null; } - $fragment .= null === $default ? 'NULL' : $this->pdo->quote( $default ); + $fragment .= null === $default ? 'NULL' : $this->connection->quote( $default ); } else { // When a column value is included, we can use it without change. $position = array_search( $column['COLUMN_NAME'], $insert_list, true ); @@ -3163,7 +3098,7 @@ private function translate_update_list_in_non_strict_mode( string $table_name, W // Get the UPDATE value. It's either an expression or a DEFAULT keyword. if ( null === $expr ) { // Emulate "column = DEFAULT". - $value = null === $default ? 'NULL' : $this->pdo->quote( $default ); + $value = null === $default ? 'NULL' : $this->connection->quote( $default ); } else { $value = $this->translate( $expr ); } @@ -3171,7 +3106,7 @@ private function translate_update_list_in_non_strict_mode( string $table_name, W // If the column is NOT NULL, a NULL value resolves to implicit default. $implicit_default = self::DATA_TYPE_IMPLICIT_DEFAULT_MAP[ $data_type ] ?? null; if ( ! $is_nullable && null !== $implicit_default ) { - $value = sprintf( 'COALESCE(%s, %s)', $value, $this->pdo->quote( $implicit_default ) ); + $value = sprintf( 'COALESCE(%s, %s)', $value, $this->connection->quote( $implicit_default ) ); } // Compose the UPDATE list item. @@ -3304,7 +3239,7 @@ private function get_sqlite_create_table_statement( ) { $query .= ' DEFAULT CURRENT_TIMESTAMP'; } else { - $query .= ' DEFAULT ' . $this->pdo->quote( $column['COLUMN_DEFAULT'] ); + $query .= ' DEFAULT ' . $this->connection->quote( $column['COLUMN_DEFAULT'] ); } } $rows[] = $query; @@ -3461,7 +3396,7 @@ private function get_mysql_create_table_statement( bool $table_is_temporary, str ) { $sql .= ' DEFAULT CURRENT_TIMESTAMP'; } elseif ( null !== $column['COLUMN_DEFAULT'] ) { - $sql .= ' DEFAULT ' . $this->pdo->quote( $column['COLUMN_DEFAULT'] ); + $sql .= ' DEFAULT ' . $this->connection->quote( $column['COLUMN_DEFAULT'] ); } elseif ( 'YES' === $column['IS_NULLABLE'] ) { $sql .= ' DEFAULT NULL'; } 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 6ddc96e..3b64742 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 @@ -358,25 +358,22 @@ class WP_SQLite_Information_Schema_Builder { private $temporary_information_schema_exists = false; /** - * Query callback. + * An instance of the SQLite connection. * - * @TODO: Consider extracting a part of the WP_SQLite_Driver class - * to a class like "WP_SQLite_Connection" and reuse it in both. - * - * @var callable(string, array): PDOStatement + * @var WP_SQLite_Connection */ - private $query_callback; + private $connection; /** * Constructor. * - * @param string $database Database name. - * @param string $reserved_prefix An identifier prefix for internal database objects. - * @param callable(string, array): PDOStatement $query_callback A callback that executes an SQLite query. + * @param string $database Database name. + * @param string $reserved_prefix An identifier prefix for internal database objects. + * @param WP_SQLite_Connection $connection An instance of the SQLite connection. */ - public function __construct( string $database, string $reserved_prefix, callable $query_callback ) { + public function __construct( string $database, string $reserved_prefix, WP_SQLite_Connection $connection ) { $this->db_name = $database; - $this->query_callback = $query_callback; + $this->connection = $connection; $this->table_prefix = $reserved_prefix . 'mysql_information_schema_'; $this->temporary_table_prefix = $reserved_prefix . 'mysql_information_schema_tmp_'; } @@ -404,7 +401,7 @@ public function temporary_table_exists( string $table_name ): bool { * We could search in the "{$this->temporary_table_prefix}tables" table, * but it may not exist yet, so using "sqlite_temp_schema" is simpler. */ - $stmt = $this->query( + $stmt = $this->connection->query( "SELECT 1 FROM sqlite_temp_schema WHERE type = 'table' AND name = ?", array( $table_name ) ); @@ -417,7 +414,7 @@ public function temporary_table_exists( string $table_name ): bool { */ public function ensure_information_schema_tables(): void { foreach ( self::CREATE_INFORMATION_SCHEMA_QUERIES as $query ) { - $this->query( str_replace( '', $this->table_prefix, $query ) ); + $this->connection->query( str_replace( '', $this->table_prefix, $query ) ); } } @@ -428,7 +425,7 @@ public function ensure_information_schema_tables(): void { public function ensure_temporary_information_schema_tables(): void { foreach ( self::CREATE_INFORMATION_SCHEMA_QUERIES as $query ) { $query = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $query ); - $this->query( str_replace( '', $this->temporary_table_prefix, $query ) ); + $this->connection->query( str_replace( '', $this->temporary_table_prefix, $query ) ); } $this->temporary_information_schema_exists = true; } @@ -673,7 +670,7 @@ private function record_add_column( WP_Parser_Node $node ): void { $columns_table_name = $this->get_table_name( $table_is_temporary, 'columns' ); - $position = $this->query( + $position = $this->connection->query( " SELECT MAX(ordinal_position) FROM $columns_table_name @@ -893,7 +890,7 @@ private function record_add_constraint( $column_names = array_filter( $key_part_column_names ); if ( count( $column_names ) > 0 ) { $columns_table_name = $this->get_table_name( $table_is_temporary, 'columns' ); - $column_info = $this->query( + $column_info = $this->connection->query( " SELECT column_name, data_type, is_nullable, character_maximum_length FROM $columns_table_name @@ -1078,7 +1075,7 @@ private function sync_column_key_info( bool $table_is_temporary, string $table_n // @TODO: Consider listing only affected columns. $columns_table_name = $this->get_table_name( $table_is_temporary, 'columns' ); $statistics_table_name = $this->get_table_name( $table_is_temporary, 'statistics' ); - $this->query( + $this->connection->query( " WITH s AS ( SELECT @@ -1892,7 +1889,7 @@ private function get_value( WP_Parser_Node $node ): string { * @param array $data The data to insert (key is column name, value is column value). */ private function insert_values( string $table_name, array $data ): void { - $this->query( + $this->connection->query( ' INSERT INTO ' . $table_name . ' (' . implode( ', ', array_keys( $data ) ) . ') VALUES (' . implode( ', ', array_fill( 0, count( $data ), '?' ) ) . ') @@ -1919,7 +1916,7 @@ private function update_values( string $table_name, array $data, array $where ): $where_clause[] = $column . ' = ?'; } - $this->query( + $this->connection->query( ' UPDATE ' . $table_name . ' SET ' . implode( ', ', $set ) . ' @@ -1941,7 +1938,7 @@ private function delete_values( string $table_name, array $where ): void { $where_clause[] = $column . ' = ?'; } - $this->query( + $this->connection->query( ' DELETE FROM ' . $table_name . ' WHERE ' . implode( ' AND ', $where_clause ) . ' @@ -1949,16 +1946,4 @@ private function delete_values( string $table_name, array $where ): void { array_values( $where ) ); } - - /** - * Execute an SQLite query. - * - * @param string $query The query to execute. - * @param array $params The query parameters. - * - * @return PDOStatement - */ - private function query( string $query, array $params = array() ) { - return ( $this->query_callback )( $query, $params ); - } } diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/wp-includes/sqlite/class-wp-sqlite-db.php index c625708..418c349 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/wp-includes/sqlite/class-wp-sqlite-db.php @@ -314,6 +314,7 @@ public function db_connect( $allow_bail = true ) { require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-token.php'; require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-lexer.php'; require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-parser.php'; + require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-connection.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-configurator.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver.php'; require_once __DIR__ . '/../../wp-includes/sqlite-ast/class-wp-sqlite-driver-exception.php'; @@ -322,26 +323,27 @@ public function db_connect( $allow_bail = true ) { $this->ensure_database_directory( FQDB ); try { - $this->dbh = new WP_SQLite_Driver( + $connection = new WP_SQLite_Connection( array( - 'connection' => $pdo, - 'path' => FQDB, - 'database' => $this->dbname, - 'sqlite_journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null, + 'pdo' => $pdo, + 'path' => FQDB, + 'journal_mode' => defined( 'SQLITE_JOURNAL_MODE' ) ? SQLITE_JOURNAL_MODE : null, ) ); + $this->dbh = new WP_SQLite_Driver( $connection, $this->dbname ); + $GLOBALS['@pdo'] = $this->dbh->get_connection()->get_pdo(); } catch ( Throwable $e ) { $this->last_error = $this->format_error_message( $e ); } } else { $this->dbh = new WP_SQLite_Translator( $pdo ); $this->last_error = $this->dbh->get_error_message(); + $GLOBALS['@pdo'] = $this->dbh->get_pdo(); } if ( $this->last_error ) { return false; } - $GLOBALS['@pdo'] = $this->dbh->get_pdo(); - $this->ready = true; + $this->ready = true; $this->set_sql_mode(); } diff --git a/wp-includes/sqlite/install-functions.php b/wp-includes/sqlite/install-functions.php index bdfb006..022afc8 100644 --- a/wp-includes/sqlite/install-functions.php +++ b/wp-includes/sqlite/install-functions.php @@ -35,10 +35,8 @@ function sqlite_make_db_sqlite() { if ( defined( 'WP_SQLITE_AST_DRIVER' ) && WP_SQLITE_AST_DRIVER ) { $translator = new WP_SQLite_Driver( - array( - 'database' => $wpdb->dbname, - 'connection' => $pdo, - ) + new WP_SQLite_Connection( array( 'pdo' => $pdo ) ), + $wpdb->dbname ); } else { $translator = new WP_SQLite_Translator( $pdo ); From 1d284e6eacc1557d8afb3c5a8cab48573c55b37d Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Mon, 28 Apr 2025 20:52:24 +0200 Subject: [PATCH 32/36] Consolidate SQLite identifier escaping in WP_SQLite_Connection, improve docs --- .../sqlite-ast/class-wp-sqlite-connection.php | 41 ++++++++++++++++++ .../sqlite-ast/class-wp-sqlite-driver.php | 4 +- ...qlite-information-schema-reconstructor.php | 42 +++++++++---------- 3 files changed, 61 insertions(+), 26 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-connection.php b/wp-includes/sqlite-ast/class-wp-sqlite-connection.php index 5082a49..f79e9b5 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-connection.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-connection.php @@ -137,6 +137,47 @@ public function quote( $value, int $type = PDO::PARAM_STR ): string { return $this->pdo->quote( $value, $type ); } + /** + * Quote an SQLite identifier. + * + * Wraps the identifier in backticks and escapes backtick characters within. + * + * --- + * + * Quoted identifiers in SQLite are represented by string constants: + * + * A string constant is formed by enclosing the string in single quotes ('). + * A single quote within the string can be encoded by putting two single + * quotes in a row - as in Pascal. C-style escapes using the backslash + * character are not supported because they are not standard SQL. + * + * See: https://www.sqlite.org/lang_expr.html#literal_values_constants_ + * + * Although sparsely documented, this applies to backtick and double quoted + * string constants as well, so only the quote character needs to be escaped. + * + * For more details, see the grammar for SQLite table and column names: + * + * - https://github.com/sqlite/sqlite/blob/873fc5dff2a781251f2c9bd2c791a5fac45b7a2b/src/tokenize.c#L395-L419 + * - https://github.com/sqlite/sqlite/blob/873fc5dff2a781251f2c9bd2c791a5fac45b7a2b/src/parse.y#L321-L338 + * + * --- + * + * We use backtick quotes instead of the SQL standard double quotes, due to + * an SQLite quirk causing double-quoted strings to be accepted as literals: + * + * This misfeature means that a misspelled double-quoted identifier will + * be interpreted as a string literal, rather than generating an error. + * + * See: https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted + * + * @param string $unquoted_identifier The unquoted identifier value. + * @return string The quoted identifier value. + */ + public function quote_identifier( string $unquoted_identifier ): string { + return '`' . str_replace( '`', '``', $unquoted_identifier ) . '`'; + } + /** * Get the PDO object. * diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index 44cbc9d..722696f 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -3525,13 +3525,11 @@ private function unquote_sqlite_identifier( string $quoted_identifier ): string /** * Quote an SQLite identifier. * - * Wrap the identifier in backticks and escape backtick values within. - * * @param string $unquoted_identifier The unquoted identifier value. * @return string The quoted identifier value. */ private function quote_sqlite_identifier( string $unquoted_identifier ): string { - return '`' . str_replace( '`', '``', $unquoted_identifier ) . '`'; + return $this->connection->quote_identifier( $unquoted_identifier ); } diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index af5ae3a..a025159 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -23,6 +23,13 @@ class WP_SQLite_Information_Schema_Reconstructor { */ private $driver; + /** + * An instance of the SQLite connection. + * + * @var WP_SQLite_Connection + */ + private $connection; + /** * A service for managing MySQL INFORMATION_SCHEMA tables in SQLite. * @@ -41,6 +48,7 @@ public function __construct( WP_SQLite_Information_Schema_Builder $schema_builder ) { $this->driver = $driver; + $this->connection = $driver->get_connection(); $this->schema_builder = $schema_builder; } @@ -78,7 +86,7 @@ public function ensure_correct_information_schema(): void { // Remove information schema records for tables that don't exist. foreach ( $information_schema_tables as $table ) { if ( ! in_array( $table, $sqlite_tables, true ) ) { - $sql = sprintf( 'DROP TABLE %s', $this->quote_sqlite_identifier( $table ) ); + $sql = sprintf( 'DROP TABLE %s', $this->connection->quote_identifier( $table ) ); // TODO: mysql quote $ast = $this->driver->create_parser( $sql )->parse(); if ( null === $ast ) { throw new WP_SQLite_Driver_Exception( $this->driver, 'Failed to parse the MySQL query.' ); @@ -124,7 +132,7 @@ private function get_information_schema_table_names(): array { return $this->driver->execute_sqlite_query( sprintf( 'SELECT table_name FROM %s ORDER BY table_name', - $this->quote_sqlite_identifier( $tables_table ) + $this->connection->quote_identifier( $tables_table ) ) )->fetchAll( PDO::FETCH_COLUMN ); } @@ -212,7 +220,7 @@ private function generate_create_table_statement( string $table_name ): string { $columns = $this->driver->execute_sqlite_query( sprintf( 'PRAGMA table_xinfo(%s)', - $this->quote_sqlite_identifier( $table_name ) + $this->connection->quote_identifier( $table_name ) ) )->fetchAll( PDO::FETCH_ASSOC ); @@ -244,7 +252,7 @@ private function generate_create_table_statement( string $table_name ): string { if ( count( $pk_columns ) > 0 ) { $quoted_pk_columns = array(); foreach ( $pk_columns as $pk_column ) { - $quoted_pk_columns[] = $this->quote_sqlite_identifier( $pk_column ); + $quoted_pk_columns[] = $this->connection->quote_identifier( $pk_column ); } $definitions[] = sprintf( 'PRIMARY KEY (%s)', implode( ', ', $quoted_pk_columns ) ); } @@ -253,7 +261,7 @@ private function generate_create_table_statement( string $table_name ): string { $keys = $this->driver->execute_sqlite_query( sprintf( 'PRAGMA index_list(%s)', - $this->quote_sqlite_identifier( $table_name ) + $this->connection->quote_identifier( $table_name ) ) )->fetchAll( PDO::FETCH_ASSOC ); @@ -268,7 +276,7 @@ private function generate_create_table_statement( string $table_name ): string { return sprintf( "CREATE TABLE %s (\n %s\n)", - $this->quote_sqlite_identifier( $table_name ), + $this->connection->quote_identifier( $table_name ), implode( ",\n ", $definitions ) ); } @@ -284,7 +292,7 @@ private function generate_create_table_statement( string $table_name ): string { */ private function generate_column_definition( string $table_name, array $column_info ): string { $definition = array(); - $definition[] = $this->quote_sqlite_identifier( $column_info['name'] ); + $definition[] = $this->connection->quote_identifier( $column_info['name'] ); // Data type. $mysql_type = $this->get_cached_mysql_data_type( $table_name, $column_info['name'] ); @@ -379,14 +387,14 @@ private function generate_key_definition( string $table_name, array $key_info, a * the generated MySQL definition, they follow implicit MySQL naming. */ if ( ! str_starts_with( $name, 'sqlite_autoindex_' ) ) { - $definition[] = $this->quote_sqlite_identifier( $name ); + $definition[] = $this->connection->quote_identifier( $name ); } // Key columns. $key_columns = $this->driver->execute_sqlite_query( sprintf( 'PRAGMA index_info(%s)', - $this->quote_sqlite_identifier( $key_info['name'] ) + $this->connection->quote_identifier( $key_info['name'] ) ) )->fetchAll( PDO::FETCH_ASSOC ); $cols = array(); @@ -423,11 +431,11 @@ private function generate_key_definition( string $table_name, array $key_info, a ) { $cols[] = sprintf( '%s(%d)', - $this->quote_sqlite_identifier( $column['name'] ), + $this->connection->quote_identifier( $column['name'] ), min( $column_length ?? $max_prefix_length, $max_prefix_length ) ); } else { - $cols[] = $this->quote_sqlite_identifier( $column['name'] ); + $cols[] = $this->connection->quote_identifier( $column['name'] ); } } @@ -640,18 +648,6 @@ private function escape_mysql_string_literal( string $literal ): string { return "'" . addcslashes( $literal, "\0\n\r'\"\Z" ) . "'"; } - /** - * Quote an SQLite identifier. - * - * Wrap the identifier in backticks and escape backtick values within. - * - * @param string $unquoted_identifier The unquoted identifier value. - * @return string The quoted identifier value. - */ - private function quote_sqlite_identifier( string $unquoted_identifier ): string { - return '`' . str_replace( '`', '``', $unquoted_identifier ) . '`'; - } - /** * Unquote a quoted MySQL identifier. * From 4df273e6f962cee4861ba97fcc053e43a7e05048 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 29 Apr 2025 09:40:30 +0200 Subject: [PATCH 33/36] Fix and refactor test setup --- tests/WP_SQLite_Driver_Metadata_Tests.php | 24 +++---------------- tests/WP_SQLite_Driver_Query_Tests.php | 24 +++---------------- tests/WP_SQLite_Driver_Tests.php | 24 +++---------------- ...Information_Schema_Reconstructor_Tests.php | 14 ----------- tests/WP_SQLite_Metadata_Tests.php | 24 +++---------------- tests/WP_SQLite_Query_Tests.php | 24 +++---------------- tests/WP_SQLite_Translator_Tests.php | 24 +++---------------- tests/bootstrap.php | 11 +++++++++ 8 files changed, 29 insertions(+), 140 deletions(-) diff --git a/tests/WP_SQLite_Driver_Metadata_Tests.php b/tests/WP_SQLite_Driver_Metadata_Tests.php index 41a212b..23c287f 100644 --- a/tests/WP_SQLite_Driver_Metadata_Tests.php +++ b/tests/WP_SQLite_Driver_Metadata_Tests.php @@ -3,29 +3,11 @@ use PHPUnit\Framework\TestCase; class WP_SQLite_Driver_Metadata_Tests extends TestCase { - + /** @var WP_SQLite_Driver */ private $engine; - private $sqlite; - public static function setUpBeforeClass(): void { - // if ( ! defined( 'PDO_DEBUG' )) { - // define( 'PDO_DEBUG', true ); - // } - if ( ! defined( 'FQDB' ) ) { - define( 'FQDB', ':memory:' ); - define( 'FQDBDIR', __DIR__ . '/../testdb' ); - } - error_reporting( E_ALL & ~E_DEPRECATED ); - if ( ! isset( $GLOBALS['table_prefix'] ) ) { - $GLOBALS['table_prefix'] = 'wptests_'; - } - if ( ! isset( $GLOBALS['wpdb'] ) ) { - $GLOBALS['wpdb'] = new stdClass(); - $GLOBALS['wpdb']->suppress_errors = false; - $GLOBALS['wpdb']->show_errors = true; - } - return; - } + /** @var PDO */ + private $sqlite; // Before each test, we create a new database public function setUp(): void { diff --git a/tests/WP_SQLite_Driver_Query_Tests.php b/tests/WP_SQLite_Driver_Query_Tests.php index 032926d..636813f 100644 --- a/tests/WP_SQLite_Driver_Query_Tests.php +++ b/tests/WP_SQLite_Driver_Query_Tests.php @@ -6,29 +6,11 @@ * Unit tests using the WordPress table definitions. */ class WP_SQLite_Driver_Query_Tests extends TestCase { - + /** @var WP_SQLite_Driver */ private $engine; - private $sqlite; - public static function setUpBeforeClass(): void { - // if ( ! defined( 'PDO_DEBUG' )) { - // define( 'PDO_DEBUG', true ); - // } - if ( ! defined( 'FQDB' ) ) { - define( 'FQDB', ':memory:' ); - define( 'FQDBDIR', __DIR__ . '/../testdb' ); - } - error_reporting( E_ALL & ~E_DEPRECATED ); - if ( ! isset( $GLOBALS['table_prefix'] ) ) { - $GLOBALS['table_prefix'] = 'wptests_'; - } - if ( ! isset( $GLOBALS['wpdb'] ) ) { - $GLOBALS['wpdb'] = new stdClass(); - $GLOBALS['wpdb']->suppress_errors = false; - $GLOBALS['wpdb']->show_errors = true; - } - return; - } + /** @var PDO */ + private $sqlite; /** * Before each test, we create a new volatile database and WordPress tables. diff --git a/tests/WP_SQLite_Driver_Tests.php b/tests/WP_SQLite_Driver_Tests.php index a64b418..788511b 100644 --- a/tests/WP_SQLite_Driver_Tests.php +++ b/tests/WP_SQLite_Driver_Tests.php @@ -3,29 +3,11 @@ use PHPUnit\Framework\TestCase; class WP_SQLite_Driver_Tests extends TestCase { - + /** @var WP_SQLite_Driver */ private $engine; - private $sqlite; - public static function setUpBeforeClass(): void { - // if ( ! defined( 'PDO_DEBUG' )) { - // define( 'PDO_DEBUG', true ); - // } - if ( ! defined( 'FQDB' ) ) { - define( 'FQDB', ':memory:' ); - define( 'FQDBDIR', __DIR__ . '/../testdb' ); - } - error_reporting( E_ALL & ~E_DEPRECATED ); - if ( ! isset( $GLOBALS['table_prefix'] ) ) { - $GLOBALS['table_prefix'] = 'wptests_'; - } - if ( ! isset( $GLOBALS['wpdb'] ) ) { - $GLOBALS['wpdb'] = new stdClass(); - $GLOBALS['wpdb']->suppress_errors = false; - $GLOBALS['wpdb']->show_errors = true; - } - return; - } + /** @var PDO */ + private $sqlite; // Before each test, we create a new database public function setUp(): void { diff --git a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php index 4d09198..35d3da4 100644 --- a/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php +++ b/tests/WP_SQLite_Information_Schema_Reconstructor_Tests.php @@ -21,20 +21,6 @@ class WP_SQLite_Information_Schema_Reconstructor_Tests extends TestCase { private $sqlite; public static function setUpBeforeClass(): void { - if ( ! defined( 'FQDB' ) ) { - define( 'FQDB', ':memory:' ); - define( 'FQDBDIR', __DIR__ . '/../testdb' ); - } - error_reporting( E_ALL & ~E_DEPRECATED ); - if ( ! isset( $GLOBALS['table_prefix'] ) ) { - $GLOBALS['table_prefix'] = 'wptests_'; - } - if ( ! isset( $GLOBALS['wpdb'] ) ) { - $GLOBALS['wpdb'] = new class() { - public function set_prefix( string $prefix ): void {} - }; - } - // Mock symbols that are used for WordPress table reconstruction. if ( ! defined( 'ABSPATH' ) ) { define( 'ABSPATH', __DIR__ ); diff --git a/tests/WP_SQLite_Metadata_Tests.php b/tests/WP_SQLite_Metadata_Tests.php index cc66b35..399edb5 100644 --- a/tests/WP_SQLite_Metadata_Tests.php +++ b/tests/WP_SQLite_Metadata_Tests.php @@ -3,29 +3,11 @@ use PHPUnit\Framework\TestCase; class WP_SQLite_Metadata_Tests extends TestCase { - + /** @var WP_SQLite_Translator */ private $engine; - private $sqlite; - public static function setUpBeforeClass(): void { - // if ( ! defined( 'PDO_DEBUG' )) { - // define( 'PDO_DEBUG', true ); - // } - if ( ! defined( 'FQDB' ) ) { - define( 'FQDB', ':memory:' ); - define( 'FQDBDIR', __DIR__ . '/../testdb' ); - } - error_reporting( E_ALL & ~E_DEPRECATED ); - if ( ! isset( $GLOBALS['table_prefix'] ) ) { - $GLOBALS['table_prefix'] = 'wptests_'; - } - if ( ! isset( $GLOBALS['wpdb'] ) ) { - $GLOBALS['wpdb'] = new stdClass(); - $GLOBALS['wpdb']->suppress_errors = false; - $GLOBALS['wpdb']->show_errors = true; - } - return; - } + /** @var PDO */ + private $sqlite; // Before each test, we create a new database public function setUp(): void { diff --git a/tests/WP_SQLite_Query_Tests.php b/tests/WP_SQLite_Query_Tests.php index ff6e0aa..648cd4e 100644 --- a/tests/WP_SQLite_Query_Tests.php +++ b/tests/WP_SQLite_Query_Tests.php @@ -6,29 +6,11 @@ * Unit tests using the WordPress table definitions. */ class WP_SQLite_Query_Tests extends TestCase { - + /** @var WP_SQLite_Translator */ private $engine; - private $sqlite; - public static function setUpBeforeClass(): void { - // if ( ! defined( 'PDO_DEBUG' )) { - // define( 'PDO_DEBUG', true ); - // } - if ( ! defined( 'FQDB' ) ) { - define( 'FQDB', ':memory:' ); - define( 'FQDBDIR', __DIR__ . '/../testdb' ); - } - error_reporting( E_ALL & ~E_DEPRECATED ); - if ( ! isset( $GLOBALS['table_prefix'] ) ) { - $GLOBALS['table_prefix'] = 'wptests_'; - } - if ( ! isset( $GLOBALS['wpdb'] ) ) { - $GLOBALS['wpdb'] = new stdClass(); - $GLOBALS['wpdb']->suppress_errors = false; - $GLOBALS['wpdb']->show_errors = true; - } - return; - } + /** @var PDO */ + private $sqlite; /** * Before each test, we create a new volatile database and WordPress tables. diff --git a/tests/WP_SQLite_Translator_Tests.php b/tests/WP_SQLite_Translator_Tests.php index d68a6b1..a892fe7 100644 --- a/tests/WP_SQLite_Translator_Tests.php +++ b/tests/WP_SQLite_Translator_Tests.php @@ -3,29 +3,11 @@ use PHPUnit\Framework\TestCase; class WP_SQLite_Translator_Tests extends TestCase { - + /** @var WP_SQLite_Translator */ private $engine; - private $sqlite; - public static function setUpBeforeClass(): void { - // if ( ! defined( 'PDO_DEBUG' )) { - // define( 'PDO_DEBUG', true ); - // } - if ( ! defined( 'FQDB' ) ) { - define( 'FQDB', ':memory:' ); - define( 'FQDBDIR', __DIR__ . '/../testdb' ); - } - error_reporting( E_ALL & ~E_DEPRECATED ); - if ( ! isset( $GLOBALS['table_prefix'] ) ) { - $GLOBALS['table_prefix'] = 'wptests_'; - } - if ( ! isset( $GLOBALS['wpdb'] ) ) { - $GLOBALS['wpdb'] = new stdClass(); - $GLOBALS['wpdb']->suppress_errors = false; - $GLOBALS['wpdb']->show_errors = true; - } - return; - } + /** @var PDO */ + private $sqlite; // Before each test, we create a new database public function setUp(): void { diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 0f0f59a..d46d01c 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -21,6 +21,17 @@ require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-builder.php'; require_once __DIR__ . '/../wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php'; +// Configure the test environment. +error_reporting( E_ALL & ~E_DEPRECATED ); +define( 'FQDB', ':memory:' ); +define( 'FQDBDIR', __DIR__ . '/../testdb' ); + +// Polyfill WPDB globals. +$GLOBALS['table_prefix'] = 'wptests_'; +$GLOBALS['wpdb'] = new class() { + public function set_prefix( string $prefix ): void {} +}; + /** * Polyfills for WordPress functions */ From 838967ac2ebd2b6d9c7c9a38ab9e020305100aa5 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 29 Apr 2025 14:19:57 +0200 Subject: [PATCH 34/36] Always quote identifiers in information schema builder --- tests/WP_SQLite_Driver_Translation_Tests.php | 304 +++++++++--------- ...s-wp-sqlite-information-schema-builder.php | 63 ++-- 2 files changed, 188 insertions(+), 179 deletions(-) diff --git a/tests/WP_SQLite_Driver_Translation_Tests.php b/tests/WP_SQLite_Driver_Translation_Tests.php index 0b6f306..1a80dc1 100644 --- a/tests/WP_SQLite_Driver_Translation_Tests.php +++ b/tests/WP_SQLite_Driver_Translation_Tests.php @@ -206,9 +206,9 @@ public function testCreateTable(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -225,13 +225,13 @@ public function testCreateTableWithMultipleColumns(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -248,11 +248,11 @@ public function testCreateTableWithBasicConstraints(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -270,9 +270,9 @@ public function testCreateTableWithEngine(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -290,9 +290,9 @@ public function testCreateTableWithCollate(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -318,11 +318,11 @@ public function testCreateTableWithPrimaryKey(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -340,11 +340,11 @@ public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't1'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't1'", @@ -360,11 +360,11 @@ public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't2'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't2'", @@ -380,14 +380,14 @@ public function testCreateTableWithPrimaryKeyAndAutoincrement(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't3' AND column_name IN ('id')", - 'INSERT INTO _wp_sqlite_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)' + "SELECT column_name, data_type, is_nullable, character_maximum_length FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't3' AND column_name IN ('id')", + 'INSERT INTO `_wp_sqlite_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)", - "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't3' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't3' AND s.column_name = c.column_name", + "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't3' GROUP BY column_name ) UPDATE `_wp_sqlite_mysql_information_schema_columns` AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't3' AND s.column_name = c.column_name", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't3'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't3'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't3'", @@ -407,15 +407,15 @@ public function testCreateTableWithInlineUniqueIndexes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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', 'UNI', '', 'select,insert,update,references', '', '', null)", - 'INSERT INTO _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -436,20 +436,20 @@ public function testCreateTableWithStandaloneUniqueIndexes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name IN ('id')", - 'INSERT INTO _wp_sqlite_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)' + "SELECT column_name, data_type, is_nullable, character_maximum_length FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't' AND column_name IN ('id')", + 'INSERT INTO `_wp_sqlite_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)", - "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", - "SELECT column_name, data_type, is_nullable, character_maximum_length FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name IN ('name')", - 'INSERT INTO _wp_sqlite_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)' + "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE `_wp_sqlite_mysql_information_schema_columns` AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", + "SELECT column_name, data_type, is_nullable, character_maximum_length FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't' AND column_name IN ('name')", + 'INSERT INTO `_wp_sqlite_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)", - "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", + "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE `_wp_sqlite_mysql_information_schema_columns` AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", @@ -515,8 +515,8 @@ public function testAlterTableAddColumn(): void { $this->assertExecutedInformationSchemaQueries( array( "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - 'INSERT INTO _wp_sqlite_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)' + "SELECT MAX(ordinal_position) FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + 'INSERT INTO `_wp_sqlite_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', 'a', 2, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -544,8 +544,8 @@ public function testAlterTableAddColumnWithNotNull(): void { $this->assertExecutedInformationSchemaQueries( array( "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - 'INSERT INTO _wp_sqlite_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)' + "SELECT MAX(ordinal_position) FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + 'INSERT INTO `_wp_sqlite_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', 'a', 2, null, 'NO', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -573,8 +573,8 @@ public function testAlterTableAddColumnWithDefault(): void { $this->assertExecutedInformationSchemaQueries( array( "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - 'INSERT INTO _wp_sqlite_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)' + "SELECT MAX(ordinal_position) FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + 'INSERT INTO `_wp_sqlite_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', 'a', 2, '0', 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -602,8 +602,8 @@ public function testAlterTableAddColumnWithNotNullAndDefault(): void { $this->assertExecutedInformationSchemaQueries( array( "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - 'INSERT INTO _wp_sqlite_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)' + "SELECT MAX(ordinal_position) FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + 'INSERT INTO `_wp_sqlite_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', 'a', 2, '0', 'NO', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -631,14 +631,14 @@ public function testAlterTableAddMultipleColumns(): void { $this->assertExecutedInformationSchemaQueries( array( "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - 'INSERT INTO _wp_sqlite_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)' + "SELECT MAX(ordinal_position) FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + 'INSERT INTO `_wp_sqlite_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', 'a', 2, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", - "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - 'INSERT INTO _wp_sqlite_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)' + "SELECT MAX(ordinal_position) FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + 'INSERT INTO `_wp_sqlite_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', 3, null, 'YES', 'text', 65535, 65535, null, null, null, 'utf8mb4', 'utf8mb4_general_ci', 'text', '', '', 'select,insert,update,references', '', '', null)", - "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - 'INSERT INTO _wp_sqlite_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)' + "SELECT MAX(ordinal_position) FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + 'INSERT INTO `_wp_sqlite_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', 'c', 4, null, 'YES', 'tinyint', null, null, 3, 0, null, null, null, 'tinyint(1)', '', '', 'select,insert,update,references', '', '', null)", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -666,9 +666,9 @@ public function testAlterTableDropColumn(): void { $this->assertExecutedInformationSchemaQueries( array( "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", - "DELETE FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", - "DELETE FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", - "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", + "DELETE FROM `_wp_sqlite_mysql_information_schema_columns` WHERE `table_schema` = 'wp' AND `table_name` = 't' AND `column_name` = 'a'", + "DELETE FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE `table_schema` = 'wp' AND `table_name` = 't' AND `column_name` = 'a'", + "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE `_wp_sqlite_mysql_information_schema_columns` AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", @@ -694,12 +694,12 @@ public function testAlterTableDropMultipleColumns(): void { $this->assertExecutedInformationSchemaQueries( array( "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", - "DELETE FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", - "DELETE FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", - "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", - "DELETE FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'b'", - "DELETE FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'b'", - "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", + "DELETE FROM `_wp_sqlite_mysql_information_schema_columns` WHERE `table_schema` = 'wp' AND `table_name` = 't' AND `column_name` = 'a'", + "DELETE FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE `table_schema` = 'wp' AND `table_name` = 't' AND `column_name` = 'a'", + "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE `_wp_sqlite_mysql_information_schema_columns` AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", + "DELETE FROM `_wp_sqlite_mysql_information_schema_columns` WHERE `table_schema` = 'wp' AND `table_name` = 't' AND `column_name` = 'b'", + "DELETE FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE `table_schema` = 'wp' AND `table_name` = 't' AND `column_name` = 'b'", + "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE `_wp_sqlite_mysql_information_schema_columns` AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", @@ -726,12 +726,12 @@ public function testAlterTableAddAndDropColumns(): void { $this->assertExecutedInformationSchemaQueries( array( "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", - "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - 'INSERT INTO _wp_sqlite_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)' + "SELECT MAX(ordinal_position) FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + 'INSERT INTO `_wp_sqlite_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', 2, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", - "DELETE FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", - "DELETE FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", - "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", + "DELETE FROM `_wp_sqlite_mysql_information_schema_columns` WHERE `table_schema` = 'wp' AND `table_name` = 't' AND `column_name` = 'a'", + "DELETE FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE `table_schema` = 'wp' AND `table_name` = 't' AND `column_name` = 'a'", + "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE `_wp_sqlite_mysql_information_schema_columns` AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't'", @@ -758,11 +758,11 @@ public function testAlterTableDropAndAddSingleColumn(): void { $this->assertExecutedInformationSchemaQueries( array( "SELECT COLUMN_NAME FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", - "DELETE FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", - "DELETE FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' AND column_name = 'a'", - "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM _wp_sqlite_mysql_information_schema_statistics WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE _wp_sqlite_mysql_information_schema_columns AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", - "SELECT MAX(ordinal_position) FROM _wp_sqlite_mysql_information_schema_columns WHERE table_schema = 'wp' AND table_name = 't'", - 'INSERT INTO _wp_sqlite_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)' + "DELETE FROM `_wp_sqlite_mysql_information_schema_columns` WHERE `table_schema` = 'wp' AND `table_name` = 't' AND `column_name` = 'a'", + "DELETE FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE `table_schema` = 'wp' AND `table_name` = 't' AND `column_name` = 'a'", + "WITH s AS ( SELECT column_name, CASE WHEN MAX(index_name = 'PRIMARY') THEN 'PRI' WHEN MAX(non_unique = 0 AND seq_in_index = 1) THEN 'UNI' WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key FROM `_wp_sqlite_mysql_information_schema_statistics` WHERE table_schema = 'wp' AND table_name = 't' GROUP BY column_name ) UPDATE `_wp_sqlite_mysql_information_schema_columns` AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) FROM s WHERE c.table_schema = 'wp' AND c.table_name = 't' AND s.column_name = c.column_name", + "SELECT MAX(ordinal_position) FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", + 'INSERT INTO `_wp_sqlite_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', 'a', 1, null, 'YES', 'int', null, null, 10, 0, null, null, null, 'int', '', '', 'select,insert,update,references', '', '', null)", "SELECT * FROM `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -779,11 +779,11 @@ public function testBitDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -800,11 +800,11 @@ public function testBooleanDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -821,19 +821,19 @@ public function testIntegerDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -850,15 +850,15 @@ public function testFloatDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -875,15 +875,15 @@ public function testDecimalTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -900,11 +900,11 @@ public function testCharDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -921,13 +921,13 @@ public function testVarcharDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -944,15 +944,15 @@ public function testNationalCharDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -969,13 +969,13 @@ public function testNcharVarcharDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -992,13 +992,13 @@ public function testNationalVarcharDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -1015,15 +1015,15 @@ public function testTextDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -1040,9 +1040,9 @@ public function testEnumDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -1059,17 +1059,17 @@ public function testDateAndTimeDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -1086,11 +1086,11 @@ public function testBinaryDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -1107,15 +1107,15 @@ public function testBlobDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -1132,15 +1132,15 @@ public function testBasicSpatialDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -1157,13 +1157,13 @@ public function testMultiObjectSpatialDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -1180,11 +1180,11 @@ public function testGeometryCollectionDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", @@ -1201,9 +1201,9 @@ public function testSerialDataTypes(): void { $this->assertExecutedInformationSchemaQueries( array( - 'INSERT INTO _wp_sqlite_mysql_information_schema_tables (table_schema, table_name, table_type, engine, row_format, table_collation)' + 'INSERT INTO `_wp_sqlite_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 _wp_sqlite_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)' + 'INSERT INTO `_wp_sqlite_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 `_wp_sqlite_mysql_information_schema_tables` WHERE table_type = 'BASE TABLE' AND table_schema = 'wp' AND table_name = 't'", "SELECT * FROM `_wp_sqlite_mysql_information_schema_columns` WHERE table_schema = 'wp' AND table_name = 't'", 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 3b64742..1ba51dc 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 @@ -671,12 +671,12 @@ private function record_add_column( ): void { $columns_table_name = $this->get_table_name( $table_is_temporary, 'columns' ); $position = $this->connection->query( - " + ' SELECT MAX(ordinal_position) - FROM $columns_table_name + FROM ' . $this->connection->quote_identifier( $columns_table_name ) . ' WHERE table_schema = ? AND table_name = ? - ", + ', array( $this->db_name, $table_name ) )->fetchColumn(); @@ -891,12 +891,12 @@ private function record_add_constraint( if ( count( $column_names ) > 0 ) { $columns_table_name = $this->get_table_name( $table_is_temporary, 'columns' ); $column_info = $this->connection->query( - " + ' SELECT column_name, data_type, is_nullable, character_maximum_length - FROM $columns_table_name + FROM ' . $this->connection->quote_identifier( $columns_table_name ) . ' WHERE table_schema = ? AND table_name = ? - AND column_name IN (" . implode( ',', array_fill( 0, count( $column_names ), '?' ) ) . ') + AND column_name IN (' . implode( ',', array_fill( 0, count( $column_names ), '?' ) ) . ') ', array_merge( array( $this->db_name, $table_name ), $column_names ) )->fetchAll( @@ -1086,12 +1086,12 @@ private function sync_column_key_info( bool $table_is_temporary, string $table_n WHEN MAX(seq_in_index = 1) THEN 'MUL' ELSE '' END AS column_key - FROM $statistics_table_name + FROM " . $this->connection->quote_identifier( $statistics_table_name ) . ' WHERE table_schema = ? AND table_name = ? GROUP BY column_name ) - UPDATE $columns_table_name AS c + UPDATE ' . $this->connection->quote_identifier( $columns_table_name ) . " AS c SET column_key = s.column_key, is_nullable = IIF(s.column_key = 'PRI', 'NO', c.is_nullable) @@ -1889,11 +1889,18 @@ private function get_value( WP_Parser_Node $node ): string { * @param array $data The data to insert (key is column name, value is column value). */ private function insert_values( string $table_name, array $data ): void { + $insert_columns = array(); + foreach ( $data as $column => $value ) { + $insert_columns[] = $this->connection->quote_identifier( $column ); + } + $this->connection->query( - ' - INSERT INTO ' . $table_name . ' (' . implode( ', ', array_keys( $data ) ) . ') - VALUES (' . implode( ', ', array_fill( 0, count( $data ), '?' ) ) . ') - ', + sprintf( + 'INSERT INTO %s (%s) VALUES (%s)', + $this->connection->quote_identifier( $table_name ), + implode( ', ', $insert_columns ), + implode( ', ', array_fill( 0, count( $data ), '?' ) ) + ), array_values( $data ) ); } @@ -1906,22 +1913,23 @@ private function insert_values( string $table_name, array $data ): void { * @param array $where The WHERE clause conditions (key is column name, value is column value). */ private function update_values( string $table_name, array $data, array $where ): void { - $set = array(); + $set_statements = array(); foreach ( $data as $column => $value ) { - $set[] = $column . ' = ?'; + $set_statements[] = $this->connection->quote_identifier( $column ) . ' = ?'; } - $where_clause = array(); + $where_statements = array(); foreach ( $where as $column => $value ) { - $where_clause[] = $column . ' = ?'; + $where_statements[] = $this->connection->quote_identifier( $column ) . ' = ?'; } $this->connection->query( - ' - UPDATE ' . $table_name . ' - SET ' . implode( ', ', $set ) . ' - WHERE ' . implode( ' AND ', $where_clause ) . ' - ', + sprintf( + 'UPDATE %s SET %s WHERE %s', + $this->connection->quote_identifier( $table_name ), + implode( ', ', $set_statements ), + implode( ' AND ', $where_statements ) + ), array_merge( array_values( $data ), array_values( $where ) ) ); } @@ -1933,16 +1941,17 @@ private function update_values( string $table_name, array $data, array $where ): * @param array $where The WHERE clause conditions (key is column name, value is column value). */ private function delete_values( string $table_name, array $where ): void { - $where_clause = array(); + $where_statements = array(); foreach ( $where as $column => $value ) { - $where_clause[] = $column . ' = ?'; + $where_statements[] = $this->connection->quote_identifier( $column ) . ' = ?'; } $this->connection->query( - ' - DELETE FROM ' . $table_name . ' - WHERE ' . implode( ' AND ', $where_clause ) . ' - ', + sprintf( + 'DELETE FROM %s WHERE %s', + $this->connection->quote_identifier( $table_name ), + implode( ' AND ', $where_statements ) + ), array_values( $where ) ); } From c745a66a96d92385cdd629397bc59126cedbc4af Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 29 Apr 2025 16:36:37 +0200 Subject: [PATCH 35/36] Unify quoting also for $wpdb->blogs table name --- .../class-wp-sqlite-information-schema-reconstructor.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php index a025159..3cab7b1 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-information-schema-reconstructor.php @@ -177,8 +177,12 @@ private function get_wp_create_table_statements(): array { * of all existing blogs, independent of any filters and actions that * could possibly alter the results of a "get_sites()" call. */ - $stmt = $this->driver->execute_sqlite_query( "SELECT blog_id FROM {$wpdb->blogs}" ); - $blog_ids = $stmt->fetchAll( PDO::FETCH_COLUMN ); + $blog_ids = $this->driver->execute_sqlite_query( + sprintf( + 'SELECT blog_id FROM %s', + $this->connection->quote_identifier( $wpdb->blogs ) + ) + )->fetchAll( PDO::FETCH_COLUMN ); foreach ( $blog_ids as $blog_id ) { $schema .= wp_get_db_schema( 'blog', (int) $blog_id ); } From 9b21ab3ac52515b42456be57bba3703a0c7608ef Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 29 Apr 2025 17:31:22 +0200 Subject: [PATCH 36/36] Fix parser API, explicitly disable multi-queries in WP_SQLite_Driver::query() --- tests/WP_SQLite_Driver_Tests.php | 6 ++++++ wp-includes/mysql/class-wp-mysql-parser.php | 3 --- wp-includes/sqlite-ast/class-wp-sqlite-driver.php | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/WP_SQLite_Driver_Tests.php b/tests/WP_SQLite_Driver_Tests.php index 788511b..f5ee1c0 100644 --- a/tests/WP_SQLite_Driver_Tests.php +++ b/tests/WP_SQLite_Driver_Tests.php @@ -4463,4 +4463,10 @@ public function testSessionSqlModes(): void { $result = $this->assertQuery( 'SELECT @@session.SQL_mode' ); $this->assertSame( 'ONLY_FULL_GROUP_BY', $result[0]->{'@@session.SQL_mode'} ); } + + public function testMultiQueryNotSupported(): void { + $this->expectException( WP_SQLite_Driver_Exception::class ); + $this->expectExceptionMessage( 'Multi-query is not supported.' ); + $this->assertQuery( 'SELECT 1; SELECT 2' ); + } } diff --git a/wp-includes/mysql/class-wp-mysql-parser.php b/wp-includes/mysql/class-wp-mysql-parser.php index d0cfaaf..f291064 100644 --- a/wp-includes/mysql/class-wp-mysql-parser.php +++ b/wp-includes/mysql/class-wp-mysql-parser.php @@ -33,9 +33,6 @@ public function next_query(): bool { return false; } $this->current_ast = $this->parse(); - if ( ! $this->current_ast ) { - return false; - } return true; } diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index 722696f..80b05c9 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -599,7 +599,6 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo try { // Parse the MySQL query. - // TODO: Translate and execute all queries in the SQL input string. $parser = $this->create_parser( $query ); $parser->next_query(); $ast = $parser->get_query_ast(); @@ -607,6 +606,10 @@ public function query( string $query, $fetch_mode = PDO::FETCH_OBJ, ...$fetch_mo throw $this->new_driver_exception( 'Failed to parse the MySQL query.' ); } + if ( $parser->next_query() ) { + throw $this->new_driver_exception( 'Multi-query is not supported.' ); + } + // Handle transaction commands. /*