summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Shulga <dmitry.shulga@mariadb.com>2020-10-26 12:28:01 +0700
committerDmitry Shulga <dmitry.shulga@mariadb.com>2020-10-26 12:28:01 +0700
commit12d2ce0c0890fa2f44e2f317919497b2abd09cef (patch)
tree7ac2bcd89b2d335c560ce365636f3df3ae573604
parentc2ac0ce1f02e3ae2b1de5c07ba40bed25c30dc40 (diff)
downloadmariadb-git-bb-10.4-MDEV-22805.tar.gz
MDEV-22805: SIGSEGV in check_fields on UPDATEbb-10.4-MDEV-22805
For debug build of MariaDB server running of the following test case will hit the assert `thd->lex->sql_command == SQLCOM_UPDATE' in the function check_fields() on attempt to execute the UPDATE statement. CREATE TABLE t1 (a INT); UPDATE t1 FOR PORTION OF APPTIME FROM (SELECT 1 FROM t1) TO 2 SET a = 1; Stack trace to the fired assert statement DBUG_ASSERT(thd->lex->sql_command == SQLCOM_UPDATE) listed below: mysql_execute_command() -> mysql_multi_update_prepare() --> Multiupdate_prelocking_strategy::handle_end() --> check_fiels() It's worth to note that this stack trace looks like a multi update statement is being executed. The fired assert is checked inside the function check_fields() in case table->has_period() returns the value true that in turns happens when temporal period specified in the UPDATE statement. Condition specified in the DEBUG_ASSERT statement returns the false value since the data member thd->lex->sql_command have the value SQLCOM_UPDATE_MULTI. So, the main question is why a program control flow go to the path prescribed for handling MULTI update statement despite of the fact that the ordinary UPDATE statement being executed. The answer is a way that SQL grammar rules written. When the statement UPDATE t1 FOR PORTION OF APPTIME FROM (SELECT 1 FROM t1) TO 2 SET a = 1; being parsed an action for the rule 'table_primary_ident' (part of this action is listed below to simplify description) is invoked to handle the table name 't1' specified in the clause 'SELECT 1 FROM t1'. table_primary_ident: table_ident opt_use_partition opt_for_system_time_clause opt_table_alias_clause opt_key_definition { SELECT_LEX *sel= Select; sel->table_join_options= 0; if (!($$= Select->add_table_to_list(thd, $1, $4, This action calls the method st_select_lex::add_table_to_list() to add the table name 't1' to the list of tables being used by the statement. Later, an action for the following grammar rule update_table_list: table_ident opt_use_partition for_portion_of_time_clause opt_table_alias_clause opt_key_definition { SELECT_LEX *sel= Select; sel->table_join_options= 0; if (!($$= Select->add_table_to_list(thd, $1, $4, is invoked to handle the clause 't1 FOR PORTION OF APPTIME FROM ... TO 2'. This action also calls the method st_select_lex::add_table_to_list() to add the table name 't1' to the list of tables being used by the statement. In result the table name 't1' contained twice in this list. Presence of duplicate names for the table 't1' in a list of table used by a statement leads to the fact that the function unique_table() called from the function mysql_update() returns the value true that forces implementation of the function mysql_update() to return the value 2 as a signal to fall through the case boundary of the switch statement placed in the function mysql_execute_statement() and start handling of the case for sql_command SQLCOM_UPDATE_MULTI. The compound statement block for the case SQLCOM_UPDATE_MULTI invokes the function mysql_multi_update_prepare() that executes the statement set thd->lex->sql_command= SQLCOM_UPDATE_MULTI; and after that calls the method Multiupdate_prelocking_strategy::handle_end(). Finally, this method invokes the check_field() function and assert is fired. The above analysis shows that update for a table that simultaneously specified both as a destination table of UPDATE statement and as a table taking part in subquery is actually treated by MariaDB server as multi-update statement. Taking into account that multi-update statement for temporal period table is not supported yet by MariaDB, correct way to fix the bug is to return the error ER_NOT_SUPPORTED_YET for this case.
-rw-r--r--mysql-test/suite/period/r/update.result12
-rw-r--r--mysql-test/suite/period/t/update.test18
-rw-r--r--sql/sql_parse.cc5
-rw-r--r--sql/sql_update.cc8
4 files changed, 43 insertions, 0 deletions
diff --git a/mysql-test/suite/period/r/update.result b/mysql-test/suite/period/r/update.result
index 05d1a2eb6d3..662db611106 100644
--- a/mysql-test/suite/period/r/update.result
+++ b/mysql-test/suite/period/r/update.result
@@ -281,3 +281,15 @@ create table t1 (s date, e date, period for app(s,e), f varchar(8)) engine=aria
insert into t1 values ('2024-05-13','2026-03-25','foo');
update t1 for portion of app from '2024-04-02' to '2026-03-15' set f = 'bar';
drop table t1;
+#
+# MDEV-22805 SIGSEGV in check_fields on UPDATE (optimized builds) | Assertion `thd->lex->sql_command == SQLCOM_UPDATE' failed.
+#
+CREATE TABLE t1 (a INT, b DATE, c DATE, PERIOD FOR APPTIME(b, c));
+INSERT INTO t1 VALUES(1, '1999-01-01', '2018-12-12');
+UPDATE t1 FOR PORTION OF APPTIME FROM (SELECT '1999-01-01' FROM t1 WHERE a=2) TO '2018-01-01' SET a = 100;
+ERROR 42000: This version of MariaDB doesn't yet support 'updating and querying the same temporal periods table'
+CREATE VIEW v1 AS SELECT * FROM t1;
+UPDATE v1 FOR PORTION OF APPTIME FROM (SELECT '1999-01-01' FROM t1 WHERE a=2) TO '2018-01-01' SET a = 100;
+ERROR 42S02: 'v1' is a view
+DROP VIEW v1;
+DROP TABLE t1;
diff --git a/mysql-test/suite/period/t/update.test b/mysql-test/suite/period/t/update.test
index 0f54dd80eec..d4213752b14 100644
--- a/mysql-test/suite/period/t/update.test
+++ b/mysql-test/suite/period/t/update.test
@@ -173,3 +173,21 @@ update t1 for portion of app from '2024-04-02' to '2026-03-15' set f = 'bar';
# cleanup
drop table t1;
+
+--echo #
+--echo # MDEV-22805 SIGSEGV in check_fields on UPDATE (optimized builds) | Assertion `thd->lex->sql_command == SQLCOM_UPDATE' failed.
+--echo #
+CREATE TABLE t1 (a INT, b DATE, c DATE, PERIOD FOR APPTIME(b, c));
+
+INSERT INTO t1 VALUES(1, '1999-01-01', '2018-12-12');
+
+# Without a patch the following statement crashs a server built in debug mode
+--error ER_NOT_SUPPORTED_YET
+UPDATE t1 FOR PORTION OF APPTIME FROM (SELECT '1999-01-01' FROM t1 WHERE a=2) TO '2018-01-01' SET a = 100;
+
+CREATE VIEW v1 AS SELECT * FROM t1;
+--error ER_IT_IS_A_VIEW
+UPDATE v1 FOR PORTION OF APPTIME FROM (SELECT '1999-01-01' FROM t1 WHERE a=2) TO '2018-01-01' SET a = 100;
+
+DROP VIEW v1;
+DROP TABLE t1; \ No newline at end of file
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 909cb245b2e..400b6d70273 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -4369,6 +4369,11 @@ mysql_execute_command(THD *thd)
/* mysql_update return 2 if we need to switch to multi-update */
if (up_result != 2)
break;
+ if (thd->lex->period_conditions.is_set())
+ {
+ DBUG_ASSERT(0); // Should never happen
+ goto error;
+ }
}
/* fall through */
case SQLCOM_UPDATE_MULTI:
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
index 2095f99d1ac..4c90d5d4221 100644
--- a/sql/sql_update.cc
+++ b/sql/sql_update.cc
@@ -420,6 +420,14 @@ int mysql_update(THD *thd,
DBUG_PRINT("info", ("Switch to multi-update"));
/* pass counter value */
thd->lex->table_count= table_count;
+ if (thd->lex->period_conditions.is_set())
+ {
+ my_error(ER_NOT_SUPPORTED_YET, MYF(0),
+ "updating and querying the same temporal periods table");
+
+ DBUG_RETURN(1);
+ }
+
/* convert to multiupdate */
DBUG_RETURN(2);
}