Hi, Igor! Generally all looks good, but see my notes and couple of questions below. On Thu, Jul 7, 2022 at 6:57 AM IgorBabaev <igor@mariadb.com> wrote:
revision-id: 78370ea0fd21f66a4084d488ddcebac541249241 (mariadb-10.6.1-477-g78370ea) parent(s): 2db18fdb3d68d906fbd188ec570a64502ba55849 author: Igor Babaev committer: Igor Babaev timestamp: 2022-07-06 21:57:23 -0700 message:
MDEV-28965 Assertion failure when preparing UPDATE with derived table in WHERE
This patch fixes not only the assertion failure in the function Field_iterator_table_ref::set_field_iterator() but also: - fixes the problem of forced materialization of derived tables used in subqueries contained in WHERE clauses of single-table and multi-table UPDATE and DELETE statements - fixes the problem of MDEV-17954 that prevented execution of multi-table DELETE statements if they use in their WHERE clauses references to the tables that are updated.
The patch must be considered a complement to the patch for MDEV-28883.
--- mysql-test/main/delete_use_source.result | 184 ++++++++++- mysql-test/main/delete_use_source.test | 120 ++++++++ mysql-test/main/derived.result | 336 +++++++++++++++++++++ mysql-test/main/derived.test | 210 +++++++++++++ mysql-test/main/derived_cond_pushdown.result | 27 +- mysql-test/main/derived_cond_pushdown.test | 2 +- mysql-test/main/multi_update.result | 2 - mysql-test/main/multi_update.test | 2 - mysql-test/main/subselect.result | 1 - mysql-test/main/subselect.test | 1 - mysql-test/main/subselect_no_exists_to_in.result | 1 - mysql-test/main/subselect_no_mat.result | 1 - mysql-test/main/subselect_no_opts.result | 1 - mysql-test/main/subselect_no_scache.result | 1 - mysql-test/main/subselect_no_semijoin.result | 1 - .../engines/iuds/r/update_delete_number.result | 22 +- .../suite/engines/iuds/t/update_delete_number.test | 20 +- sql/opt_subselect.cc | 56 +++- sql/sql_base.cc | 41 ++- sql/sql_delete.cc | 63 ++-- sql/sql_delete.h | 14 +- sql/sql_lex.cc | 28 +- sql/sql_lex.h | 1 + sql/sql_update.cc | 21 +- sql/sql_update.h | 16 +- sql/sql_yacc.yy | 13 + sql/table.cc | 10 +- 27 files changed, 1106 insertions(+), 89 deletions(-)
[skip]
--- a/mysql-test/suite/engines/iuds/t/update_delete_number.test +++ b/mysql-test/suite/engines/iuds/t/update_delete_number.test @@ -285,8 +285,18 @@ SELECT * FROM t1,t2 WHERE t2.c1=t1.c2; DELETE FROM a1, a2 USING t1 AS a1 INNER JOIN t2 AS a2 WHERE a2.c1=a1.c2; --sorted_result SELECT * FROM t1,t2 WHERE t2.c1=t1.c2; ---error ER_UPDATE_TABLE_USED -DELETE FROM t1,t2 using t1,t2 where t1.c1=(select c1 from t1); +TRUNCATE TABLE t1; +TRUNCATE TABLE t2; +INSERT INTO t1 VALUES(254,127,1),(0,-128,2),(1,127,3),(3,NULL,5); +INSERT INTO t2 VALUES(127,255,1),(127,1,2),(-128,0,3),(-1,NULL,5); +# After the patch for MDEV-28883 this should not report +# --error ER_UPDATE_TABLE_USED anymore
Why is the above left as a comment? (the same in other cases) [skip]
diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index fa338f0..ac47566 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -30,6 +30,8 @@ #include "sql_base.h" #include "sql_const.h" #include "sql_select.h" +#include "sql_update.h" +#include "sql_delete.h"
it would be nice to have comment why you include it as it only for one function as I can see, something like: #include "sql_update.h" // processing_as_multitable_update_prohibited (the same for delete and for including in #include "sql_update.h" & "sql_base.cc") #include "filesort.h"
#include "opt_subselect.h" #include "sql_test.h" @@ -532,6 +534,48 @@ bool is_materialization_applicable(THD *thd, Item_in_subselect *in_subs, return FALSE; }
[skip]
diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 2b41b78..447573b 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -47,6 +47,8 @@ #include "sql_prepare.h" #include "sql_statistics.h" #include "sql_cte.h" +#include "sql_update.h" +#include "sql_delete.h"
see above
#include <m_ctype.h> #include <my_dir.h> #include <hash.h> @@ -1164,7 +1166,7 @@ TABLE_LIST* find_dup_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, /* If we found entry of this table or table of SELECT which already processed in derived table or top select of multi-update/multi-delete - (exclude_from_table_unique_test) or prelocking placeholder. + (exclude_from_table_unique_test) or prelocking placeholder.
Please fix formatting above
*/ DBUG_PRINT("info", ("found same copy of table or table which we should skip")); @@ -1175,16 +1177,43 @@ TABLE_LIST* find_dup_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, We come here for queries of type: INSERT INTO t1 (SELECT tmp.a FROM (select * FROM t1) as tmp);
- Try to fix by materializing the derived table + Try to fix by materializing the derived table if one can't do without it. */ TABLE_LIST *derived= res->belong_to_derived; if (derived->is_merged_derived() && !derived->derived->is_excluded()) { - DBUG_PRINT("info", + bool materialize= true; + if (thd->lex->sql_command == SQLCOM_UPDATE) + { + Sql_cmd_update *cmd= (Sql_cmd_update *) (thd->lex->m_sql_cmd); + if (cmd->is_multitable()) + materialize= false; + else if (!cmd->processing_as_multitable_update_prohibited(thd)) + { + cmd->set_as_multitable(); + materialize= false; + } + } + else if (thd->lex->sql_command == SQLCOM_DELETE) + { + Sql_cmd_delete *cmd= (Sql_cmd_delete *) (thd->lex->m_sql_cmd); + if (cmd->is_multitable()) + materialize= false; + if (!cmd->processing_as_multitable_delete_prohibited(thd)) + { + cmd->set_as_multitable(); + materialize= false; + } + } + if (materialize) + { + DBUG_PRINT("info", ("convert merged to materialization to resolve the conflict")); - derived->change_refs_to_fields(); - derived->set_materialized_derived(); - goto retry; + derived->change_refs_to_fields(); + derived->set_materialized_derived(); + // derived->field_translation= 0;
above comment has no big sens, looks not cleaned up some chenges?
+ goto retry; + } } } DBUG_RETURN(res);
[skip]
diff --git a/sql/sql_delete.h b/sql/sql_delete.h index e1d5044..ffb8173 100644 --- a/sql/sql_delete.h +++ b/sql/sql_delete.h @@ -44,11 +44,12 @@ class Sql_cmd_delete final : public Sql_cmd_dml { public: Sql_cmd_delete(bool multitable_arg) - : multitable(multitable_arg), save_protocol(NULL) {} + : orig_multitable(multitable_arg), save_protocol(NULL) + { multitable= orig_multitable; }
why do not use the same initializers? : multitable(multitable_arg), rig_multitable(multitable_arg), save_protocol(NULL) {}
enum_sql_command sql_command_code() const override { - return multitable ? SQLCOM_DELETE_MULTI : SQLCOM_DELETE; + return orig_multitable ? SQLCOM_DELETE_MULTI : SQLCOM_DELETE; }
DML_prelocking_strategy *get_dml_prelocking_strategy()
[skip]
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 78c067a..4059c0e 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -37,6 +37,8 @@ #include "sql_partition.h" #include "sql_partition_admin.h" // Sql_cmd_alter_table_*_part #include "event_parse_data.h" +#include "sql_update.h" +#include "sql_delete.h"
see above
void LEX::parse_error(uint err_number) { @@ -4037,9 +4039,8 @@ bool LEX::can_use_merged() SYNOPSIS LEX::can_not_use_merged()
- @param no_update_or_delete Set to 1 if we can't use merge with multiple-table - updates, like when used from - TALE_LIST::init_derived() + @param forced_no_merge_for_update_delete Set to 1 if we can't use merge with + multiple-table updates/deletes
DESCRIPTION Temporary table algorithm will be used on all SELECT levels for queries @@ -4050,7 +4051,7 @@ bool LEX::can_use_merged() TRUE - VIEWs with MERGE algorithms can be used */
-bool LEX::can_not_use_merged(bool no_update_or_delete) +bool LEX::can_not_use_merged(bool forced_no_merge_for_update_delete) { switch (sql_command) { case SQLCOM_CREATE_VIEW: @@ -4064,18 +4065,29 @@ bool LEX::can_not_use_merged(bool no_update_or_delete) return TRUE;
case SQLCOM_UPDATE_MULTI: - case SQLCOM_DELETE_MULTI: - if (no_update_or_delete) + if (forced_no_merge_for_update_delete) return TRUE; /* Fall through */
case SQLCOM_UPDATE: - if (no_update_or_delete && m_sql_cmd && - (m_sql_cmd->sql_command_code() == SQLCOM_UPDATE_MULTI || + if (forced_no_merge_for_update_delete && + (((Sql_cmd_update *) m_sql_cmd)->is_multitable() || query_tables->is_multitable())) return TRUE; + return FALSE; + + case SQLCOM_DELETE_MULTI: + if (forced_no_merge_for_update_delete) + return TRUE; /* Fall through */
+ case SQLCOM_DELETE: + if (forced_no_merge_for_update_delete && + (((Sql_cmd_delete *) m_sql_cmd)->is_multitable() || + query_tables->is_multitable())) + return TRUE; + return FALSE; + default: return FALSE; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index e733b4f..402e3bd 100644
[skip]
diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 6b14c4f..7769777 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -2807,6 +2807,23 @@ bool multi_update::send_eof()
/** + @brief Check whether conversion to multi-table update is prohibited + + @param thd global context the processed statement + @returns true if conversion is prohibited, false otherwise + + @todo + Introduce handler level flag for storage engines that would prohibit + such conversion for any single-table update. +*/ + +bool Sql_cmd_update::processing_as_multitable_update_prohibited(THD *thd) +{ + return false; +} + + +/** @brief Perform precheck of table privileges for update statements
@param thd global context the processed statement @@ -2889,7 +2906,9 @@ bool Sql_cmd_update::prepare_inner(THD *thd) "updating and querying the same temporal periods table"); DBUG_RETURN(TRUE); } - multitable= true; + if (!table_list->is_multitable() && + !processing_as_multitable_update_prohibited(thd)) + multitable= true; } }
diff --git a/sql/sql_update.h b/sql/sql_update.h index d0fc7cb..4aff77a 100644 --- a/sql/sql_update.h +++ b/sql/sql_update.h @@ -46,12 +46,12 @@ class Sql_cmd_update final : public Sql_cmd_dml { public: Sql_cmd_update(bool multitable_arg) - : multitable(multitable_arg) - { } + : orig_multitable(multitable_arg) + { multitable= orig_multitable; }
why do not use construction for both? Sql_cmd_update(bool multitable_arg) : multitable(multitable_arg) : orig_multitable(multitable_arg) {}
enum_sql_command sql_command_code() const override { - return multitable ? SQLCOM_UPDATE_MULTI : SQLCOM_UPDATE; + return orig_multitable ? SQLCOM_UPDATE_MULTI : SQLCOM_UPDATE; }
DML_prelocking_strategy *get_dml_prelocking_strategy() @@ -59,6 +59,12 @@ class Sql_cmd_update final : public Sql_cmd_dml return &multiupdate_prelocking_strategy; }
+ bool processing_as_multitable_update_prohibited(THD *thd); + + bool is_multitable() { return multitable; } + + void set_as_multitable() { multitable= true; } + protected: /** @brief Perform precheck of table privileges for update statements @@ -89,13 +95,15 @@ class Sql_cmd_update final : public Sql_cmd_dml */ bool multitable;
+ /* Original value of the 'multitable' flag set by constructor */ + const bool orig_multitable; + /* The prelocking strategy used when opening the used tables */ Multiupdate_prelocking_strategy multiupdate_prelocking_strategy;
public: /* The list of the updating expressions used in the set clause */ List<Item> *update_value_list; - };
#endif /* SQL_UPDATE_INCLUDED */ diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index a587a37..13dc602 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -13370,8 +13370,21 @@ delete_single_table: YYPS->m_lock_type, YYPS->m_mdl_type, NULL, + 0))) + MYSQL_YYABORT; + Select->table_list.save_and_clear(&Lex->auxiliary_table_list); + Lex->table_count= 1;
Why it is not enough to have this counter (table_count) set by open_tables? May be it would be better to use mysql_init_select() to set everything needed by SELECT for sure?
+ 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))) MYSQL_YYABORT; + Lex->auxiliary_table_list.first->correspondent_table= + Lex->query_tables; YYPS->m_lock_type= TL_READ_DEFAULT; YYPS->m_mdl_type= MDL_SHARED_READ; } [skip]