From 737d4de12475b8f9b03b94cf1d1e3f10cf58e9cb Mon Sep 17 00:00:00 2001 From: Catalin Besleaga Date: Tue, 16 Jun 2026 11:49:42 +0300 Subject: [PATCH] Bug#112704: Fix TempTable CTE clone handler mismatch when TempTable falls back to InnoDB When TempTable materialization hits RECORD_FILE_FULL and falls back to InnoDB, CTE clones keep TempTable handlers created at preparation time (open_table_from_share()), while the table itself is created in InnoDB. Later open_tmp_table() calls on those clones fail with "Table doesn't exist". Non-recursive CTE refs hit this in create_materialized_table()'s shortcut path. Recursive CTE refs hit it separately in FollowTailIterator::Init(), which opens recursive clones without going through create_materialized_table(). Changes: 1. sql_derived.cc: In create_materialized_table()'s shortcut path, detect handler-type mismatch and replace the handler to match an already-created sibling clone before open_tmp_table(). 2. basic_row_iterators.cc: In FollowTailIterator::Init(), detect handler-type mismatch against share->db_type() (already corrected during fallback) and replace the stale handler before opening. 3. sql_tmp_table.cc: On TempTable->InnoDB fallback, update share->db_plugin so db_type() reflects InnoDB, destroy the stale TempTable handler, keep create_info.db_type consistent, and count Created_tmp_disk_tables once. --- mysql-test/r/bug112704.result | 34 ++++++++++++++++++++++++++++ mysql-test/t/bug112704.test | 24 ++++++++++++++++++++ sql/iterators/basic_row_iterators.cc | 16 +++++++++++++ sql/sql_derived.cc | 17 ++++++++++++++ sql/sql_tmp_table.cc | 13 ++++++++++- 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 mysql-test/r/bug112704.result create mode 100644 mysql-test/t/bug112704.test diff --git a/mysql-test/r/bug112704.result b/mysql-test/r/bug112704.result new file mode 100644 index 000000000000..36d5a9ec439e --- /dev/null +++ b/mysql-test/r/bug112704.result @@ -0,0 +1,34 @@ +# CTE clone handler mismatch when TempTable falls back to InnoDB +SET @@internal_tmp_mem_storage_engine = 'TempTable'; +# Case 1: Non-recursive CTE — handler mismatch fix + status counter check +FLUSH STATUS; +SET DEBUG='+d,temptable_create_return_full'; +WITH v AS (VALUES ROW(1), ROW(2)) SELECT * FROM v AS v1 JOIN v AS v2 ORDER BY v1.column_0, v2.column_0; +column_0 column_0 +1 1 +1 2 +2 1 +2 2 +SHOW STATUS LIKE 'Created_tmp_disk_tables'; +Variable_name Value +Created_tmp_disk_tables 1 +# Case 2: Recursive CTE — recursive ref clone also uses the shortcut path +FLUSH STATUS; +WITH RECURSIVE r AS ( +SELECT 1 AS n +UNION ALL +SELECT n + 1 FROM r WHERE n < 5 +) +SELECT * FROM r ORDER BY n; +n +1 +2 +3 +4 +5 +SET DEBUG='-d,temptable_create_return_full'; +SHOW STATUS LIKE 'Created_tmp_disk_tables'; +Variable_name Value +Created_tmp_disk_tables 1 +FLUSH STATUS; +SET @@internal_tmp_mem_storage_engine = DEFAULT; diff --git a/mysql-test/t/bug112704.test b/mysql-test/t/bug112704.test new file mode 100644 index 000000000000..e75534cf860a --- /dev/null +++ b/mysql-test/t/bug112704.test @@ -0,0 +1,24 @@ +--source include/have_debug.inc + +--echo # CTE clone handler mismatch when TempTable falls back to InnoDB + +SET @@internal_tmp_mem_storage_engine = 'TempTable'; + +--echo # Case 1: Non-recursive CTE — handler mismatch fix + status counter check +FLUSH STATUS; +SET DEBUG='+d,temptable_create_return_full'; +WITH v AS (VALUES ROW(1), ROW(2)) SELECT * FROM v AS v1 JOIN v AS v2 ORDER BY v1.column_0, v2.column_0; +SHOW STATUS LIKE 'Created_tmp_disk_tables'; + +--echo # Case 2: Recursive CTE — recursive ref clone also uses the shortcut path +FLUSH STATUS; +WITH RECURSIVE r AS ( + SELECT 1 AS n + UNION ALL + SELECT n + 1 FROM r WHERE n < 5 +) +SELECT * FROM r ORDER BY n; +SET DEBUG='-d,temptable_create_return_full'; +SHOW STATUS LIKE 'Created_tmp_disk_tables'; +FLUSH STATUS; +SET @@internal_tmp_mem_storage_engine = DEFAULT; diff --git a/sql/iterators/basic_row_iterators.cc b/sql/iterators/basic_row_iterators.cc index 8831ee5af3b9..fb8bdee7a2cc 100644 --- a/sql/iterators/basic_row_iterators.cc +++ b/sql/iterators/basic_row_iterators.cc @@ -387,6 +387,22 @@ bool FollowTailIterator::DoInit() { // connect to it on first run here. assert(table()->in_use == nullptr || table()->in_use == thd()); table()->in_use = thd(); + // The CTE's primary materialization may have fallen back TempTable -> + // InnoDB, updating share->db_plugin. This clone's handler (set at + // preparation time from share->db_type()) may now be stale. Detect and + // fix before opening. + if (table()->file->ht != table()->s->db_type()) { + ::destroy_at(table()->file); + table()->file = get_new_handler(table()->s, false, + table()->s->alloc_for_tmp_file_handler, + table()->s->db_type()); + if (table()->file == nullptr) return true; + table()->file->change_table_ptr(table(), table()->s); + if (table()->file->set_ha_share_ref(&table()->s->ha_share)) { + ::destroy_at(table()->file); + return true; + } + } if (open_tmp_table(table())) { return true; } diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index 5105e0eb08ae..8c3be87659b3 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -1695,6 +1695,23 @@ bool Table_ref::create_materialized_table(THD *thd) { if (t->is_created()) { assert(table->in_use == nullptr || table->in_use == thd); table->in_use = thd; + // CTE clones share a TABLE_SHARE but have separate handlers (set at + // preparation time). If the first clone triggered TempTable->InnoDB + // fallback, this clone's handler still points to TempTable. Detect the + // mismatch and replace with the correct engine to avoid opening the + // wrong handler (which would fail with "table doesn't exist"). + if (table->file->ht != t->file->ht) { + ::destroy_at(table->file); + table->file = get_new_handler(table->s, false, + table->s->alloc_for_tmp_file_handler, + t->file->ht); + if (table->file == nullptr) return true; + table->file->change_table_ptr(table, table->s); + if (table->file->set_ha_share_ref(&table->s->ha_share)) { + ::destroy_at(table->file); + return true; + } + } if (open_tmp_table(table)) return true; /* purecov: inspected */ break; } diff --git a/sql/sql_tmp_table.cc b/sql/sql_tmp_table.cc index 860ec36b4fec..2413b191686e 100644 --- a/sql/sql_tmp_table.cc +++ b/sql/sql_tmp_table.cc @@ -2320,6 +2320,13 @@ static bool create_tmp_table_with_fallback(THD *thd, TABLE *table) { table->file->create(share->table_name.str, table, &create_info, nullptr); if (error == HA_ERR_RECORD_FILE_FULL && table->s->db_type() == temptable_hton) { + // Update share->db_plugin so db_type() reflects InnoDB instead of stale + // TempTable. Other clones share this TABLE_SHARE and depend on db_type() + // to determine the actual storage engine. + plugin_unlock(nullptr, share->db_plugin); + share->db_plugin = ha_lock_engine(nullptr, innodb_hton); + create_info.db_type = innodb_hton; + ::destroy_at(table->file); table->file = get_new_handler( table->s, false, share->alloc_for_tmp_file_handler, innodb_hton); error = table->file->create(share->table_name.str, table, &create_info, @@ -2331,7 +2338,11 @@ static bool create_tmp_table_with_fallback(THD *thd, TABLE *table) { table->db_stat = 0; return true; } else { - if (table->s->db_type() != temptable_hton) { + // For non-fallback cases (TempTable succeeded, or initial InnoDB request): + // increment counter if table is on disk (not in-memory like + // TempTable/MEMORY). + if (table->s->db_type() != temptable_hton && + table->s->db_type() != heap_hton) { thd->inc_status_created_tmp_disk_tables(); } return false;