From 7e9822ba03eccd0af4d5fc11a53d76a8c038e7bf Mon Sep 17 00:00:00 2001 From: Samuel de Rougemont Date: Sun, 11 Oct 2026 22:27:28 +0200 Subject: [PATCH 1/3] Fix CREATE TEMPORARY TABLE IF NOT EXISTS as the first temporary table The IF NOT EXISTS check looked the table up in the temporary information schema tables, which are only created together with the first temporary table of a session. When the first temporary table of a session was created with IF NOT EXISTS, the lookup failed with "Table '_wp_sqlite_mysql_information_schema_tmp_tables' doesn't exist". For temporary tables, check for the table in sqlite_temp_master instead, as temporary_table_exists() already does for this reason. Co-Authored-By: Claude Fable 5.1 --- .../src/sqlite/class-wp-mysql-on-sqlite.php | 24 +++++++++++------ .../tests/WP_MySQL_On_SQLite_Tests.php | 27 +++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php index 391a1233e..813455340 100644 --- a/packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-mysql-on-sqlite.php @@ -2925,14 +2925,22 @@ private function execute_create_table_statement( WP_Parser_Node $node ): void { // Handle IF NOT EXISTS. 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( - sprintf( - 'SELECT 1 FROM %s WHERE table_schema = ? AND table_name = ?', - $this->quote_sqlite_identifier( $tables_table ) - ), - array( $this->get_saved_db_name(), $table_name ) - )->fetchColumn(); + if ( $table_is_temporary ) { + /* + * The temporary information schema tables are created with the + * first temporary table of a session, so they may not exist yet. + */ + $table_exists = $this->information_schema_builder->temporary_table_exists( $table_name ); + } else { + $tables_table = $this->information_schema_builder->get_table_name( false, 'tables' ); + $table_exists = $this->execute_sqlite_query( + sprintf( + 'SELECT 1 FROM %s WHERE table_schema = ? AND table_name = ?', + $this->quote_sqlite_identifier( $tables_table ) + ), + array( $this->get_saved_db_name(), $table_name ) + )->fetchColumn(); + } if ( $table_exists ) { $this->last_result_statement = $this->create_result_statement_from_data( array(), array() ); diff --git a/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php b/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php index b81d767ec..9b4a6e14a 100644 --- a/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php +++ b/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php @@ -4433,6 +4433,33 @@ public function testCreateTemporaryTableIfNotExists(): void { ); } + public function testCreateTemporaryTableIfNotExistsAsFirstTemporaryTable(): void { + // No temporary table exists in this session yet. + $this->assertQuery( + 'CREATE TEMPORARY TABLE IF NOT EXISTS t (ID INTEGER, name TEXT)' + ); + $this->assertQuery( "INSERT INTO t (ID, name) VALUES (1, 'a')" ); + + // A repeated statement is a no-op and keeps the existing data, + // also when the table name is cased differently. + $this->assertQuery( + 'CREATE TEMPORARY TABLE IF NOT EXISTS t (ID INTEGER, name TEXT)' + ); + $this->assertQuery( + 'CREATE TEMPORARY TABLE IF NOT EXISTS T (ID INTEGER, name TEXT)' + ); + $result = $this->assertQuery( 'SELECT ID, name FROM t' ); + $this->assertEquals( + array( + (object) array( + 'ID' => '1', + 'name' => 'a', + ), + ), + $result + ); + } + public function testTranslatesComplexDelete() { $this->sqlite->query( "CREATE TABLE wptests_dummy ( From c2efabd78b5a63b3c22fb3295f89256cfc3c5d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jakes=CC=8C?= Date: Tue, 13 Oct 2026 16:23:14 +0200 Subject: [PATCH 2/3] Test CREATE TEMPORARY TABLE IF NOT EXISTS with a same-named standard table A standard table with the same name must not make the statement a no-op. The temporary table is created and shadows the standard table, which stays unchanged. --- .../tests/WP_MySQL_On_SQLite_Tests.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php b/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php index 9b4a6e14a..3f5c9351f 100644 --- a/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php +++ b/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php @@ -4460,6 +4460,22 @@ public function testCreateTemporaryTableIfNotExistsAsFirstTemporaryTable(): void ); } + public function testCreateTemporaryTableIfNotExistsWithStandardTableOfSameName(): void { + $this->assertQuery( 'CREATE TABLE t (a INT)' ); + + // The standard table doesn't prevent creating the temporary table. + $this->assertQuery( 'CREATE TEMPORARY TABLE IF NOT EXISTS t (b INT)' ); + $result = $this->assertQuery( 'SHOW COLUMNS FROM t' ); + $this->assertCount( 1, $result ); + $this->assertEquals( 'b', $result[0]->Field ); + + // The standard table is unchanged. + $this->assertQuery( 'DROP TEMPORARY TABLE t' ); + $result = $this->assertQuery( 'SHOW COLUMNS FROM t' ); + $this->assertCount( 1, $result ); + $this->assertEquals( 'a', $result[0]->Field ); + } + public function testTranslatesComplexDelete() { $this->sqlite->query( "CREATE TABLE wptests_dummy ( From 821618532ba8b4c6098bd10f006543979a8de698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jakes=CC=8C?= Date: Tue, 13 Oct 2026 16:23:49 +0200 Subject: [PATCH 3/3] Fix DROP TEMPORARY TABLE as the first temporary table With the TEMPORARY keyword, the DROP TABLE handler deleted the table's records from the temporary information schema tables. Those are only created with the first temporary table of a session, so when no temporary table had been created yet, DROP TEMPORARY TABLE [IF EXISTS] failed with "Table '_wp_sqlite_mysql_information_schema_tmp_tables' doesn't exist". Skip recording when the TEMPORARY keyword is used and no such temporary table exists. SQLite then drops nothing with IF EXISTS, and reports the missing table without it. --- ...s-wp-sqlite-information-schema-builder.php | 11 ++++++++++- .../tests/WP_MySQL_On_SQLite_Tests.php | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-builder.php b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-builder.php index f4770b21e..bbd3728fb 100644 --- a/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-builder.php +++ b/packages/mysql-on-sqlite/src/sqlite/class-wp-sqlite-information-schema-builder.php @@ -781,7 +781,16 @@ public function record_drop_table( WP_Parser_Node $node ): void { $table_refs = $child_node->get_first_child_node( 'tableRefList' )->get_child_nodes(); foreach ( $table_refs as $table_ref ) { $table_name = $this->get_table_name_from_node( $table_ref ); - $table_is_temporary = $has_temporary_keyword || $this->temporary_table_exists( $table_name ); + $table_is_temporary = $this->temporary_table_exists( $table_name ); + + /* + * DROP TEMPORARY TABLE drops only temporary tables. When there is no + * such table, there is nothing to record. The temporary information + * schema tables may not even exist yet. + */ + if ( $has_temporary_keyword && ! $table_is_temporary ) { + continue; + } $this->delete_values( $this->get_table_name( $table_is_temporary, 'tables' ), diff --git a/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php b/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php index 3f5c9351f..8a9446781 100644 --- a/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php +++ b/packages/mysql-on-sqlite/tests/WP_MySQL_On_SQLite_Tests.php @@ -4476,6 +4476,25 @@ public function testCreateTemporaryTableIfNotExistsWithStandardTableOfSameName() $this->assertEquals( 'a', $result[0]->Field ); } + public function testDropTemporaryTableIfExistsAsFirstTemporaryTable(): void { + // No temporary table exists in this session yet. + $this->assertQuery( 'DROP TEMPORARY TABLE IF EXISTS t' ); + + // A standard table with the same name is not dropped. + $this->assertQuery( 'CREATE TABLE t (a INT)' ); + $this->assertQuery( 'DROP TEMPORARY TABLE IF EXISTS t' ); + $result = $this->assertQuery( 'SHOW COLUMNS FROM t' ); + $this->assertCount( 1, $result ); + $this->assertEquals( 'a', $result[0]->Field ); + } + + public function testDropTemporaryTableAsFirstTemporaryTable(): void { + // No temporary table exists in this session yet. + $this->expectException( WP_MySQL_On_SQLite_Exception::class ); + $this->expectExceptionMessage( "Table 'temp.t' doesn't exist" ); + $this->assertQuery( 'DROP TEMPORARY TABLE t' ); + } + public function testTranslatesComplexDelete() { $this->sqlite->query( "CREATE TABLE wptests_dummy (