From 4d3851d69c2c77c775a89ff031554d58e58c3798 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 21 Sep 2026 19:23:26 +0300 Subject: [PATCH 1/2] MDEV-40821 Wrong result after DELETE HISTORY or PORTION OF (10.11) The parser assigned versioning conditions of DELETE HISTORY ... BEFORE SYSTEM_TIME and period conditions of DELETE ... FOR PORTION OF to the table pointed by last_table(). When the BEFORE SYSTEM_TIME or FOR PORTION OF expression references another table (e.g. via a subquery or a sequence function), last_table() is already switched to that table, and the DELETE target gets no conditions. DELETE HISTORY then deletes current rows permanently instead of history, keeps the history it had to purge, and is checked for DELETE privilege instead of DELETE HISTORY. DELETE HISTORY from a non-versioned table is not rejected with ER_VERS_NOT_VERSIONED. DELETE ... FOR PORTION OF deletes all rows of the table, ignoring the period. The fix assigns the conditions to the TABLE_LIST returned by delete_single_table, i.e. the DELETE target itself, regardless of tables added later in the statement. DBUG_ASSERT in TABLE::vers_switch_partition() is removed. It checked delete_history on last_table() and assumed no other table is opened for write by DELETE HISTORY, which is false for the underlying table of a view or a table locked by SELECT ... FOR UPDATE in the history point. The DELETE HISTORY target is already skipped there by its own delete_history check. --- mysql-test/suite/period/r/delete.result | 17 ++++ mysql-test/suite/period/t/delete.test | 17 ++++ .../suite/versioning/r/delete_history.result | 97 +++++++++++++++++++ .../suite/versioning/r/not_embedded.result | 18 ++++ .../suite/versioning/t/delete_history.test | 82 ++++++++++++++++ .../suite/versioning/t/not_embedded.test | 20 ++++ sql/sql_base.cc | 2 - sql/sql_yacc.yy | 17 ++-- 8 files changed, 260 insertions(+), 10 deletions(-) diff --git a/mysql-test/suite/period/r/delete.result b/mysql-test/suite/period/r/delete.result index 2b0a97e03d8e1..5fb00d4d6a00f 100644 --- a/mysql-test/suite/period/r/delete.result +++ b/mysql-test/suite/period/r/delete.result @@ -421,3 +421,20 @@ id s e drop table t1; drop table log_tbl; drop procedure log; +# +# MDEV-40821 SIGSEGV in Window_funcs_sort::setup +# +# Bound referencing another table must not delete whole rows +create table t1 (x int, s date, e date, period for app(s,e)); +create table t2 (d date); +insert into t1 values (1, '2000-01-01', '2000-01-10'), +(2, '2000-02-01', '2000-02-10'); +insert into t2 values ('2000-01-05'); +delete from t1 for portion of app from (select d from t2) to '2000-01-20'; +ERROR HY000: Expression in FOR PORTION OF must be constant +select * from t1; +x s e +1 2000-01-01 2000-01-10 +2 2000-02-01 2000-02-10 +drop table t1, t2; +# End of 10.11 tests diff --git a/mysql-test/suite/period/t/delete.test b/mysql-test/suite/period/t/delete.test index 676fa256f95ed..b6380e53c35de 100644 --- a/mysql-test/suite/period/t/delete.test +++ b/mysql-test/suite/period/t/delete.test @@ -268,3 +268,20 @@ select * from t1; drop table t1; drop table log_tbl; drop procedure log; + +--echo # +--echo # MDEV-40821 SIGSEGV in Window_funcs_sort::setup +--echo # +--echo # Bound referencing another table must not delete whole rows +create table t1 (x int, s date, e date, period for app(s,e)); +create table t2 (d date); +insert into t1 values (1, '2000-01-01', '2000-01-10'), + (2, '2000-02-01', '2000-02-10'); +insert into t2 values ('2000-01-05'); +--error ER_NOT_CONSTANT_EXPRESSION +delete from t1 for portion of app from (select d from t2) to '2000-01-20'; +--sorted_result +select * from t1; +drop table t1, t2; + +--echo # End of 10.11 tests diff --git a/mysql-test/suite/versioning/r/delete_history.result b/mysql-test/suite/versioning/r/delete_history.result index e0ba80e24363b..adff2933a9987 100644 --- a/mysql-test/suite/versioning/r/delete_history.result +++ b/mysql-test/suite/versioning/r/delete_history.result @@ -504,4 +504,101 @@ create table t (a int) with system versioning; insert into t values (1),(2); prepare stmt from 'select * from t for system_time as of timestamp ?'; drop table t; +# +# MDEV-40821 SIGSEGV in Window_funcs_sort::setup +# +# Point referencing another table must purge only history +create table t1 (x int) with system versioning; +create table t2 (d timestamp(6)); +set timestamp= unix_timestamp('2000-01-01 00:00:01'); +insert into t1 values (1), (2); +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +delete from t1 where x = 2; +insert into t2 values ('2000-01-01 00:00:03'); +set timestamp= default; +delete history from t1 before system_time (select d from t2); +select * from t1; +x +1 +select * from t1 for system_time all; +x +1 +# Prepared statement +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +insert into t1 values (3); +delete from t1 where x = 3; +set timestamp= default; +prepare stmt from 'delete history from t1 before system_time (select d from t2)'; +execute stmt; +select * from t1 for system_time all; +x +1 +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +insert into t1 values (4); +delete from t1 where x = 4; +set timestamp= default; +execute stmt; +select * from t1 for system_time all; +x +1 +drop prepare stmt; +# Sequence function in the point +create sequence s start with 3 engine=myisam; +select nextval(s); +nextval(s) +3 +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +insert into t1 values (5); +delete from t1 where x = 5; +set timestamp= default; +delete history from t1 before system_time +timestampadd(second, lastval(s), '2000-01-01 00:00:00'); +select * from t1 for system_time all; +x +1 +drop sequence s; +# Non-versioned table must be rejected +create table t3 (x int); +insert into t3 values (1); +delete history from t3 before system_time (select d from t2); +ERROR HY000: Table `t3` is not system-versioned +select * from t3; +x +1 +drop table t3; +# View over partitioned table +create table t3 (x int) with system versioning +partition by system_time limit 1; +create view v as select * from t3; +delete history from v; +ERROR 42S02: 'v' is a view +drop view v; +drop table t3; +# Locking subquery on partitioned table +create table t3 (d timestamp(6)) with system versioning +partition by system_time limit 1; +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +insert into t1 values (6); +delete from t1 where x = 6; +insert into t3 values ('2000-01-01 00:00:03'); +set timestamp= default; +delete history from t1 before system_time (select d from t3 for update); +select * from t1 for system_time all; +x +1 +# Subquery with its own FOR SYSTEM_TIME +create table t4 (d timestamp(6)) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +insert into t1 values (7); +delete from t1 where x = 7; +insert into t4 values ('2000-01-01 00:00:03'); +set timestamp= unix_timestamp('2000-01-01 00:00:03'); +update t4 set d= '2000-01-01 00:00:00'; +set timestamp= default; +delete history from t1 before system_time +(select max(d) from t4 for system_time all); +select * from t1 for system_time all; +x +1 +drop table t1, t2, t3, t4; # End of 10.11 tests diff --git a/mysql-test/suite/versioning/r/not_embedded.result b/mysql-test/suite/versioning/r/not_embedded.result index 5d8380e096962..eb03338fb115c 100644 --- a/mysql-test/suite/versioning/r/not_embedded.result +++ b/mysql-test/suite/versioning/r/not_embedded.result @@ -93,3 +93,21 @@ connection default; commit; drop table t; set timestamp= default; +# +# MDEV-40821 SIGSEGV in Window_funcs_sort::setup +# +# DELETE privilege is not enough for DELETE HISTORY with a subquery +create database mysqltest; +create table mysqltest.t (a int) with system versioning; +create table mysqltest.t2 (d timestamp(6)); +insert into mysqltest.t2 values (now(6)); +create user mysqltest_1@localhost; +grant select, delete on mysqltest.* to mysqltest_1@localhost; +connect user1,localhost,mysqltest_1,,mysqltest; +delete history from t before system_time (select d from t2); +ERROR 42000: DELETE HISTORY command denied to user 'mysqltest_1'@'localhost' for table `mysqltest`.`t` +disconnect user1; +connection default; +drop user mysqltest_1@localhost; +drop database mysqltest; +# End of 10.11 tests diff --git a/mysql-test/suite/versioning/t/delete_history.test b/mysql-test/suite/versioning/t/delete_history.test index 65f1e8f22245b..d1483132be931 100644 --- a/mysql-test/suite/versioning/t/delete_history.test +++ b/mysql-test/suite/versioning/t/delete_history.test @@ -371,6 +371,88 @@ insert into t values (1),(2); prepare stmt from 'select * from t for system_time as of timestamp ?'; drop table t; +--echo # +--echo # MDEV-40821 SIGSEGV in Window_funcs_sort::setup +--echo # +--echo # Point referencing another table must purge only history +create table t1 (x int) with system versioning; +create table t2 (d timestamp(6)); +set timestamp= unix_timestamp('2000-01-01 00:00:01'); +insert into t1 values (1), (2); +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +delete from t1 where x = 2; +insert into t2 values ('2000-01-01 00:00:03'); +set timestamp= default; +delete history from t1 before system_time (select d from t2); +select * from t1; +select * from t1 for system_time all; +--echo # Prepared statement +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +insert into t1 values (3); +delete from t1 where x = 3; +set timestamp= default; +prepare stmt from 'delete history from t1 before system_time (select d from t2)'; +execute stmt; +select * from t1 for system_time all; +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +insert into t1 values (4); +delete from t1 where x = 4; +set timestamp= default; +execute stmt; +select * from t1 for system_time all; +drop prepare stmt; +--echo # Sequence function in the point +create sequence s start with 3 engine=myisam; +--disable_ps2_protocol +select nextval(s); +--enable_ps2_protocol +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +insert into t1 values (5); +delete from t1 where x = 5; +set timestamp= default; +delete history from t1 before system_time + timestampadd(second, lastval(s), '2000-01-01 00:00:00'); +select * from t1 for system_time all; +drop sequence s; +--echo # Non-versioned table must be rejected +create table t3 (x int); +insert into t3 values (1); +--error ER_VERS_NOT_VERSIONED +delete history from t3 before system_time (select d from t2); +select * from t3; +drop table t3; +--echo # View over partitioned table +create table t3 (x int) with system versioning + partition by system_time limit 1; +create view v as select * from t3; +--error ER_IT_IS_A_VIEW +delete history from v; +drop view v; +drop table t3; +--echo # Locking subquery on partitioned table +create table t3 (d timestamp(6)) with system versioning + partition by system_time limit 1; +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +insert into t1 values (6); +delete from t1 where x = 6; +insert into t3 values ('2000-01-01 00:00:03'); +set timestamp= default; +delete history from t1 before system_time (select d from t3 for update); +select * from t1 for system_time all; +--echo # Subquery with its own FOR SYSTEM_TIME +create table t4 (d timestamp(6)) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:02'); +insert into t1 values (7); +delete from t1 where x = 7; +insert into t4 values ('2000-01-01 00:00:03'); +set timestamp= unix_timestamp('2000-01-01 00:00:03'); +update t4 set d= '2000-01-01 00:00:00'; +set timestamp= default; +delete history from t1 before system_time + (select max(d) from t4 for system_time all); +select * from t1 for system_time all; +drop table t1, t2, t3, t4; + --echo # End of 10.11 tests --source suite/versioning/common_finish.inc diff --git a/mysql-test/suite/versioning/t/not_embedded.test b/mysql-test/suite/versioning/t/not_embedded.test index 38f42a44587c1..73953045146f4 100644 --- a/mysql-test/suite/versioning/t/not_embedded.test +++ b/mysql-test/suite/versioning/t/not_embedded.test @@ -105,3 +105,23 @@ reap; commit; drop table t; set timestamp= default; + +--echo # +--echo # MDEV-40821 SIGSEGV in Window_funcs_sort::setup +--echo # +--echo # DELETE privilege is not enough for DELETE HISTORY with a subquery +create database mysqltest; +create table mysqltest.t (a int) with system versioning; +create table mysqltest.t2 (d timestamp(6)); +insert into mysqltest.t2 values (now(6)); +create user mysqltest_1@localhost; +grant select, delete on mysqltest.* to mysqltest_1@localhost; +connect (user1,localhost,mysqltest_1,,mysqltest); +--error ER_TABLEACCESS_DENIED_ERROR +delete history from t before system_time (select d from t2); +disconnect user1; +connection default; +drop user mysqltest_1@localhost; +drop database mysqltest; + +--echo # End of 10.11 tests diff --git a/sql/sql_base.cc b/sql/sql_base.cc index dee70e54d0368..8377d6c635b45 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1878,8 +1878,6 @@ bool TABLE::vers_switch_partition(THD *thd, TABLE_LIST *table_list, } break; } - DBUG_ASSERT(!thd->lex->last_table() || - !thd->lex->last_table()->vers_conditions.delete_history); } if (table_list->partition_names) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 38704d7909de9..d9d5dd554e608 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1675,6 +1675,7 @@ rule: derived_table_list table_reference_list_parens nested_table_reference_list join_table_parens update_table_list table_function + delete_single_table %type date_time_type; %type interval @@ -13669,7 +13670,7 @@ delete_part2: | HISTORY_SYM delete_single_table opt_delete_system_time { LEX *lex= Lex; - lex->last_table()->vers_conditions= lex->vers_conditions; + $2->vers_conditions= lex->vers_conditions; lex->sql_command= SQLCOM_DELETE; if (!(lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_delete(false))) @@ -13697,12 +13698,12 @@ delete_single_table: Lex->query_tables= 0; Lex->query_tables_last= &Lex->query_tables; - if (unlikely(!Select-> - add_table_to_list(thd, $2, NULL, TL_OPTION_UPDATING, - YYPS->m_lock_type, - YYPS->m_mdl_type, - NULL, - $3))) + if (!($$= Select-> + add_table_to_list(thd, $2, NULL, TL_OPTION_UPDATING, + YYPS->m_lock_type, + YYPS->m_mdl_type, + NULL, + $3))) MYSQL_YYABORT; Lex->auxiliary_table_list.first->correspondent_table= Lex->query_tables; @@ -13715,7 +13716,7 @@ delete_single_table_for_period: delete_single_table opt_for_portion_of_time_clause { if ($2) - Lex->last_table()->period_conditions= Lex->period_conditions; + $1->period_conditions= Lex->period_conditions; } ; From 958a290cddf818c40336a161162859e682feb8b7 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Wed, 14 Oct 2026 17:21:54 +0300 Subject: [PATCH 2/2] MDEV-40821 Wrong result after PORTION OF with subquery (11.4) Single-table DELETE converted to multi-table (when a subquery references the target table) skipped FOR PORTION OF row splitting and the constant expression check, so it deleted whole rows instead of the portion. The fix prohibits processing DELETE ... FOR PORTION OF as multi-table, like DELETE ... RETURNING. --- mysql-test/suite/period/r/delete.result | 13 +++++++++++++ mysql-test/suite/period/t/delete.test | 13 +++++++++++++ sql/sql_delete.cc | 3 ++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/period/r/delete.result b/mysql-test/suite/period/r/delete.result index 5fb00d4d6a00f..65a28f97fab49 100644 --- a/mysql-test/suite/period/r/delete.result +++ b/mysql-test/suite/period/r/delete.result @@ -438,3 +438,16 @@ x s e 2 2000-02-01 2000-02-10 drop table t1, t2; # End of 10.11 tests +# +# MDEV-40821 SIGSEGV in Window_funcs_sort::setup +# +# Subquery on the target: row must be split, not deleted +create table t1 (id int, s date, e date, period for app(s,e)); +insert into t1 values (1, '2000-01-01', '2000-01-10'); +delete from t1 for portion of app from '2000-01-05' to '2000-01-20' + where id in (select id from t1); +select * from t1; +id s e +1 2000-01-01 2000-01-05 +drop table t1; +# End of 11.4 tests diff --git a/mysql-test/suite/period/t/delete.test b/mysql-test/suite/period/t/delete.test index b6380e53c35de..ae28e9f7527bd 100644 --- a/mysql-test/suite/period/t/delete.test +++ b/mysql-test/suite/period/t/delete.test @@ -285,3 +285,16 @@ select * from t1; drop table t1, t2; --echo # End of 10.11 tests + +--echo # +--echo # MDEV-40821 SIGSEGV in Window_funcs_sort::setup +--echo # +--echo # Subquery on the target: row must be split, not deleted +create table t1 (id int, s date, e date, period for app(s,e)); +insert into t1 values (1, '2000-01-01', '2000-01-10'); +delete from t1 for portion of app from '2000-01-05' to '2000-01-20' + where id in (select id from t1); +select * from t1; +drop table t1; + +--echo # End of 11.4 tests diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index fed5b4c7f7ec8..9f059d8331290 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1606,7 +1606,8 @@ bool Sql_cmd_delete::processing_as_multitable_delete_prohibited(THD *thd) return ((select_lex->order_list.elements && select_lex->limit_params.select_limit) || - thd->lex->has_returning()); + thd->lex->has_returning() || + thd->lex->period_conditions.is_set()); }