1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-40598: Capture sequences used only in column DEFAULT expressions

Problem:
A sequence referenced only in a column's DEFAULT expression (e.g.
"a INT DEFAULT NEXTVAL(s1)") is opened only when a statement
evaluates DEFAULT values (INSERT, LOAD DATA, etc). A plain SELECT
never opens it, so the sequence never appears in
thd->lex->query_tables, and Optimizer_context_recorder::
dump_sql_script() had no way to see it. The dependent table's
definition was then captured without the sequence it depends on,
making the captured context unusable on replay.

Fix:
TABLE::internal_tables lists these sequences. dump_sql_script() walks
it for each dumped table and calls the new dump_sequence_placeholder()
for each entry, before the table's CREATE TABLE so that replay can
recreate both in order. The sequence is not opened; only its database
and name are used, so for now it is captured as a placeholder
"CREATE SEQUENCE IF NOT EXISTS db.name" without its parameters or
current value. The name is qualified with the sequence's own database,
so a sequence in a different database than the table is recreated in
that database.

Such a placeholder is also remembered in a separate hash. If the query
uses the same sequence directly as well, the main loop of
dump_sql_script() reaches it later, finds the placeholder, emits
"DROP SEQUENCE IF EXISTS db.name", and then dumps the sequence's real
definition and current value (SETVAL) as for any sequence used in the
query, regardless of the order the tables are listed in.

To share code with the existing dump loop, the SETVAL logic of the loop
is factored out into dump_sequence_current_value().

Even for the scenarios where sequence is dropped after table creation,
a name is replaced by a view, or a user doesn't privilege on the sequence;
The name is still captured as a sequence.

Tests (opt_context_store_ddls.test): INSERT-opened sequence; plain
SELECT; LOCK TABLES; a user without privilege on the sequence; two
sequences in one table; dropped sequence; stored function in the
query; name replaced by a view; re-execution as a prepared statement;
sequence in a different database; sequence used both directly and as a
DEFAULT dependency, in both orders.
This commit is contained in:
bsrikanth-mariadb
2026-09-22 16:36:50 +05:30
parent 6e14c96d15
commit b0cdf72a39
4 changed files with 664 additions and 41 deletions

View File

