From 957dea0ee752e9c41ef3323d417d248aa243d15c Mon Sep 17 00:00:00 2001 From: Luke Lu Date: Mon, 20 Jul 2026 23:20:09 +0000 Subject: [PATCH] MDEV-40388 Skip optimizer-context DDL for exists-by-name tables With optimizer_record_context enabled, the optimizer trace records a CREATE TABLE statement for every base table a query references, so an Optimizer Context Replay server (MDEV-39368) can rebuild the query's context. For SEQUENCE-engine helper tables (seq__to_[_step_]) this recorded a CREATE TABLE ... ENGINE=SEQUENCE that can never be replayed: the SEQUENCE engine discovers such tables from their name alone and ha_seq::create() rejects CREATE TABLE, so replay fails with "Table '...' already exists". Add a handlerton capability flag HTON_TABLE_EXISTS_BY_NAME that an engine sets to declare that its tables are discovered from the table name alone and cannot be created with an explicit CREATE TABLE. The SEQUENCE storage engine sets it in init(). is_base_table() in opt_trace_ddl_info.cc skips tables whose engine has the flag, so their DDL is not recorded. The flag uses a previously unused bit and defaults to 0 (handlertons are zero-filled), so no other engine is affected. PERFORMANCE_SCHEMA tables remain excluded earlier by the table-category check. Test main.opt_trace_store_ddls_sequence: a query over a sequence table records no DDL, and a join of a regular table with a sequence table records only the regular table's DDL. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- .../main/opt_trace_store_ddls_sequence.result | 47 +++++++++++++++++++ .../main/opt_trace_store_ddls_sequence.test | 46 ++++++++++++++++++ sql/handler.h | 1 + sql/opt_trace_ddl_info.cc | 5 +- storage/sequence/sequence.cc | 1 + 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 mysql-test/main/opt_trace_store_ddls_sequence.result create mode 100644 mysql-test/main/opt_trace_store_ddls_sequence.test diff --git a/mysql-test/main/opt_trace_store_ddls_sequence.result b/mysql-test/main/opt_trace_store_ddls_sequence.result new file mode 100644 index 0000000000000..49514a16b0d5c --- /dev/null +++ b/mysql-test/main/opt_trace_store_ddls_sequence.result @@ -0,0 +1,47 @@ +# +# MDEV-40388: sequence.simple fails on replay +# +# Auto-generated SEQUENCE-engine tables (seq__to_[_step_]) +# are discovered by the engine from their name and cannot be recreated +# with an explicit CREATE TABLE. Their DDL must therefore NOT be recorded +# into the optimizer trace, otherwise replaying the recorded context +# fails with "Table '...' already exists". +# +create database db1; +use db1; +create table t1 (a int, b int); +insert into t1 values (1,2),(2,3); +set optimizer_trace=1; +set optimizer_record_context=ON; +# +# A query over an auto-generated sequence table only. +# No ddl should be recorded for the sequence table. +# +explain select * from seq_15_to_1_step_12345; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE seq_15_to_1_step_12345 ALL NULL NULL NULL NULL 1 +set @ddls= (select json_detailed(json_extract(trace, '$**.ddl')) +from information_schema.optimizer_trace); +select ddl +from json_table(@ddls, '$[*]' columns(ddl text path '$')) as jt; +ddl +# +# A query joining a regular table with a sequence table. +# Only the regular table t1 should have its ddl recorded. +# +select t1.a from t1, seq_1_to_10 where t1.a = seq; +a +1 +2 +set @ddls= (select json_detailed(json_extract(trace, '$**.ddl')) +from information_schema.optimizer_trace); +select ddl +from json_table(@ddls, '$[*]' columns(ddl text path '$')) as jt; +ddl +CREATE TABLE `t1` ( + `a` int(11) DEFAULT NULL, + `b` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +set optimizer_record_context=OFF; +set optimizer_trace=0; +drop database db1; diff --git a/mysql-test/main/opt_trace_store_ddls_sequence.test b/mysql-test/main/opt_trace_store_ddls_sequence.test new file mode 100644 index 0000000000000..8a520ff8f02dc --- /dev/null +++ b/mysql-test/main/opt_trace_store_ddls_sequence.test @@ -0,0 +1,46 @@ +--source include/not_embedded.inc +--source include/have_sequence.inc +--source include/no_view_protocol.inc + +--echo # +--echo # MDEV-40388: sequence.simple fails on replay +--echo # +--echo # Auto-generated SEQUENCE-engine tables (seq__to_[_step_]) +--echo # are discovered by the engine from their name and cannot be recreated +--echo # with an explicit CREATE TABLE. Their DDL must therefore NOT be recorded +--echo # into the optimizer trace, otherwise replaying the recorded context +--echo # fails with "Table '...' already exists". +--echo # + +create database db1; +use db1; +create table t1 (a int, b int); +insert into t1 values (1,2),(2,3); + +set optimizer_trace=1; +set optimizer_record_context=ON; + +--echo # +--echo # A query over an auto-generated sequence table only. +--echo # No ddl should be recorded for the sequence table. +--echo # +explain select * from seq_15_to_1_step_12345; +set @ddls= (select json_detailed(json_extract(trace, '$**.ddl')) + from information_schema.optimizer_trace); +select ddl +from json_table(@ddls, '$[*]' columns(ddl text path '$')) as jt; + +--echo # +--echo # A query joining a regular table with a sequence table. +--echo # Only the regular table t1 should have its ddl recorded. +--echo # +select t1.a from t1, seq_1_to_10 where t1.a = seq; +set @ddls= (select json_detailed(json_extract(trace, '$**.ddl')) + from information_schema.optimizer_trace); +select ddl +from json_table(@ddls, '$[*]' columns(ddl text path '$')) as jt; + +set optimizer_record_context=OFF; +set optimizer_trace=0; + +drop database db1; diff --git a/sql/handler.h b/sql/handler.h index f0763aebe717c..24ed4dadf3ff2 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1926,6 +1926,7 @@ handlerton *ha_default_tmp_handlerton(THD *thd); #define HTON_ALTER_NOT_SUPPORTED (1 << 1) //Engine does not support alter #define HTON_CAN_RECREATE (1 << 2) //Delete all is used for truncate #define HTON_HIDDEN (1 << 3) //Engine does not appear in lists +#define HTON_TABLE_EXISTS_BY_NAME (1 << 4) //Tables auto-discovered from name; reject CREATE TABLE (e.g. SEQUENCE) #define HTON_NOT_USER_SELECTABLE (1 << 5) #define HTON_TEMPORARY_NOT_SUPPORTED (1 << 6) //Having temporary tables not supported #define HTON_SUPPORT_LOG_TABLES (1 << 7) //Engine supports log tables diff --git a/sql/opt_trace_ddl_info.cc b/sql/opt_trace_ddl_info.cc index 27e044fa2d4dd..aae62ce519340 100644 --- a/sql/opt_trace_ddl_info.cc +++ b/sql/opt_trace_ddl_info.cc @@ -62,6 +62,7 @@ static const uchar *get_rec_key(const void *entry_, size_t *length, - INFORMATION_SCHEMA tables - Tables in PERFORMANCE_SCHEMA and mysql database - Internal temporary ("work") tables + - Tables auto-discovered from their name (e.g. SEQUENCE), which reject CREATE TABLE */ static bool is_base_table(TABLE_LIST *tbl) { @@ -73,7 +74,9 @@ static bool is_base_table(TABLE_LIST *tbl) get_table_category(tbl->get_db_name(), tbl->get_table_name()) == TABLE_CATEGORY_USER && tbl->table->s->tmp_table != INTERNAL_TMP_TABLE && - tbl->table->s->tmp_table != SYSTEM_TMP_TABLE); + tbl->table->s->tmp_table != SYSTEM_TMP_TABLE && + !(tbl->table->s->db_type() && + (tbl->table->s->db_type()->flags & HTON_TABLE_EXISTS_BY_NAME))); } static bool dump_record_to_trace(THD *thd, DDL_Key *ddl_key, String *stmt) diff --git a/storage/sequence/sequence.cc b/storage/sequence/sequence.cc index 2f34d7c1a2fb6..7f5ce1ca7e8d9 100644 --- a/storage/sequence/sequence.cc +++ b/storage/sequence/sequence.cc @@ -531,6 +531,7 @@ static int init(void *p) hton->drop_table= drop_table; hton->discover_table= discover_table; hton->discover_table_existence= discover_table_existence; + hton->flags|= HTON_TABLE_EXISTS_BY_NAME; hton->commit= hton->rollback= [](THD *, bool) { return 0; }; hton->savepoint_set= hton->savepoint_rollback= hton->savepoint_release= [](THD *, void *) { return 0; };