@@ -15,7 +15,7 @@ set @opt_context=
set @ddls=
(select REGEXP_SUBSTR(
context,
'(CREATE TABLE.*)([\n\r].*)*(?=(REPLACE INTO|set @opt_context))')
'(CREATE (TABLE|SEQUENCE).*)([\n\r].*)*(?=(REPLACE INTO|set @opt_context))')
from information_schema.optimizer_context);
set @fn= (select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.name')));
select name from json_table(@fn, '$[*]' columns(name text path '$')) as jt;

View File

@@ -571,4 +571,364 @@ CREATE TABLE IF NOT EXISTS `seq_1_to_15_step_2` (
# End of 13.1 tests
#
# MDEV-40598: Capture sequences used only in column DEFAULT expressions
#
create sequence s1;
create table t1 (a int primary key default nextval(s1), b int);
# The INSERT itself evaluates the DEFAULT, so s1 is already open as
# part of this very statement; ddls should include both s1 and t1
insert into t1 (b) values (1),(2),(3);
# == Optimizer Context Tables
name
db1.t1
# === Optimizer Context DDLs
@ddls
CREATE TABLE IF NOT EXISTS `s1` (
`next_not_cached_value` bigint(21) NOT NULL,
`minimum_value` bigint(21) NOT NULL,
`maximum_value` bigint(21) NOT NULL,
`start_value` bigint(21) NOT NULL COMMENT 'start value when sequences is created or value if RESTART is used',
`increment` bigint(21) NOT NULL COMMENT 'increment value',
`cache_size` bigint(21) unsigned NOT NULL,
`cycle_option` tinyint(1) unsigned NOT NULL COMMENT '0 if no cycles are allowed, 1 if the sequence should begin a new cycle when maximum_value is passed',
`cycle_count` bigint(21) NOT NULL COMMENT 'How many cycles have been done'
) ENGINE=MyISAM SEQUENCE=1;
SELECT SETVAL(db1.s1, 2);
CREATE TABLE IF NOT EXISTS `t1` (
`a` int(11) NOT NULL DEFAULT nextval(`db1`.`s1`),
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
#
# s1 was opened only by the INSERT above and closed again at the
# end of that statement; this SELECT never evaluates DEFAULT
# NEXTVAL() itself, so it doesn't reopen s1 through normal means
# either. It is still captured, resolved independently by name:
# ddls should include both s1 and t1, with s1 appearing before t1
# so that replay can recreate them
select * from t1 where b < 10;
a b
1 1
2 2
3 3
# == Optimizer Context Tables
name
db1.t1
# === Optimizer Context DDLs
@ddls
CREATE SEQUENCE IF NOT EXISTS `db1`.`s1`;
CREATE TABLE IF NOT EXISTS `t1` (
`a` int(11) NOT NULL DEFAULT nextval(`db1`.`s1`),
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
#
# Under LOCK TABLES, s1 sits in thd->open_tables merely because it
# is locked, not because this particular SELECT touches it - its
# current value must not be captured just from being locked
#
flush tables;
lock tables t1 read, s1 read;
select * from t1 where b < 10;
a b
1 1
2 2
3 3
# == Optimizer Context Tables
name
db1.t1
# === Optimizer Context DDLs
@ddls
CREATE SEQUENCE IF NOT EXISTS `db1`.`s1`;
CREATE TABLE IF NOT EXISTS `t1` (
`a` int(11) NOT NULL DEFAULT nextval(`db1`.`s1`),
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
unlock tables;
#
# A user with SELECT on t1 but no privilege on s1:
# ddls include both s1 and t1
#
create user u1@localhost;
grant select on db1.t1 to u1@localhost;
flush tables;
connect con_u1,localhost,u1,,db1;
set optimizer_record_context=ON;
select * from t1 where b < 10;
a b
1 1
2 2
3 3
# == Optimizer Context Tables
name
db1.t1
# === Optimizer Context DDLs
@ddls
CREATE SEQUENCE IF NOT EXISTS `db1`.`s1`;
CREATE TABLE IF NOT EXISTS `t1` (
`a` int(11) NOT NULL DEFAULT nextval(`db1`.`s1`),
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
disconnect con_u1;
connection default;
drop user u1@localhost;
#
# A table with two columns whose DEFAULT expressions depend on two
# different sequences: both must be captured, before the table
#
create sequence s2;
create table t2 (a int default nextval(s1), c int default nextval(s2));
select * from t2;
a c
# == Optimizer Context Tables
name
db1.t2
# === Optimizer Context DDLs
@ddls
CREATE SEQUENCE IF NOT EXISTS `db1`.`s2`;
CREATE SEQUENCE IF NOT EXISTS `db1`.`s1`;
CREATE TABLE IF NOT EXISTS `t2` (
`a` int(11) DEFAULT nextval(`db1`.`s1`),
`c` int(11) DEFAULT nextval(`db1`.`s2`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
drop table t2;
drop sequence s2;
#
# The sequence s3 that t3's DEFAULT depends on was dropped:
# ddls include both s3 and t3
#
create sequence s3;
create table t3 (a int default nextval(s3), d int);
insert into t3 (d) values (1);
drop sequence s3;
flush tables;
select * from t3;
a d
1 1
# == Optimizer Context Tables
name
db1.t3
# === Optimizer Context DDLs
@ddls
CREATE SEQUENCE IF NOT EXISTS `db1`.`s3`;
CREATE TABLE IF NOT EXISTS `t3` (
`a` int(11) DEFAULT nextval(`db1`.`s3`),
`d` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
SET STATEMENT sql_mode=REPLACE(REPLACE(@@sql_mode,'STRICT_ALL_TABLES',''),'STRICT_TRANS_TABLES','') FOR
REPLACE INTO db1.t3(a, d) VALUES (1, 1);
drop table t3;
#
# A query that also calls a stored function:
# ddls include both s1 and t1
#
create function f1() returns int return 1;
flush tables;
select f1() from t1 where b < 10;
f1()
1
1
1
# == Optimizer Context Tables
name
db1.t1
# === Optimizer Context DDLs
@ddls
CREATE SEQUENCE IF NOT EXISTS `db1`.`s1`;
CREATE TABLE IF NOT EXISTS `t1` (
`a` int(11) NOT NULL DEFAULT nextval(`db1`.`s1`),
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
drop function f1;
#
# The sequence s1 that t1's DEFAULT depends on was dropped and a
# view s1 created in its place:
# ddls include both s1 and t1
#
drop sequence s1;
create view s1 as select 1 as x;
flush tables;
select * from t1 where b < 10;
a b
1 1
2 2
3 3
# == Optimizer Context Tables
name
db1.t1
# === Optimizer Context DDLs
@ddls
CREATE SEQUENCE IF NOT EXISTS `db1`.`s1`;
CREATE TABLE IF NOT EXISTS `t1` (
`a` int(11) NOT NULL DEFAULT nextval(`db1`.`s1`),
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
#
# Same, as a prepared statement executed twice:
# ddls include both s1 and t1
#
flush tables;
prepare stmt from 'select * from t1 where b < 10';
execute stmt;
a b
1 1
2 2
3 3
execute stmt;
a b
1 1
2 2
3 3
# == Optimizer Context Tables
name
db1.t1
# === Optimizer Context DDLs
@ddls
CREATE SEQUENCE IF NOT EXISTS `db1`.`s1`;
CREATE TABLE IF NOT EXISTS `t1` (
`a` int(11) NOT NULL DEFAULT nextval(`db1`.`s1`),
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`a`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
deallocate prepare stmt;
drop view s1;
drop table t1;
#
# The sequence db2.s1 that db1.t1's DEFAULT depends on is in a
# different database: ddls include db2.s1 and t1
#
create database db2;
create sequence db2.s1 start 100 increment 10;
create table t1 (a int default nextval(db2.s1), b int);
insert into t1 (b) values (1);
flush tables;
select * from t1 where b < 10;
a b
100 1
# == Optimizer Context Tables
name
db1.t1
# === Optimizer Context DDLs
@ddls
CREATE SEQUENCE IF NOT EXISTS `db2`.`s1`;
CREATE TABLE IF NOT EXISTS `t1` (
`a` int(11) DEFAULT nextval(`db2`.`s1`),
`b` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
SET STATEMENT sql_mode=REPLACE(REPLACE(@@sql_mode,'STRICT_ALL_TABLES',''),'STRICT_TRANS_TABLES','') FOR
REPLACE INTO db1.t1(a, b) VALUES (100, 1);
drop table t1;
drop database db2;
#
# The sequence s1 is used in the query and t1's DEFAULT depends on it.
# Whichever order they are listed in, ddls include s1's definition
# with its current value, and t1
#
create sequence s1 start 100 increment 10 maxvalue 200 cycle;
create table t1 (a int default nextval(s1), b int);
insert into t1 (b) values (1),(2);
flush tables;
select * from s1, t1;
next_not_cached_value minimum_value maximum_value start_value increment cache_size cycle_option cycle_count a b
201 1 200 100 10 1000 1 0 100 1
201 1 200 100 10 1000 1 0 110 2
# == Optimizer Context Tables
name
db1.t1
# === Optimizer Context DDLs
@ddls
CREATE SEQUENCE IF NOT EXISTS `db1`.`s1`;
CREATE TABLE IF NOT EXISTS `t1` (
`a` int(11) DEFAULT nextval(`db1`.`s1`),
`b` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
DROP SEQUENCE IF EXISTS db1.s1;
CREATE TABLE IF NOT EXISTS `s1` (
`next_not_cached_value` bigint(21) NOT NULL,
`minimum_value` bigint(21) NOT NULL,
`maximum_value` bigint(21) NOT NULL,
`start_value` bigint(21) NOT NULL COMMENT 'start value when sequences is created or value if RESTART is used',
`increment` bigint(21) NOT NULL COMMENT 'increment value',
`cache_size` bigint(21) unsigned NOT NULL,
`cycle_option` tinyint(1) unsigned NOT NULL COMMENT '0 if no cycles are allowed, 1 if the sequence should begin a new cycle when maximum_value is passed',
`cycle_count` bigint(21) NOT NULL COMMENT 'How many cycles have been done'
) ENGINE=MyISAM SEQUENCE=1;
SELECT SETVAL(db1.s1, 100);
flush tables;
select * from t1, s1;
a b next_not_cached_value minimum_value maximum_value start_value increment cache_size cycle_option cycle_count
100 1 201 1 200 100 10 1000 1 0
110 2 201 1 200 100 10 1000 1 0
# == Optimizer Context Tables
name
db1.t1
# === Optimizer Context DDLs
@ddls
CREATE TABLE IF NOT EXISTS `s1` (
`next_not_cached_value` bigint(21) NOT NULL,
`minimum_value` bigint(21) NOT NULL,
`maximum_value` bigint(21) NOT NULL,
`start_value` bigint(21) NOT NULL COMMENT 'start value when sequences is created or value if RESTART is used',
`increment` bigint(21) NOT NULL COMMENT 'increment value',
`cache_size` bigint(21) unsigned NOT NULL,
`cycle_option` tinyint(1) unsigned NOT NULL COMMENT '0 if no cycles are allowed, 1 if the sequence should begin a new cycle when maximum_value is passed',
`cycle_count` bigint(21) NOT NULL COMMENT 'How many cycles have been done'
) ENGINE=MyISAM SEQUENCE=1;
SELECT SETVAL(db1.s1, 100);
CREATE TABLE IF NOT EXISTS `t1` (
`a` int(11) DEFAULT nextval(`db1`.`s1`),
`b` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
drop table t1;
drop sequence s1;
# End of 13.2 tests
drop database db1;

View File

@@ -329,4 +329,144 @@ explain select * from seq_1_to_15_step_2 where seq = 5;
--echo # End of 13.1 tests
--echo #
--echo # MDEV-40598: Capture sequences used only in column DEFAULT expressions
--echo #
create sequence s1;
create table t1 (a int primary key default nextval(s1), b int);
--echo # The INSERT itself evaluates the DEFAULT, so s1 is already open as
--echo # part of this very statement; ddls should include both s1 and t1
insert into t1 (b) values (1),(2),(3);
--source include/opt_context_list_tables_ddls.inc
--echo #
--echo # s1 was opened only by the INSERT above and closed again at the
--echo # end of that statement; this SELECT never evaluates DEFAULT
--echo # NEXTVAL() itself, so it doesn't reopen s1 through normal means
--echo # either. It is still captured, resolved independently by name:
--echo # ddls should include both s1 and t1, with s1 appearing before t1
--echo # so that replay can recreate them
select * from t1 where b < 10;
--source include/opt_context_list_tables_ddls.inc
--echo #
--echo # Under LOCK TABLES, s1 sits in thd->open_tables merely because it
--echo # is locked, not because this particular SELECT touches it - its
--echo # current value must not be captured just from being locked
--echo #
flush tables;
lock tables t1 read, s1 read;
select * from t1 where b < 10;
--source include/opt_context_list_tables_ddls.inc
unlock tables;
--echo #
--echo # A user with SELECT on t1 but no privilege on s1:
--echo # ddls include both s1 and t1
--echo #
create user u1@localhost;
grant select on db1.t1 to u1@localhost;
flush tables;
connect (con_u1,localhost,u1,,db1);
set optimizer_record_context=ON;
select * from t1 where b < 10;
--source include/opt_context_list_tables_ddls.inc
disconnect con_u1;
connection default;
drop user u1@localhost;
--echo #
--echo # A table with two columns whose DEFAULT expressions depend on two
--echo # different sequences: both must be captured, before the table
--echo #
create sequence s2;
create table t2 (a int default nextval(s1), c int default nextval(s2));
#flush tables;
select * from t2;
--source include/opt_context_list_tables_ddls.inc
drop table t2;
drop sequence s2;
--echo #
--echo # The sequence s3 that t3's DEFAULT depends on was dropped:
--echo # ddls include both s3 and t3
--echo #
create sequence s3;
create table t3 (a int default nextval(s3), d int);
insert into t3 (d) values (1);
drop sequence s3;
flush tables;
select * from t3;
--source include/opt_context_list_tables_ddls.inc
drop table t3;
--echo #
--echo # A query that also calls a stored function:
--echo # ddls include both s1 and t1
--echo #
create function f1() returns int return 1;
flush tables;
select f1() from t1 where b < 10;
--source include/opt_context_list_tables_ddls.inc
drop function f1;
--echo #
--echo # The sequence s1 that t1's DEFAULT depends on was dropped and a
--echo # view s1 created in its place:
--echo # ddls include both s1 and t1
--echo #
drop sequence s1;
create view s1 as select 1 as x;
flush tables;
select * from t1 where b < 10;
--source include/opt_context_list_tables_ddls.inc
--echo #
--echo # Same, as a prepared statement executed twice:
--echo # ddls include both s1 and t1
--echo #
flush tables;
prepare stmt from 'select * from t1 where b < 10';
execute stmt;
execute stmt;
--source include/opt_context_list_tables_ddls.inc
deallocate prepare stmt;
drop view s1;
drop table t1;
--echo #
--echo # The sequence db2.s1 that db1.t1's DEFAULT depends on is in a
--echo # different database: ddls include db2.s1 and t1
--echo #
create database db2;
create sequence db2.s1 start 100 increment 10;
create table t1 (a int default nextval(db2.s1), b int);
insert into t1 (b) values (1);
flush tables;
select * from t1 where b < 10;
--source include/opt_context_list_tables_ddls.inc
drop table t1;
drop database db2;
--echo #
--echo # The sequence s1 is used in the query and t1's DEFAULT depends on it.
--echo # Whichever order they are listed in, ddls include s1's definition
--echo # with its current value, and t1
--echo #
create sequence s1 start 100 increment 10 maxvalue 200 cycle;
create table t1 (a int default nextval(s1), b int);
insert into t1 (b) values (1),(2);
flush tables;
select * from s1, t1;
--source include/opt_context_list_tables_ddls.inc
flush tables;
select * from t1, s1;
--source include/opt_context_list_tables_ddls.inc
drop table t1;
drop sequence s1;
--echo # End of 13.2 tests
drop database db1;

View File

@@ -20,6 +20,7 @@
#include "set_var.h"
#include "sql_class.h"
#include "opt_context_store_replay.h"
#include "sql_base.h"
#include "sql_show.h"
#include "my_json_writer.h"
#include "hash.h"
@@ -204,6 +205,9 @@ static void append_base_table_name(const TABLE *table, String *buf);
static bool parse_range_cost_estimate(MEM_ROOT*, json_engine_t *je,
String *err_buf, Cost_estimate *cost);
static bool store_db_ddl(THD *thd, HASH *db_name_hash, String &script,
const char *db_name, bool req_use_db_stmt);
static char *strdup_root(MEM_ROOT *root, const String *str)
{
return strmake_root(root, str->ptr(), str->length());
@@ -452,6 +456,111 @@ static bool get_create_table_stmt(THD *thd, TABLE_LIST *tbl, String *ddl)
return res;
}
/*
@brief
Append "SELECT SETVAL(seq_name, <current value>);" for the sequence
table @arg tbl into @arg qry_ctx_script, so that replaying the
dumped script continues handing out NEXTVAL()s from where the
original sequence had reached.
@return
false when no error occurred during the computation
*/
static void dump_sequence_current_value(THD *thd, TABLE_LIST *tbl,
const String *full_tbl_name,
String &qry_ctx_script)
{
const char *key;
uint length= get_table_def_key(tbl, &key); // table def key = hash key
SEQUENCE_LAST_VALUE *entry= (SEQUENCE_LAST_VALUE *) my_hash_search(
&thd->sequences, (uchar *) key, length);
SEQUENCE *seq= tbl->table->s->sequence;
DBUG_ASSERT(seq);
longlong value;
if (entry && !entry->check_version(tbl->table))
{
/*
Set the sequence so that the next NEXTVAL returns the value that
was last handed out (entry->value): SETVAL(x) makes the next
NEXTVAL return x + increment, so use entry->value - increment.
Keep the argument within the sequence bounds - for an ascending
sequence it may fall below min_value, for a descending one it may
rise above max_value, and SETVAL rejects out-of-range values.
*/
longlong candidate= entry->value - seq->increment;
value= seq->increment > 0 ? MY_MAX(candidate, seq->min_value)
: MY_MIN(candidate, seq->max_value);
}
else
value= seq->reserved_until;
qry_ctx_script.append(STRING_WITH_LEN("SELECT SETVAL("));
qry_ctx_script.append(*full_tbl_name);
qry_ctx_script.append(STRING_WITH_LEN(", "));
qry_ctx_script.append_longlong(value);
qry_ctx_script.append(STRING_WITH_LEN(");\n\n"));
}
/*
@brief
Dump a placeholder "CREATE SEQUENCE IF NOT EXISTS" for the sequence
@arg tbl into @arg qry_ctx_script, unless it has already been dumped.
The sequence is also recorded in @arg dep_seq_hash.
@detail
A table's DEFAULT expression can depend on a sequence
(e.g. "a INT DEFAULT NEXTVAL(s1)") that the current statement never
opens itself (e.g. a plain SELECT never evaluates DEFAULT NEXTVAL()).
Such sequences are not in thd->lex->query_tables and are reached via
TABLE::internal_tables instead.
@return
false when no error occurred during the computation
*/
static bool dump_sequence_placeholder(THD *thd, TABLE_LIST *tbl,
HASH *table_name_hash, HASH *db_name_hash,
HASH *dep_seq_hash, String &qry_ctx_script)
{
StringBuffer<256> full_tbl_name;
LEX_CSTRING *tbl_name_key;
append_table_or_view_name(tbl, &full_tbl_name);
if (my_hash_search(table_name_hash, (uchar *) full_tbl_name.c_ptr_safe(),
full_tbl_name.length()))
return false; // Already dumped
if (!(tbl_name_key= (LEX_CSTRING *) thd->alloc(sizeof(LEX_CSTRING))) ||
!(tbl_name_key->str= strdup_root(thd->mem_root, &full_tbl_name)))
return true; // OOM
tbl_name_key->length= strlen(tbl_name_key->str);
/*
Remember that this sequence was dumped only as a placeholder, so that
dump_sql_script() can replace it with the real definition if the query
uses the sequence directly as well.
*/
if (my_hash_insert(table_name_hash, (uchar *) tbl_name_key) ||
my_hash_insert(dep_seq_hash, (uchar *) tbl_name_key))
return true; // OOM
if (store_db_ddl(thd, db_name_hash, qry_ctx_script,
tbl->db.str, false))
return true;
qry_ctx_script.append(STRING_WITH_LEN("CREATE SEQUENCE IF NOT EXISTS "));
append_identifier(thd, &qry_ctx_script, tbl->db.str, tbl->db.length);
qry_ctx_script.append(STRING_WITH_LEN("."));
append_identifier(thd, &qry_ctx_script, tbl->table_name.str,
tbl->table_name.length);
qry_ctx_script.append(STRING_WITH_LEN(";\n\n"));
return false;
}
/*
System variables that are required for the optimizer context,
but do not have "optimizer" in their name.
@@ -720,6 +829,7 @@ bool Optimizer_context_recorder::dump_sql_script(THD* thd, String &sql_script)
HASH table_name_hash;
HASH used_storage_engines;
HASH db_name_hash;
HASH dep_seq_hash; // sequences dumped only as a DEFAULT dependency
List<TABLE_LIST> tables_list;
bool res= false;
@@ -745,6 +855,9 @@ bool Optimizer_context_recorder::dump_sql_script(THD* thd, String &sql_script)
my_hash_init(key_memory_trace_ddl_info, &db_name_hash,
system_charset_info, 16, 0, 0, get_hash_key, NULL,
HASH_UNIQUE) ||
my_hash_init(key_memory_trace_ddl_info, &dep_seq_hash,
system_charset_info, 16, 0, 0, get_hash_key, NULL,
HASH_UNIQUE) ||
store_db_ddl(thd, &db_name_hash, qry_ctx_script, thd->get_db(), true))
{
res= true; // OOM
@@ -765,20 +878,37 @@ bool Optimizer_context_recorder::dump_sql_script(THD* thd, String &sql_script)
*/
if (my_hash_search(&table_name_hash, (uchar *) full_tbl_name.c_ptr_safe(),
full_tbl_name.length()))
continue;
if (!(tbl_name_key= (LEX_CSTRING *) thd->alloc(sizeof(LEX_CSTRING))) ||
!(tbl_name_key->str= strdup_root(thd->mem_root, &full_tbl_name)))
{
res= true;
break;
/*
If this sequence has been dumped as a placeholder,
add a DROP SEQUENCE statement and then dump the real definition,
if we haven't done that already.
*/
uchar *dep_seq= my_hash_search(&dep_seq_hash,
(uchar *) full_tbl_name.c_ptr_safe(),
full_tbl_name.length());
if (!dep_seq)
continue;
my_hash_delete(&dep_seq_hash, dep_seq);
qry_ctx_script.append(STRING_WITH_LEN("DROP SEQUENCE IF EXISTS "));
qry_ctx_script.append(full_tbl_name);
qry_ctx_script.append(STRING_WITH_LEN(";\n\n"));
}
tbl_name_key->length= strlen(tbl_name_key->str);
if (my_hash_insert(&table_name_hash, (uchar *) tbl_name_key))
else
{
res= true; // OOM
break;
if (!(tbl_name_key= (LEX_CSTRING *) thd->alloc(sizeof(LEX_CSTRING))) ||
!(tbl_name_key->str= strdup_root(thd->mem_root, &full_tbl_name)))
{
res= true;
break;
}
tbl_name_key->length= strlen(tbl_name_key->str);
if (my_hash_insert(&table_name_hash, (uchar *) tbl_name_key))
{
res= true; // OOM
break;
}
}
uniq_tables_list.push_front(tbl);
@@ -790,6 +920,25 @@ bool Optimizer_context_recorder::dump_sql_script(THD* thd, String &sql_script)
break;
}
/*
Dump the sequences that this table's DEFAULT expressions depend on
first, as they must exist before its CREATE TABLE.
*/
for (TABLE_LIST *seq_tbl= tbl->is_view() ? NULL
: tbl->table->internal_tables;
seq_tbl; seq_tbl= seq_tbl->next_global)
{
if (dump_sequence_placeholder(thd, seq_tbl, &table_name_hash,
&db_name_hash, &dep_seq_hash,
qry_ctx_script))
{
res= true;
break;
}
}
if (res)
break;
/* Add CREATE TABLE|VIEW statement */
if (tbl->is_view())
{
@@ -819,35 +968,8 @@ bool Optimizer_context_recorder::dump_sql_script(THD* thd, String &sql_script)
*/
if (tbl->table->s->sequence)
{
const char *key;
uint length= get_table_def_key(tbl, &key); // table def key = hash key
SEQUENCE_LAST_VALUE *entry= (SEQUENCE_LAST_VALUE *) my_hash_search(
&thd->sequences, (uchar *) key, length);
SEQUENCE *seq= tbl->table->s->sequence;
longlong value;
if (entry && !entry->check_version(tbl->table))
{
/*
Set the sequence so that the next NEXTVAL returns the value that
was last handed out (entry->value): SETVAL(x) makes the next
NEXTVAL return x + increment, so use entry->value - increment.
Keep the argument within the sequence bounds - for an ascending
sequence it may fall below min_value, for a descending one it may
rise above max_value, and SETVAL rejects out-of-range values.
*/
longlong candidate= entry->value - seq->increment;
value= seq->increment > 0 ? MY_MAX(candidate, seq->min_value)
: MY_MIN(candidate, seq->max_value);
}
else
value= seq->reserved_until;
qry_ctx_script.append(STRING_WITH_LEN("SELECT SETVAL("));
qry_ctx_script.append(full_tbl_name);
qry_ctx_script.append(STRING_WITH_LEN(", "));
qry_ctx_script.append_longlong(value);
qry_ctx_script.append(STRING_WITH_LEN(");\n\n"));
dump_sequence_current_value(thd, tbl, &full_tbl_name,
qry_ctx_script);
continue;
}
else if (tbl->table->s->db_type() &&
@@ -965,6 +1087,7 @@ end:
my_hash_free(&table_name_hash);
my_hash_free(&used_storage_engines);
my_hash_free(&db_name_hash);
my_hash_free(&dep_seq_hash);
return res;
}