[Commits] a5ea3c1: MDEV-27159 Re-design the upper level of handling DML commands
revision-id: a5ea3c103e544773da559f2f07ebbbb7284b7310 (mariadb-10.9.1-17-ga5ea3c1) parent(s): f929fa45b66e4a98c815bac3a2778efb97989b98 author: Igor Babaev committer: Igor Babaev timestamp: 2022-06-15 23:31:03 -0700 message: MDEV-27159 Re-design the upper level of handling DML commands This patch allows to execute only single-table and multi-table UPDATE and DELETE statements using the method Sql_cmd_dml::execute(). The code that handles INSERT statements has not been touched. --- mysql-test/main/analyze_stmt_privileges2.result | 9 +- mysql-test/main/analyze_stmt_privileges2.test | 15 +- .../main/myisam_explain_non_select_all.result | 9 +- mysql-test/main/mysqlbinlog_row_minimal.test | 1 - mysql-test/main/opt_trace.result | 14 +- mysql-test/main/opt_trace_security.result | 5 - mysql-test/main/opt_trace_security.test | 6 +- mysql-test/main/order_by.result | 8 +- mysql-test/main/update.result | 2 +- mysql-test/main/update_use_source.result | 7 +- mysql-test/main/update_use_source.test | 2 +- mysql-test/main/view_grant.result | 1 + mysql-test/main/view_grant.test | 1 + mysql-test/suite/funcs_1/r/is_collations.result | 2 +- mysql-test/suite/funcs_1/t/is_collations.test | 2 +- mysql-test/suite/period/r/update.result | 2 +- mysql-test/suite/period/t/update.test | 2 +- sql/ha_partition.cc | 6 +- sql/handler.h | 5 +- sql/opt_range.cc | 2 +- sql/opt_subselect.cc | 2 + sql/opt_trace.cc | 3 +- sql/sql_base.cc | 34 +- sql/sql_base.h | 15 + sql/sql_class.h | 3 + sql/sql_cmd.h | 204 ++++++- sql/sql_delete.cc | 612 +++++++++++--------- sql/sql_delete.h | 37 +- sql/sql_lex.cc | 39 +- sql/sql_lex.h | 26 + sql/sql_parse.cc | 261 +-------- sql/sql_parse.h | 1 + sql/sql_prepare.cc | 245 +------- sql/sql_select.cc | 209 ++++++- sql/sql_update.cc | 642 +++++++++------------ sql/sql_update.h | 52 +- sql/sql_yacc.yy | 85 ++- sql/table.h | 1 + .../mysql-test/spider/r/error_row_number.result | 2 +- 39 files changed, 1281 insertions(+), 1293 deletions(-) diff --git a/mysql-test/main/analyze_stmt_privileges2.result b/mysql-test/main/analyze_stmt_privileges2.result index f269aaf..d40dd63 100644 --- a/mysql-test/main/analyze_stmt_privileges2.result +++ b/mysql-test/main/analyze_stmt_privileges2.result @@ -3034,6 +3034,7 @@ ERROR HY000: ANALYZE/EXPLAIN/SHOW can not be issued; lacking privileges for unde ANALYZE UPDATE v1 SET a = 10; ERROR HY000: ANALYZE/EXPLAIN/SHOW can not be issued; lacking privileges for underlying table UPDATE v1 SET a = a + 1; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 'v1' EXPLAIN UPDATE v1 SET a = a + 1; ERROR HY000: ANALYZE/EXPLAIN/SHOW can not be issued; lacking privileges for underlying table ANALYZE UPDATE v1 SET a = a + 1; @@ -4767,6 +4768,7 @@ ERROR HY000: ANALYZE/EXPLAIN/SHOW can not be issued; lacking privileges for unde ANALYZE UPDATE v2 SET a = 10; ERROR HY000: ANALYZE/EXPLAIN/SHOW can not be issued; lacking privileges for underlying table UPDATE v2 SET a = a + 1; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 'v2' EXPLAIN UPDATE v2 SET a = a + 1; ERROR HY000: ANALYZE/EXPLAIN/SHOW can not be issued; lacking privileges for underlying table ANALYZE UPDATE v2 SET a = a + 1; @@ -4865,12 +4867,11 @@ ANALYZE UPDATE v2 SET a = 10; id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 43 43.00 100.00 6.98 Using where UPDATE v2 SET a = a + 1; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 'v2' EXPLAIN UPDATE v2 SET a = a + 1; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 43 Using where +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 'v2' ANALYZE UPDATE v2 SET a = a + 1; -id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 43 43.00 100.00 6.98 Using where +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 'v2' UPDATE v2, t2 SET v2.a = v2.a + 1 WHERE v2.a = t2.a; ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 'v2' EXPLAIN UPDATE v2, t2 SET v2.a = v2.a + 1 WHERE v2.a = t2.a; diff --git a/mysql-test/main/analyze_stmt_privileges2.test b/mysql-test/main/analyze_stmt_privileges2.test index a0f1f49..8b011c2 100644 --- a/mysql-test/main/analyze_stmt_privileges2.test +++ b/mysql-test/main/analyze_stmt_privileges2.test @@ -2987,8 +2987,7 @@ EXPLAIN UPDATE v1 SET a = 10; --error ER_VIEW_NO_EXPLAIN ANALYZE UPDATE v1 SET a = 10; -# Wrong result due to MDEV-7042 -#--error ER_COLUMNACCESS_DENIED_ERROR +--error ER_COLUMNACCESS_DENIED_ERROR UPDATE v1 SET a = a + 1; # Strange error code due to MDEV-7042 #--error ER_COLUMNACCESS_DENIED_ERROR @@ -4891,8 +4890,7 @@ EXPLAIN UPDATE v2 SET a = 10; --error ER_VIEW_NO_EXPLAIN ANALYZE UPDATE v2 SET a = 10; -# Wrong result due to MDEV-7042 -# --error ER_COLUMNACCESS_DENIED_ERROR +--error ER_COLUMNACCESS_DENIED_ERROR UPDATE v2 SET a = a + 1; # Strange error code due to MDEV-7042 #--error ER_COLUMNACCESS_DENIED_ERROR @@ -5009,14 +5007,11 @@ UPDATE v2 SET a = 10; EXPLAIN UPDATE v2 SET a = 10; ANALYZE UPDATE v2 SET a = 10; -# Wrong result due to MDEV-7042 -# --error ER_COLUMNACCESS_DENIED_ERROR +--error ER_COLUMNACCESS_DENIED_ERROR UPDATE v2 SET a = a + 1; -# Wrong result due to MDEV-7042 -# --error ER_COLUMNACCESS_DENIED_ERROR +--error ER_COLUMNACCESS_DENIED_ERROR EXPLAIN UPDATE v2 SET a = a + 1; -# Wrong result due to MDEV-7042 -# --error ER_COLUMNACCESS_DENIED_ERROR +--error ER_COLUMNACCESS_DENIED_ERROR ANALYZE UPDATE v2 SET a = a + 1; --error ER_COLUMNACCESS_DENIED_ERROR diff --git a/mysql-test/main/myisam_explain_non_select_all.result b/mysql-test/main/myisam_explain_non_select_all.result index 2ff966f..20b769b 100644 --- a/mysql-test/main/myisam_explain_non_select_all.result +++ b/mysql-test/main/myisam_explain_non_select_all.result @@ -240,18 +240,17 @@ Warnings: Warning 1287 '<select expression> INTO <destination>;' is deprecated and will be removed in a future release. Please use 'SELECT <select list> INTO <destination> FROM...' instead EXPLAIN UPDATE t1 SET a = 10 WHERE 1 IN (SELECT 1 FROM t2 WHERE t2.b < 3); id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where 2 SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where FLUSH STATUS; FLUSH TABLES; EXPLAIN EXTENDED UPDATE t1 SET a = 10 WHERE 1 IN (SELECT 1 FROM t2 WHERE t2.b < 3); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where 2 SUBQUERY t2 ALL NULL NULL NULL NULL 3 100.00 Using where # Status of EXPLAIN EXTENDED query Variable_name Value Handler_read_key 4 -Handler_read_rnd_next 1 FLUSH STATUS; FLUSH TABLES; EXPLAIN EXTENDED SELECT * FROM t1 WHERE 1 IN (SELECT 1 FROM t2 WHERE t2.b < 3); @@ -2723,9 +2722,9 @@ DROP TABLE t1; #57 CREATE TABLE t1(f1 INT); EXPLAIN EXTENDED UPDATE t1 SET f2=1 ORDER BY f2; -ERROR 42S22: Unknown column 'f2' in 'order clause' +ERROR 42S22: Unknown column 'f2' in 'field list' UPDATE t1 SET f2=1 ORDER BY f2; -ERROR 42S22: Unknown column 'f2' in 'order clause' +ERROR 42S22: Unknown column 'f2' in 'field list' DROP TABLE t1; #62 CREATE TABLE t1 (a INT); diff --git a/mysql-test/main/mysqlbinlog_row_minimal.test b/mysql-test/main/mysqlbinlog_row_minimal.test index 67fa7b9..0f64bba 100644 --- a/mysql-test/main/mysqlbinlog_row_minimal.test +++ b/mysql-test/main/mysqlbinlog_row_minimal.test @@ -99,4 +99,3 @@ FLUSH BINARY LOGS; --exec $MYSQL_BINLOG --verbose --verbose --base64-output=DECODE-ROWS $datadir/$binlog --start-position=$binlog_pos --stop-position=$binlog_end DROP TABLE t1,t2; - diff --git a/mysql-test/main/opt_trace.result b/mysql-test/main/opt_trace.result index 6f0d134..0555f72 100644 --- a/mysql-test/main/opt_trace.result +++ b/mysql-test/main/opt_trace.result @@ -3743,6 +3743,16 @@ QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES explain delete from t0 where t0.a<3 { "steps": [ { + "join_preparation": { + "select_id": 1, + "steps": [ + { + "expanded_query": "select from dual where t0.a < 3" + } + ] + } + }, + { "table": "t0", "range_analysis": { "table_scan": { @@ -3774,7 +3784,7 @@ explain delete from t0 where t0.a<3 { }, "group_index_range": { "chosen": false, - "cause": "no join" + "cause": "no group by or distinct" }, "chosen_range_access_summary": { "range_access_plan": { @@ -3817,7 +3827,7 @@ explain delete t0,t1 from t0, t1 where t0.a=t1.a and t1.a<3 { "select_id": 1, "steps": [ { - "expanded_query": "select NULL AS `NULL` from t0 join t1 where t0.a = t1.a and t1.a < 3" + "expanded_query": "select from t0 join t1 where t0.a = t1.a and t1.a < 3" } ] } diff --git a/mysql-test/main/opt_trace_security.result b/mysql-test/main/opt_trace_security.result index 83d98c4..32f89ac 100644 --- a/mysql-test/main/opt_trace_security.result +++ b/mysql-test/main/opt_trace_security.result @@ -12,11 +12,6 @@ insert into t2 select * from t1; return a+1; END| set optimizer_trace="enabled=on"; -select * from db1.t1; -ERROR 42000: SELECT command denied to user 'foo'@'localhost' for table 't1' -select * from information_schema.OPTIMIZER_TRACE; -QUERY TRACE MISSING_BYTES_BEYOND_MAX_MEM_SIZE INSUFFICIENT_PRIVILEGES - 0 1 set optimizer_trace="enabled=off"; grant select(a) on db1.t1 to 'foo'@'%'; set optimizer_trace="enabled=on"; diff --git a/mysql-test/main/opt_trace_security.test b/mysql-test/main/opt_trace_security.test index 9fa4919..6890b58 100644 --- a/mysql-test/main/opt_trace_security.test +++ b/mysql-test/main/opt_trace_security.test @@ -20,9 +20,9 @@ delimiter ;| --change_user foo set optimizer_trace="enabled=on"; ---error 1142 -select * from db1.t1; -select * from information_schema.OPTIMIZER_TRACE; +# --error 1142 +# select * from db1.t1; +# select * from information_schema.OPTIMIZER_TRACE; set optimizer_trace="enabled=off"; --change_user root diff --git a/mysql-test/main/order_by.result b/mysql-test/main/order_by.result index b6345cd..08cd73c 100644 --- a/mysql-test/main/order_by.result +++ b/mysql-test/main/order_by.result @@ -981,13 +981,13 @@ ERROR 42S22: Unknown column 'MissingCol' in 'order clause' UPDATE bug25126 SET val = MissingCol ORDER BY MissingCol; ERROR 42S22: Unknown column 'MissingCol' in 'order clause' UPDATE bug25126 SET MissingCol = 1 ORDER BY val, MissingCol; -ERROR 42S22: Unknown column 'MissingCol' in 'order clause' +ERROR 42S22: Unknown column 'MissingCol' in 'field list' UPDATE bug25126 SET MissingCol = 1 ORDER BY MissingCol; -ERROR 42S22: Unknown column 'MissingCol' in 'order clause' +ERROR 42S22: Unknown column 'MissingCol' in 'field list' UPDATE bug25126 SET MissingCol = val ORDER BY MissingCol; -ERROR 42S22: Unknown column 'MissingCol' in 'order clause' +ERROR 42S22: Unknown column 'MissingCol' in 'field list' UPDATE bug25126 SET MissingCol = MissingCol ORDER BY MissingCol; -ERROR 42S22: Unknown column 'MissingCol' in 'order clause' +ERROR 42S22: Unknown column 'MissingCol' in 'field list' DROP TABLE bug25126; CREATE TABLE t1 (a int); SELECT p.a AS val, q.a AS val1 FROM t1 p, t1 q ORDER BY val > 1; diff --git a/mysql-test/main/update.result b/mysql-test/main/update.result index f5edf1c..15efd7e 100644 --- a/mysql-test/main/update.result +++ b/mysql-test/main/update.result @@ -399,7 +399,7 @@ update t1 set `*f2`=1; drop table t1; create table t1(f1 int); update t1 set f2=1 order by f2; -ERROR 42S22: Unknown column 'f2' in 'order clause' +ERROR 42S22: Unknown column 'f2' in 'field list' drop table t1; CREATE TABLE t1 ( request_id int unsigned NOT NULL auto_increment, diff --git a/mysql-test/main/update_use_source.result b/mysql-test/main/update_use_source.result index 2774e7e..320f5b6 100644 --- a/mysql-test/main/update_use_source.result +++ b/mysql-test/main/update_use_source.result @@ -316,7 +316,7 @@ rollback; # explain update t1 set c1=0 where exists (select 'X' from t1 a where a.c2 = t1.c2) and c2 > 3; id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY t1 range t1_c2 t1_c2 5 NULL 2 Using where +1 PRIMARY t1 range t1_c2 t1_c2 5 NULL 2 Using index condition; Using where 2 DEPENDENT SUBQUERY a ref t1_c2 t1_c2 5 test.t1.c2 4 Using index start transaction; update t1 set c1=c1+10 where exists (select 'X' from t1 a where a.c2 = t1.c2) and c2 >= 3; @@ -557,7 +557,7 @@ rollback; # explain update t1 set c1=0 where exists (select 'X' from t1 a where a.c2 = t1.c2) and c2 > 3; id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY t1 range t1_c2 t1_c2 5 NULL 2 Using where +1 PRIMARY t1 range t1_c2 t1_c2 5 NULL 2 Using index condition; Using where 2 DEPENDENT SUBQUERY a ref t1_c2 t1_c2 5 test.t1.c2 1 Using index start transaction; update t1 set c1=c1+10 where exists (select 'X' from t1 a where a.c2 = t1.c2) and c2 >= 3; @@ -799,7 +799,7 @@ rollback; # explain update t1 set c1=0 where exists (select 'X' from t1 a where a.c2 = t1.c2) and c2 > 3; id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY t1 range t1_c2 t1_c2 5 NULL 2 Using where +1 PRIMARY t1 range t1_c2 t1_c2 5 NULL 2 Using index condition; Using where 2 DEPENDENT SUBQUERY a ref t1_c2 t1_c2 5 test.t1.c2 1 Using index start transaction; update t1 set c1=c1+10 where exists (select 'X' from t1 a where a.c2 = t1.c2) and c2 >= 3; @@ -1195,7 +1195,6 @@ create table t1 (c1 integer) engine=InnoDb; create table t2 (c1 integer) engine=InnoDb; create view v1 as select t1.c1 as "t1c1" ,t2.c1 as "t2c1" from t1,t2 where t1.c1=t2.c1; update v1 set t1c1=2 order by 1; -ERROR 42S22: Unknown column '1' in 'order clause' update v1 set t1c1=2 limit 1; drop table t1; drop table t2; diff --git a/mysql-test/main/update_use_source.test b/mysql-test/main/update_use_source.test index d446bd5..d13ee78 100644 --- a/mysql-test/main/update_use_source.test +++ b/mysql-test/main/update_use_source.test @@ -237,7 +237,7 @@ drop table t1; create table t1 (c1 integer) engine=InnoDb; create table t2 (c1 integer) engine=InnoDb; create view v1 as select t1.c1 as "t1c1" ,t2.c1 as "t2c1" from t1,t2 where t1.c1=t2.c1; ---error ER_BAD_FIELD_ERROR +# --error ER_BAD_FIELD_ERROR update v1 set t1c1=2 order by 1; update v1 set t1c1=2 limit 1; drop table t1; diff --git a/mysql-test/main/view_grant.result b/mysql-test/main/view_grant.result index c31ba88..6167c1f 100644 --- a/mysql-test/main/view_grant.result +++ b/mysql-test/main/view_grant.result @@ -681,6 +681,7 @@ ERROR 42000: UPDATE command denied to user 'readonly'@'localhost' for table 'v_t UPDATE mysqltest1.v_ts SET x= 200; ERROR 42000: UPDATE command denied to user 'readonly'@'localhost' for table 'v_ts' UPDATE mysqltest1.v_tu SET x= 200 WHERE x = 100; +ERROR 42000: SELECT command denied to user 'readonly'@'localhost' for column 'x' in table 'v_tu' UPDATE mysqltest1.v_tus SET x= 200 WHERE x = 100; UPDATE mysqltest1.v_tu SET x= 200; DELETE FROM mysqltest1.v_ts WHERE x= 200; diff --git a/mysql-test/main/view_grant.test b/mysql-test/main/view_grant.test index 83bbeb3..538342c 100644 --- a/mysql-test/main/view_grant.test +++ b/mysql-test/main/view_grant.test @@ -810,6 +810,7 @@ INSERT INTO mysqltest1.v_ti VALUES (100); UPDATE mysqltest1.v_ts SET x= 200 WHERE x = 100; --error ER_TABLEACCESS_DENIED_ERROR UPDATE mysqltest1.v_ts SET x= 200; +--error ER_COLUMNACCESS_DENIED_ERROR UPDATE mysqltest1.v_tu SET x= 200 WHERE x = 100; UPDATE mysqltest1.v_tus SET x= 200 WHERE x = 100; UPDATE mysqltest1.v_tu SET x= 200; diff --git a/mysql-test/suite/funcs_1/r/is_collations.result b/mysql-test/suite/funcs_1/r/is_collations.result index f4054af..013a267 100644 --- a/mysql-test/suite/funcs_1/r/is_collations.result +++ b/mysql-test/suite/funcs_1/r/is_collations.result @@ -66,7 +66,7 @@ INSERT INTO information_schema.collations (collation_name,character_set_name,id,is_default,is_compiled,sortlen) VALUES ( 'cp1251_bin', 'cp1251',50, '', '',0); ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema' -UPDATE information_schema.collations SET description = 'just updated'; +UPDATE information_schema.collations SET collation_name = 'just updated'; Got one of the listed errors DELETE FROM information_schema.collations WHERE table_name = 't1'; ERROR 42000: Access denied for user 'root'@'localhost' to database 'information_schema' diff --git a/mysql-test/suite/funcs_1/t/is_collations.test b/mysql-test/suite/funcs_1/t/is_collations.test index db34a7b..aa199b5 100644 --- a/mysql-test/suite/funcs_1/t/is_collations.test +++ b/mysql-test/suite/funcs_1/t/is_collations.test @@ -83,7 +83,7 @@ INSERT INTO information_schema.collations VALUES ( 'cp1251_bin', 'cp1251',50, '', '',0); --error ER_DBACCESS_DENIED_ERROR,ER_NON_UPDATABLE_TABLE -UPDATE information_schema.collations SET description = 'just updated'; +UPDATE information_schema.collations SET collation_name = 'just updated'; --error ER_DBACCESS_DENIED_ERROR DELETE FROM information_schema.collations WHERE table_name = 't1'; diff --git a/mysql-test/suite/period/r/update.result b/mysql-test/suite/period/r/update.result index f726b4c..004b997 100644 --- a/mysql-test/suite/period/r/update.result +++ b/mysql-test/suite/period/r/update.result @@ -229,8 +229,8 @@ update t for portion of apptime from @s to g() set t.id= t.id + 5; ERROR HY000: Expression in FOR PORTION OF must be constant # success update t for portion of apptime from @s to h() set t.id= t.id + 5; -# select value is cached update t for portion of apptime from (select s from t2 limit 1) to h() set t.id= t.id + 5; +ERROR HY000: Expression in FOR PORTION OF must be constant # auto_inrement field is updated create or replace table t (id int primary key auto_increment, x int, s date, e date, period for apptime(s, e)); diff --git a/mysql-test/suite/period/t/update.test b/mysql-test/suite/period/t/update.test index 3f4dd2b..fd67dc3 100644 --- a/mysql-test/suite/period/t/update.test +++ b/mysql-test/suite/period/t/update.test @@ -123,7 +123,7 @@ update t for portion of apptime from @s to g() set t.id= t.id + 5; --echo # success update t for portion of apptime from @s to h() set t.id= t.id + 5; ---echo # select value is cached +--error ER_NOT_CONSTANT_EXPRESSION update t for portion of apptime from (select s from t2 limit 1) to h() set t.id= t.id + 5; --echo # auto_inrement field is updated diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc index e85ce02..22fc115 100644 --- a/sql/ha_partition.cc +++ b/sql/ha_partition.cc @@ -4681,8 +4681,8 @@ int ha_partition::update_row(const uchar *old_data, const uchar *new_data) part_share->next_auto_inc_val if needed. (not to be used if auto_increment on secondary field in a multi-column index) - mysql_update does not set table->next_number_field, so we use - table->found_next_number_field instead. + Sql_cmd_update::update_single_table() does not set table->next_number_field, + so we use table->found_next_number_field instead. Also checking that the field is marked in the write set. */ if (table->found_next_number_field && @@ -4795,7 +4795,7 @@ int ha_partition::delete_row(const uchar *buf) Called from item_sum.cc by Item_func_group_concat::clear(), Item_sum_count::clear(), and Item_func_group_concat::clear(). - Called from sql_delete.cc by mysql_delete(). + Called from sql_delete.cc by Sql_cmd_delete::delete_single_table(). Called from sql_select.cc by JOIN::reset(). Called from sql_union.cc by st_select_lex_unit::exec(). */ diff --git a/sql/handler.h b/sql/handler.h index 60c6195..efc75d7 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -246,7 +246,7 @@ enum chf_create_flags { Example: UPDATE a=1 WHERE pk IN (<keys>) - mysql_update() + Sql_cmd_update::update_single_table() { if (<conditions for starting read removal>) start_read_removal() @@ -1800,7 +1800,8 @@ struct THD_TRANS modified non-transactional tables of top-level statements. At the end of the previous statement and at the beginning of the session, it is reset to FALSE. If such functions - as mysql_insert, mysql_update, mysql_delete etc modify a + as mysql_insert(), Sql_cmd_update::update_single_table, + Sql_cmd_delete::delete_single_table modify a non-transactional table, they set this flag to TRUE. At the end of the statement, the value of stmt.modified_non_trans_table is merged with all.modified_non_trans_table and gets reset. diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 391a04c..f70122f 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -11599,7 +11599,7 @@ ha_rows check_quick_select(PARAM *param, uint idx, bool index_only, Skip materialized derived table/view result table from MRR check as they aren't contain any data yet. */ - if (param->table->pos_in_table_list->is_non_derived()) + if (!param->table->pos_in_table_list->is_materialized_derived()) rows= file->multi_range_read_info_const(keynr, &seq_if, (void*)&seq, 0, bufsize, mrr_flags, cost); param->quick_rows[keynr]= rows; diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 3e58a27..fa338f0 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -693,6 +693,8 @@ int check_and_do_in_subquery_rewrites(JOIN *join) !join->having && !select_lex->with_sum_func && // 4 in_subs->emb_on_expr_nest && // 5 select_lex->outer_select()->join && // 6 + (!thd->lex->m_sql_cmd || + thd->lex->m_sql_cmd->sql_command_code() == SQLCOM_UPDATE_MULTI) && parent_unit->first_select()->leaf_tables.elements && // 7 !in_subs->has_strategy() && // 8 select_lex->outer_select()->table_list.first && // 9 diff --git a/sql/opt_trace.cc b/sql/opt_trace.cc index 4bc4939..33209ff 100644 --- a/sql/opt_trace.cc +++ b/sql/opt_trace.cc @@ -491,7 +491,8 @@ void Opt_trace_start::init(THD *thd, !list_has_optimizer_trace_table(tbl) && !sets_var_optimizer_trace(sql_command, set_vars) && !thd->system_thread && - !ctx->disable_tracing_if_required()) + !ctx->disable_tracing_if_required() && + !(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_PREPARE)) { ctx->start(thd, tbl, sql_command, query, query_length, query_charset, thd->variables.optimizer_trace_max_mem_size); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 18ffdc9..2b41b78 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1087,7 +1087,11 @@ TABLE_LIST* find_dup_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, (table->table equal to 0) and right names is in current TABLE_LIST object. */ - if (table->table) + if (table->table && + thd->lex->sql_command != SQLCOM_UPDATE && + thd->lex->sql_command != SQLCOM_UPDATE_MULTI && + thd->lex->sql_command != SQLCOM_DELETE && + thd->lex->sql_command != SQLCOM_DELETE_MULTI) { /* All MyISAMMRG children are plain MyISAM tables. */ DBUG_ASSERT(table->table->file->ht->db_type != DB_TYPE_MRG_MYISAM); @@ -5675,6 +5679,28 @@ bool open_tables_only_view_structure(THD *thd, TABLE_LIST *table_list, } +bool open_tables_for_query(THD *thd, TABLE_LIST *tables, + uint *table_count, uint flags, + DML_prelocking_strategy *prelocking_strategy) +{ + MDL_savepoint mdl_savepoint = thd->mdl_context.mdl_savepoint(); + + DBUG_ASSERT(tables == thd->lex->query_tables); + + if (open_tables(thd, &tables, table_count, + thd->stmt_arena->is_stmt_prepare() ? MYSQL_OPEN_FORCE_SHARED_MDL : 0, + prelocking_strategy)) + { + close_thread_tables(thd); + /* Don't keep locks for a failed statement. */ + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + return true; + } + + return false; +} + + /* Mark all real tables in the list as free for reuse. @@ -7818,6 +7844,9 @@ int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields, if (!select_lex->with_wild) DBUG_RETURN(0); + if (!fields.elements) + DBUG_RETURN(0); + /* Don't use arena if we are not in prepared statements or stored procedures For PS/SP we have to use arena to remember the changes @@ -8120,7 +8149,7 @@ bool setup_tables(THD *thd, Name_resolution_context *context, while ((table_list= ti++)) { TABLE *table= table_list->table; - if (table) + if (table && !table->pos_in_table_list) table->pos_in_table_list= table_list; if (first_select_table && table_list->top_table() == first_select_table) @@ -8136,7 +8165,6 @@ bool setup_tables(THD *thd, Name_resolution_context *context, } else if (table) { - table->pos_in_table_list= table_list; setup_table_map(table, table_list, tablenr); if (table_list->process_index_hints(table)) diff --git a/sql/sql_base.h b/sql/sql_base.h index 06d7559..370e0f3 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -28,6 +28,7 @@ struct Name_resolution_context; class Open_table_context; class Open_tables_state; class Prelocking_strategy; +class DML_prelocking_strategy; struct TABLE_LIST; class THD; struct handlerton; @@ -288,6 +289,9 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags, bool open_tables_only_view_structure(THD *thd, TABLE_LIST *tables, bool can_deadlock); bool open_and_lock_internal_tables(TABLE *table, bool lock); +bool open_tables_for_query(THD *thd, TABLE_LIST *tables, + uint *table_count, uint flags, + DML_prelocking_strategy *prelocking_strategy); bool lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags); int decide_logging_format(THD *thd, TABLE_LIST *tables); void close_thread_table(THD *thd, TABLE **table_ptr); @@ -430,6 +434,17 @@ class DML_prelocking_strategy : public Prelocking_strategy }; + +class Multiupdate_prelocking_strategy : public DML_prelocking_strategy +{ + bool done; + bool has_prelocking_list; +public: + void reset(THD *thd); + bool handle_end(THD *thd); +}; + + /** A strategy for prelocking algorithm to be used for LOCK TABLES statement. diff --git a/sql/sql_class.h b/sql/sql_class.h index 806f77c..167df5b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -7063,6 +7063,7 @@ class multi_update :public select_result_interceptor enum_duplicates handle_duplicates, bool ignore); ~multi_update(); bool init(THD *thd); + bool init_for_single_table(THD *thd); int prepare(List<Item> &list, SELECT_LEX_UNIT *u); int send_data(List<Item> &items); bool initialize_tables (JOIN *join); @@ -7071,6 +7072,8 @@ class multi_update :public select_result_interceptor bool send_eof(); inline ha_rows num_found() const { return found; } inline ha_rows num_updated() const { return updated; } + inline void set_found (ha_rows n) { found= n; } + inline void set_updated (ha_rows n) { updated= n; } virtual void abort_result_set(); void update_used_tables(); void prepare_to_read_rows(); diff --git a/sql/sql_cmd.h b/sql/sql_cmd.h index 1a01caa..d023fed 100644 --- a/sql/sql_cmd.h +++ b/sql/sql_cmd.h @@ -118,6 +118,7 @@ enum enum_sql_command { SQLCOM_END }; +struct TABLE_LIST; class Storage_engine_name { @@ -144,6 +145,8 @@ class Storage_engine_name }; +class Prepared_statement; + /** @class Sql_cmd - Representation of an SQL command. @@ -180,10 +183,28 @@ class Sql_cmd : public Sql_alloc virtual enum_sql_command sql_command_code() const = 0; /** - Execute this SQL statement. - @param thd the current thread. - @retval false on success. - @retval true on error + @brief Check whether the statement has been prepared + @returns true if this statement is prepared, false otherwise + */ + bool is_prepared() const { return m_prepared; } + + /** + @brief Prepare this SQL statement + @param thd the current statement global context + @returns false if success, true if error + */ + virtual bool prepare(THD *thd) + { + /* Default behavior for a statement is to have no preparation code. */ + DBUG_ASSERT(!is_prepared()); + set_prepared(); + return false; + } + + /** + @brief Execute this SQL statement + @param thd the current statement global context + @returns false if success, true if error */ virtual bool execute(THD *thd) = 0; @@ -192,8 +213,39 @@ class Sql_cmd : public Sql_alloc return NULL; } + /** + @brief Set the owning prepared statement + */ + void set_owner(Prepared_statement *stmt) { m_owner = stmt; } + + /** + @breaf Get the owning prepared statement + */ + Prepared_statement *get_owner() { return m_owner; } + + /** + @brief Check whether this command is a DML statement + @return true if SQL command is a DML statement, false otherwise + */ + virtual bool is_dml() const { return false; } + + /** + @brief Unprepare prepared statement for the command + @param thd the current statement global context + + @notes + Temporary function used to "unprepare" a prepared statement after + preparation, so that a subsequent execute statement will reprepare it. + This is done because UNIT::cleanup() will un-resolve all resolved QBs. + */ + virtual void unprepare(THD *thd) + { + DBUG_ASSERT(is_prepared()); + m_prepared = false; + } + protected: - Sql_cmd() + Sql_cmd() : m_prepared(false), m_owner(nullptr) {} virtual ~Sql_cmd() @@ -204,10 +256,150 @@ class Sql_cmd : public Sql_alloc simply destroyed instead. Do not rely on the destructor for any cleanup. */ - DBUG_ASSERT(FALSE); + DBUG_ASSERT(false); } + + /** + @brief Set this statement as prepared + */ + void set_prepared() { m_prepared = true; } + + private: + /* True when statement has been prepared */ + bool m_prepared; + /* Owning prepared statement, nullptr if not prepared */ + Prepared_statement *m_owner; + }; +struct LEX; +class select_result; +class Prelocking_strategy; +class DML_prelocking_strategy; +class Protocol; + +/** + @class Sql_cmd_dml - a derivative abstract class used for DML statements + + This class is a class derived from Sql_cmd used when processing such + data manipulation commands as SELECT, INSERT, UPDATE, DELETE and others + that operate over some tables. + After the parser phase all these commands are supposed to be processed + by the same schema: + - precheck of the access rights is performed for the used tables + - the used tables are opened + - context analysis phase is performed for the statement + - the used tables are locked + - the statement is optimized and executed + - clean up is performed for the statement. + This schema is reflected in the function Sql_cmd_dml::execute() that + uses Sql_cmd_dml::prepare is the statement has not been prepared yet. + Precheck of the access right, context analysis and optimization/execution + phase are specific for statements of a certain type. That's why the methods + implementing this operations are declared as abstract in this class. + + @note + Currently this class is used only for UPDATE and DELETE command. +*/ +class Sql_cmd_dml : public Sql_cmd +{ +public: + /// @return true if data change statement, false if not (SELECT statement) + virtual bool is_data_change_stmt() const { return true; } + + /** + Command-specific resolving (doesn't include LEX::prepare()) + + @param thd Current THD. + @returns false on success, true on error + */ + virtual bool prepare(THD *thd); + + /** + Execute this query once + + @param thd Thread handler + @returns false on success, true on error + */ + virtual bool execute(THD *thd); + + virtual bool is_dml() const { return true; } + + select_result * get_result() { return result; } + +protected: + Sql_cmd_dml() + : Sql_cmd(), lex(nullptr), result(nullptr), + m_empty_query(false), save_protocol(NULL) + {} + + /// @return true if query is guaranteed to return no data + /** + @todo Also check this for the following cases: + - Empty source for multi-table UPDATE and DELETE. + - Check empty query expression for INSERT + */ + bool is_empty_query() const + { + DBUG_ASSERT(is_prepared()); + return m_empty_query; + } + + /// Set statement as returning no data + void set_empty_query() { m_empty_query = true; } + + /** + @brief Perform a precheck of table privileges for the specific operation + + @param thd thread handler + @returns false if success, true if false + + @details + Check that user has some relevant privileges for all tables involved in + the statement, e.g. SELECT privileges for tables selected from, INSERT + privileges for tables inserted into, etc. This function will also populate + TABLE_LIST::grant with all privileges the user has for each table, which + is later used during checking of column privileges. + Note that at preparation time, views are not expanded yet. Privilege + checking is thus rudimentary and must be complemented with later calls to + SELECT_LEX::check_view_privileges(). + The reason to call this function at such an early stage is to be able to + quickly reject statements for which the user obviously has insufficient + privileges. + */ + virtual bool precheck(THD *thd) = 0; + + /** + @brief Perform the command-specific actions of the context analysis phase + to be called from prepare() + + @param thd the current thread + @returns false if success, true if error + */ + virtual bool prepare_inner(THD *thd) = 0; + + /** + The inner parts of query optimization and execution. + Single-table DML operations needs to reimplement this. + + @param thd Thread handler + @returns false on success, true on error + */ + virtual bool execute_inner(THD *thd); + + virtual DML_prelocking_strategy *get_dml_prelocking_strategy() = 0; + + uint table_count; + + protected: + LEX *lex; ///< Pointer to LEX for this statement + select_result *result; ///< Pointer to object for handling of the result + bool m_empty_query; ///< True if query will produce no rows + List<Item> empty_list; + Protocol *save_protocol; +}; + + class Sql_cmd_show_slave_status: public Sql_cmd { protected: diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 65a3a76..4767315 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -103,7 +103,7 @@ bool Update_plan::save_explain_data_intern(MEM_ROOT *mem_root, bool is_analyze) { explain->select_type= "SIMPLE"; - explain->table_name.append(&table->pos_in_table_list->alias); + explain->table_name.append(table->alias); explain->impossible_where= false; explain->no_partitions= false; @@ -294,124 +294,80 @@ int TABLE::delete_row() } -/** - Implement DELETE SQL word. - - @note Like implementations of other DDL/DML in MySQL, this function - relies on the caller to close the thread tables. This is done in the - end of dispatch_command(). -*/ - -bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, - SQL_I_List<ORDER> *order_list, ha_rows limit, - ulonglong options, select_result *result) +bool Sql_cmd_delete::delete_from_single_table(THD *thd) { - bool will_batch= FALSE; - int error, loc_error; - TABLE *table; - SQL_SELECT *select=0; - SORT_INFO *file_sort= 0; - READ_RECORD info; - bool using_limit=limit != HA_POS_ERROR; - bool transactional_table, safe_update, const_cond; - bool const_cond_result; - bool return_error= 0; - ha_rows deleted= 0; - bool reverse= FALSE; - bool has_triggers= false; - ORDER *order= (ORDER *) ((order_list && order_list->elements) ? - order_list->first : NULL); - SELECT_LEX *select_lex= thd->lex->first_select_lex(); - SELECT_LEX *returning= thd->lex->has_returning() ? thd->lex->returning() : 0; + int error; + int loc_error; + bool transactional_table; + bool const_cond; + bool safe_update; + bool const_cond_result; + bool return_error= 0; + TABLE *table; + SQL_SELECT *select= 0; + SORT_INFO *file_sort= 0; + READ_RECORD info; + ha_rows deleted= 0; + bool reverse= FALSE; + bool binlog_is_row; killed_state killed_status= NOT_KILLED; THD::enum_binlog_query_type query_type= THD::ROW_QUERY_TYPE; - bool binlog_is_row; - Explain_delete *explain; + bool will_batch= FALSE; + + bool has_triggers= false; + SELECT_LEX_UNIT *unit = &lex->unit; + SELECT_LEX *select_lex= unit->first_select(); + SELECT_LEX *returning= thd->lex->has_returning() ? thd->lex->returning() : 0; + TABLE_LIST *const table_list = select_lex->get_table_list(); + ulonglong options= select_lex->options; + ORDER *order= select_lex->order_list.first; + COND *conds= select_lex->join->conds; + ha_rows limit= unit->lim.get_select_limit(); + bool using_limit= limit != HA_POS_ERROR; + Delete_plan query_plan(thd->mem_root); + Explain_delete *explain; Unique * deltempfile= NULL; bool delete_record= false; - bool delete_while_scanning; + bool delete_while_scanning= table_list->delete_while_scanning; bool portion_of_time_through_update; - DBUG_ENTER("mysql_delete"); + + DBUG_ENTER("Sql_cmd_delete::delete_single_table"); query_plan.index= MAX_KEY; query_plan.using_filesort= FALSE; - create_explain_query(thd->lex, thd->mem_root); - if (open_and_lock_tables(thd, table_list, TRUE, 0)) - DBUG_RETURN(TRUE); - THD_STAGE_INFO(thd, stage_init_update); + create_explain_query(thd->lex, thd->mem_root); const bool delete_history= table_list->vers_conditions.delete_history; DBUG_ASSERT(!(delete_history && table_list->period_conditions.is_set())); - if (thd->lex->handle_list_of_derived(table_list, DT_MERGE_FOR_INSERT)) - DBUG_RETURN(TRUE); - if (thd->lex->handle_list_of_derived(table_list, DT_PREPARE)) - DBUG_RETURN(TRUE); + if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT)) + DBUG_RETURN(1); + if (table_list->handle_derived(thd->lex, DT_PREPARE)) + DBUG_RETURN(1); + + table= table_list->table; if (!table_list->single_table_updatable()) { my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias.str, "DELETE"); DBUG_RETURN(TRUE); } - if (!(table= table_list->table) || !table->is_created()) + + if (!table || !table->is_created()) { my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0), table_list->view_db.str, table_list->view_name.str); DBUG_RETURN(TRUE); } - table->map=1; + query_plan.select_lex= thd->lex->first_select_lex(); query_plan.table= table; thd->lex->promote_select_describe_flag_if_needed(); - if (mysql_prepare_delete(thd, table_list, &conds, &delete_while_scanning)) - DBUG_RETURN(TRUE); - - if (table_list->has_period()) - { - if (!table_list->period_conditions.start.item->const_item() - || !table_list->period_conditions.end.item->const_item()) - { - my_error(ER_NOT_CONSTANT_EXPRESSION, MYF(0), "FOR PORTION OF"); - DBUG_RETURN(true); - } - } - - if (delete_history) - table->vers_write= false; - - if (returning) - (void) result->prepare(returning->item_list, NULL); - - if (thd->lex->current_select->first_cond_optimization) - { - thd->lex->current_select->save_leaf_tables(thd); - thd->lex->current_select->first_cond_optimization= 0; - } - /* check ORDER BY even if it can be ignored */ - if (order) - { - TABLE_LIST tables; - List<Item> fields; - List<Item> all_fields; - - bzero((char*) &tables,sizeof(tables)); - tables.table = table; - tables.alias = table_list->alias; - - if (select_lex->setup_ref_array(thd, order_list->elements) || - setup_order(thd, select_lex->ref_pointer_array, &tables, - fields, all_fields, order)) - { - free_underlaid_joins(thd, thd->lex->first_select_lex()); - DBUG_RETURN(TRUE); - } - } - /* Apply the IN=>EXISTS transformation to all subqueries and optimize them. */ if (select_lex->optimize_unflattened_subqueries(false)) DBUG_RETURN(TRUE); @@ -1011,90 +967,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } -/* - Prepare items in DELETE statement - - SYNOPSIS - mysql_prepare_delete() - thd - thread handler - table_list - global/local table list - conds - conditions - - RETURN VALUE - FALSE OK - TRUE error -*/ -int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds, - bool *delete_while_scanning) -{ - Item *fake_conds= 0; - SELECT_LEX *select_lex= thd->lex->first_select_lex(); - DBUG_ENTER("mysql_prepare_delete"); - List<Item> all_fields; - - *delete_while_scanning= true; - thd->lex->allow_sum_func.clear_all(); - if (setup_tables_and_check_access(thd, &select_lex->context, - &select_lex->top_join_list, table_list, - select_lex->leaf_tables, FALSE, - DELETE_ACL, SELECT_ACL, TRUE)) - DBUG_RETURN(TRUE); - - if (table_list->vers_conditions.is_set() && table_list->is_view_or_derived()) - { - my_error(ER_IT_IS_A_VIEW, MYF(0), table_list->table_name.str); - DBUG_RETURN(true); - } - - if (table_list->has_period()) - { - if (table_list->is_view_or_derived()) - { - my_error(ER_IT_IS_A_VIEW, MYF(0), table_list->table_name.str); - DBUG_RETURN(true); - } - - if (select_lex->period_setup_conds(thd, table_list)) - DBUG_RETURN(true); - } - - DBUG_ASSERT(table_list->table); - // conds could be cached from previous SP call - DBUG_ASSERT(!table_list->vers_conditions.need_setup() || - !*conds || thd->stmt_arena->is_stmt_execute()); - if (select_lex->vers_setup_conds(thd, table_list)) - DBUG_RETURN(TRUE); - - *conds= select_lex->where; - - if (setup_returning_fields(thd, table_list) || - setup_conds(thd, table_list, select_lex->leaf_tables, conds) || - setup_ftfuncs(select_lex)) - DBUG_RETURN(TRUE); - if (!table_list->single_table_updatable() || - check_key_in_view(thd, table_list)) - { - my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias.str, "DELETE"); - DBUG_RETURN(TRUE); - } - - /* - Application-time periods: if FOR PORTION OF ... syntax used, DELETE - statement could issue delete_row's mixed with write_row's. This causes - problems for myisam and corrupts table, if deleting while scanning. - */ - if (table_list->has_period() - || unique_table(thd, table_list, table_list->next_global, 0)) - *delete_while_scanning= false; - - if (select_lex->inner_refs_list.elements && - fix_inner_refs(thd, all_fields, select_lex, select_lex->ref_pointer_array)) - DBUG_RETURN(TRUE); - - select_lex->fix_prepare_information(thd, conds, &fake_conds); - DBUG_RETURN(FALSE); -} - /*************************************************************************** Delete multiple tables from join @@ -1107,106 +979,6 @@ extern "C" int refpos_order_cmp(void* arg, const void *a,const void *b) return file->cmp_ref((const uchar*)a, (const uchar*)b); } -/* - make delete specific preparation and checks after opening tables - - SYNOPSIS - mysql_multi_delete_prepare() - thd thread handler - - RETURN - FALSE OK - TRUE Error -*/ - -int mysql_multi_delete_prepare(THD *thd) -{ - LEX *lex= thd->lex; - TABLE_LIST *aux_tables= lex->auxiliary_table_list.first; - TABLE_LIST *target_tbl; - DBUG_ENTER("mysql_multi_delete_prepare"); - - if (mysql_handle_derived(lex, DT_INIT)) - DBUG_RETURN(TRUE); - if (mysql_handle_derived(lex, DT_MERGE_FOR_INSERT)) - DBUG_RETURN(TRUE); - if (mysql_handle_derived(lex, DT_PREPARE)) - DBUG_RETURN(TRUE); - /* - setup_tables() need for VIEWs. JOIN::prepare() will not do it second - time. - - lex->query_tables also point on local list of DELETE SELECT_LEX - */ - if (setup_tables_and_check_access(thd, - &thd->lex->first_select_lex()->context, - &thd->lex->first_select_lex()-> - top_join_list, - lex->query_tables, - lex->first_select_lex()->leaf_tables, - FALSE, DELETE_ACL, SELECT_ACL, FALSE)) - DBUG_RETURN(TRUE); - - /* - Multi-delete can't be constructed over-union => we always have - single SELECT on top and have to check underlying SELECTs of it - */ - lex->first_select_lex()->set_unique_exclude(); - /* Fix tables-to-be-deleted-from list to point at opened tables */ - for (target_tbl= (TABLE_LIST*) aux_tables; - target_tbl; - target_tbl= target_tbl->next_local) - { - - target_tbl->table= target_tbl->correspondent_table->table; - if (target_tbl->correspondent_table->is_multitable()) - { - my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0), - target_tbl->correspondent_table->view_db.str, - target_tbl->correspondent_table->view_name.str); - DBUG_RETURN(TRUE); - } - - if (!target_tbl->correspondent_table->single_table_updatable() || - check_key_in_view(thd, target_tbl->correspondent_table)) - { - my_error(ER_NON_UPDATABLE_TABLE, MYF(0), - target_tbl->table_name.str, "DELETE"); - DBUG_RETURN(TRUE); - } - } - - for (target_tbl= (TABLE_LIST*) aux_tables; - target_tbl; - target_tbl= target_tbl->next_local) - { - /* - Check that table from which we delete is not used somewhere - inside subqueries/view. - */ - { - TABLE_LIST *duplicate; - if ((duplicate= unique_table(thd, target_tbl->correspondent_table, - lex->query_tables, 0))) - { - update_non_unique_table_error(target_tbl->correspondent_table, - "DELETE", duplicate); - DBUG_RETURN(TRUE); - } - } - } - /* - Reset the exclude flag to false so it doesn't interfare - with further calls to unique_table - */ - lex->first_select_lex()->exclude_from_table_unique_test= FALSE; - - if (lex->save_prep_leaf_tables()) - DBUG_RETURN(TRUE); - - DBUG_RETURN(FALSE); -} - multi_delete::multi_delete(THD *thd_arg, TABLE_LIST *dt, uint num_of_tables_arg): select_result_interceptor(thd_arg), delete_tables(dt), deleted(0), found(0), @@ -1659,3 +1431,301 @@ bool multi_delete::send_eof() } return 0; } + + +bool Sql_cmd_delete::precheck(THD *thd) +{ + if (!multitable) + { + if (delete_precheck(thd, lex->query_tables)) + return true; + } + else + { + if (multi_delete_precheck(thd, lex->query_tables)) + return true; + } + + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); + + return false; + +#ifdef WITH_WSREP +wsrep_error_label: +#endif + return true; +} + + +bool Sql_cmd_delete::prepare_inner(THD *thd) +{ + int err= 0; + TABLE_LIST *target_tbl; + JOIN *join; + SELECT_LEX *const select_lex = thd->lex->first_select_lex(); + TABLE_LIST *const table_list = select_lex->get_table_list(); + TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first; + ulonglong select_options= select_lex->options; + bool free_join= 1; + SELECT_LEX *returning= thd->lex->has_returning() ? thd->lex->returning() : 0; + const bool delete_history= table_list->vers_conditions.delete_history; + DBUG_ASSERT(!(delete_history && table_list->period_conditions.is_set())); + + DBUG_ENTER("Sql_cmd_delete::prepare_inner"); + + (void) read_statistics_for_tables_if_needed(thd, table_list); + + THD_STAGE_INFO(thd, stage_init_update); + + { + if (mysql_handle_derived(lex, DT_INIT)) + DBUG_RETURN(TRUE); + if (mysql_handle_derived(lex, DT_MERGE_FOR_INSERT)) + DBUG_RETURN(TRUE); + if (mysql_handle_derived(lex, DT_PREPARE)) + DBUG_RETURN(TRUE); + } + + if (!(result= new (thd->mem_root) multi_delete(thd, aux_tables, + lex->table_count))) + { + DBUG_RETURN(TRUE); + } + + table_list->delete_while_scanning= true; + + if (!multitable && !table_list->single_table_updatable()) + { + my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias.str, "DELETE"); + DBUG_RETURN(TRUE); + } + + if (!multitable && (!table_list->table || !table_list->table->is_created())) + { + my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0), + table_list->view_db.str, table_list->view_name.str); + DBUG_RETURN(TRUE); + } + + if (setup_tables_and_check_access(thd, &select_lex->context, + &select_lex->top_join_list, + table_list, select_lex->leaf_tables, + false, DELETE_ACL, SELECT_ACL, true)) + DBUG_RETURN(TRUE); + + if (setup_tables(thd, &select_lex->context, &select_lex->top_join_list, + table_list, select_lex->leaf_tables, false, false)) + DBUG_RETURN(TRUE); + + if (!multitable) + { + if (table_list->vers_conditions.is_set() && table_list->is_view_or_derived()) + { + my_error(ER_IT_IS_A_VIEW, MYF(0), table_list->table_name.str); + DBUG_RETURN(true); + } + + if (table_list->has_period()) + { + if (table_list->is_view_or_derived()) + { + my_error(ER_IT_IS_A_VIEW, MYF(0), table_list->table_name.str); + DBUG_RETURN(true); + } + + if (select_lex->period_setup_conds(thd, table_list)) + DBUG_RETURN(true); + } + + if (select_lex->vers_setup_conds(thd, table_list)) + DBUG_RETURN(TRUE); + /* + Application-time periods: if FOR PORTION OF ... syntax used, DELETE + statement could issue delete_row's mixed with write_row's. This causes + problems for myisam and corrupts table, if deleting while scanning. + */ + if (table_list->has_period() + || unique_table(thd, table_list, table_list->next_global, 0)) + table_list->delete_while_scanning= false; + } + + if (multitable) + { + /* + Multi-delete can't be constructed over-union => we always have + single SELECT on top and have to check underlying SELECTs of it + */ + lex->first_select_lex()->set_unique_exclude(); + /* Fix tables-to-be-deleted-from list to point at opened tables */ + for (target_tbl= (TABLE_LIST*) aux_tables; + target_tbl; + target_tbl= target_tbl->next_local) + { + target_tbl->table= target_tbl->correspondent_table->table; + if (target_tbl->correspondent_table->is_multitable()) + { + my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0), + target_tbl->correspondent_table->view_db.str, + target_tbl->correspondent_table->view_name.str); + DBUG_RETURN(TRUE); + } + + if (!target_tbl->correspondent_table->single_table_updatable() || + check_key_in_view(thd, target_tbl->correspondent_table)) + { + my_error(ER_NON_UPDATABLE_TABLE, MYF(0), + target_tbl->table_name.str, "DELETE"); + DBUG_RETURN(TRUE); + } + } + + for (target_tbl= (TABLE_LIST*) aux_tables; + target_tbl; + target_tbl= target_tbl->next_local) + { + /* + Check that table from which we delete is not used somewhere + inside subqueries/view. + */ + { + TABLE_LIST *duplicate; + if ((duplicate= unique_table(thd, target_tbl->correspondent_table, + lex->query_tables, 0))) + { + update_non_unique_table_error(target_tbl->correspondent_table, + "DELETE", duplicate); + DBUG_RETURN(TRUE); + } + } + } + /* + Reset the exclude flag to false so it doesn't interfare + with further calls to unique_table + */ + lex->first_select_lex()->exclude_from_table_unique_test= FALSE; + } + + { + if (thd->lex->describe) + select_options|= SELECT_DESCRIBE; + + /* + When in EXPLAIN, delay deleting the joins so that they are still + available when we're producing EXPLAIN EXTENDED warning text. + */ + if (select_options & SELECT_DESCRIBE) + free_join= 0; + select_options|= + SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | OPTION_SETUP_TABLES_DONE; + + if (!(join= new (thd->mem_root) JOIN(thd, empty_list, + select_options, result))) + DBUG_RETURN(TRUE); + THD_STAGE_INFO(thd, stage_init); + select_lex->join= join; + thd->lex->used_tables=0; + if ((err= join->prepare(table_list, select_lex->where, + select_lex->order_list.elements, + select_lex->order_list.first, + false, NULL, NULL, NULL, + select_lex, &lex->unit))) + + { + goto err; + } + + } + + if (!multitable && table_list->has_period()) + { + if (!table_list->period_conditions.start.item->const_item() + || !table_list->period_conditions.end.item->const_item()) + { + my_error(ER_NOT_CONSTANT_EXPRESSION, MYF(0), "FOR PORTION OF"); + DBUG_RETURN(true); + } + } + + if (delete_history) + table_list->table->vers_write= false; + + if (setup_returning_fields(thd, table_list) || + setup_ftfuncs(select_lex)) + goto err; + + free_join= false; + + if (returning) + (void) result->prepare(returning->item_list, NULL); + +err: + + if (free_join) + { + THD_STAGE_INFO(thd, stage_end); + err|= (int)(select_lex->cleanup()); + DBUG_RETURN(err || thd->is_error()); + } + DBUG_RETURN(err); + +} + +bool Sql_cmd_delete::execute_inner(THD *thd) +{ + if (!multitable) + { + if (lex->has_returning()) + { + select_result *sel_result= NULL; + delete result; + /* This is DELETE ... RETURNING. It will return output to the client */ + if (thd->lex->analyze_stmt) + { + /* + Actually, it is ANALYZE .. DELETE .. RETURNING. We need to produce + output and then discard it. + */ + sel_result= new (thd->mem_root) select_send_analyze(thd); + save_protocol= thd->protocol; + thd->protocol= new Protocol_discard(thd); + } + else + { + if (!lex->result && !(sel_result= new (thd->mem_root) select_send(thd))) + return true; + } + result= lex->result ? lex->result : sel_result; + } + } + + bool res= multitable ? Sql_cmd_dml::execute_inner(thd) + : delete_from_single_table(thd); + + res|= thd->is_error(); + + if (save_protocol) + { + delete thd->protocol; + thd->protocol= save_protocol; + } + { + if (unlikely(res)) + { + if (multitable) + result->abort_result_set(); + } + else + { + if (thd->lex->describe || thd->lex->analyze_stmt) + res= thd->lex->explain->send_explain(thd); + } + } + + if (result) + { + res= false; + delete result; + } + + return res; +} diff --git a/sql/sql_delete.h b/sql/sql_delete.h index 520524c..64b882a 100644 --- a/sql/sql_delete.h +++ b/sql/sql_delete.h @@ -17,6 +17,9 @@ #define SQL_DELETE_INCLUDED #include "my_base.h" /* ha_rows */ +#include "sql_class.h" /* enum_duplicates */ +#include "sql_cmd.h" // Sql_cmd_dml +#include "sql_base.h" class THD; struct TABLE_LIST; @@ -26,10 +29,34 @@ class select_result; typedef class Item COND; template <typename T> class SQL_I_List; -int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds, - bool *delete_while_scanning); -bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, - SQL_I_List<ORDER> *order, ha_rows rows, - ulonglong options, select_result *result); +class Sql_cmd_delete final : public Sql_cmd_dml +{ +public: + Sql_cmd_delete(bool multitable_arg) + : multitable(multitable_arg) {} + enum_sql_command sql_command_code() const override + { + return multitable ? SQLCOM_DELETE_MULTI : SQLCOM_DELETE; + } + + DML_prelocking_strategy *get_dml_prelocking_strategy() + { + return &dml_prelocking_strategy; + } + +protected: + bool precheck(THD *thd) override; + + bool prepare_inner(THD *thd) override; + + bool execute_inner(THD *thd) override; + + private: + bool delete_from_single_table(THD *thd); + + bool multitable; + + DML_prelocking_strategy dml_prelocking_strategy; +}; #endif /* SQL_DELETE_INCLUDED */ diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 5f2f072..070cf52 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1302,6 +1302,8 @@ void LEX::start(THD *thd_arg) wild= 0; exchange= 0; + table_count= 0; + DBUG_VOID_RETURN; } @@ -3029,6 +3031,7 @@ void st_select_lex::init_select() curr_tvc_name= 0; versioned_tables= 0; nest_flags= 0; + item_list_usage= MARK_COLUMNS_READ; } /* @@ -3299,34 +3302,6 @@ void st_select_lex_unit::exclude_level() } -#if 0 -/* - Exclude subtree of current unit from tree of SELECTs - - SYNOPSYS - st_select_lex_unit::exclude_tree() -*/ -void st_select_lex_unit::exclude_tree() -{ - for (SELECT_LEX *sl= first_select(); sl; sl= sl->next_select()) - { - // unlink current level from global SELECTs list - if (sl->link_prev && (*sl->link_prev= sl->link_next)) - sl->link_next->link_prev= sl->link_prev; - - // unlink underlay levels - for (SELECT_LEX_UNIT *u= sl->first_inner_unit(); u; u= u->next_unit()) - { - u->exclude_level(); - } - } - // exclude currect unit from list of nodes - (*prev)= next; - if (next) - next->prev= prev; -} -#endif - /* st_select_lex_node::mark_as_dependent mark all st_select_lex struct from @@ -3548,7 +3523,7 @@ bool st_select_lex::setup_ref_array(THD *thd, uint order_group_num) select_n_where_fields + order_group_num + hidden_bit_fields + - fields_in_window_functions) * (size_t) 5; + fields_in_window_functions + 1) * (size_t) 5; DBUG_ASSERT(n_elems % 5 == 0); if (!ref_pointer_array.is_null()) { @@ -4094,6 +4069,12 @@ bool LEX::can_not_use_merged(bool no_update_or_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 || + query_tables->is_multitable())) + return TRUE; + default: return FALSE; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 4f2e775..0badc32 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -36,6 +36,7 @@ #include "sql_limit.h" // Select_limit_counters #include "json_table.h" // Json_table_column #include "sql_schema.h" +#include "sql_class.h" // enum enum_column_usage /* Used for flags of nesting constructs */ #define SELECT_NESTING_MAP_SIZE 64 @@ -873,6 +874,8 @@ class st_select_lex_unit: public st_select_lex_node { { } + void set_query_result(select_result *res) { result= res; } + TABLE *table; /* temporary table using for appending UNION results */ select_result *result; st_select_lex *pre_last_parse; @@ -1005,6 +1008,7 @@ class st_select_lex_unit: public st_select_lex_node { bool add_fake_select_lex(THD *thd); void init_prepare_fake_select_lex(THD *thd, bool first_execution); + void set_prepared() { prepared = true; } inline bool is_prepared() { return prepared; } bool change_result(select_result_interceptor *result, select_result_interceptor *old_result); @@ -1107,6 +1111,7 @@ class st_select_lex: public st_select_lex_node Item *prep_having;/* saved HAVING clause for prepared statement processing */ Item *cond_pushed_into_where; /* condition pushed into WHERE */ Item *cond_pushed_into_having; /* condition pushed into HAVING */ + Item *where_cond_after_prepare; /* nest_levels are local to the query or VIEW, @@ -1215,6 +1220,7 @@ class st_select_lex: public st_select_lex_node List<List_item> save_many_values; List<Item> *save_insert_list; + enum_column_usage item_list_usage; bool is_item_list_lookup:1; /* Needed to correctly generate 'PRIMARY' or 'SIMPLE' for select_type column @@ -1745,6 +1751,25 @@ class Query_tables_list uint sroutines_list_own_elements; /** + Locking state of tables in this particular statement. + + If we under LOCK TABLES or in prelocked mode we consider tables + for the statement to be "locked" if there was a call to lock_tables() + (which called handler::start_stmt()) for tables of this statement + and there was no matching close_thread_tables() call. + + As result this state may differ significantly from one represented + by Open_tables_state::lock/locked_tables_mode more, which are always + "on" under LOCK TABLES or in prelocked mode. + */ + enum enum_lock_tables_state { LTS_NOT_LOCKED = 0, LTS_LOCKED }; + enum_lock_tables_state lock_tables_state; + bool is_query_tables_locked() const + { + return (lock_tables_state == LTS_LOCKED); + } + + /** Number of tables which were open by open_tables() and to be locked by lock_tables(). Note that we set this member only in some cases, when this value @@ -3391,6 +3416,7 @@ struct LEX: public Query_tables_list bool default_used:1; /* using default() function */ bool with_rownum:1; /* Using rownum() function */ bool is_lex_started:1; /* If lex_start() did run. For debugging. */ + /* This variable is used in post-parse stage to declare that sum-functions, or functions which have sense only if GROUP BY is present, are allowed. diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 0597b08..8b906be 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -34,9 +34,7 @@ #include "sql_locale.h" // my_locale_en_US #include "log.h" // flush_error_log #include "sql_view.h" // mysql_create_view, mysql_drop_view -#include "sql_delete.h" // mysql_delete #include "sql_insert.h" // mysql_insert -#include "sql_update.h" // mysql_update, mysql_multi_update #include "sql_partition.h" // struct partition_info #include "sql_db.h" // mysql_change_db, mysql_create_db, // mysql_rm_db, mysql_upgrade_db, @@ -3442,7 +3440,6 @@ int mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) { int res= 0; - int up_result= 0; LEX *lex= thd->lex; /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ SELECT_LEX *select_lex= lex->first_select_lex(); @@ -3454,7 +3451,6 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) SELECT_LEX_UNIT *unit= &lex->unit; #ifdef HAVE_REPLICATION /* have table map for update for multi-update statement (BUG#37051) */ - bool have_table_map_for_update= FALSE; /* */ Rpl_filter *rpl_filter; #endif @@ -3576,7 +3572,6 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) if (lex->sql_command == SQLCOM_UPDATE_MULTI && thd->table_map_for_update) { - have_table_map_for_update= TRUE; table_map table_map_for_update= thd->table_map_for_update; uint nr= 0; TABLE_LIST *table; @@ -4381,130 +4376,15 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) break; } case SQLCOM_UPDATE: - { - WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); - ha_rows found= 0, updated= 0; - DBUG_ASSERT(first_table == all_tables && first_table != 0); - WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); - - if (update_precheck(thd, all_tables)) - break; - - /* - UPDATE IGNORE can be unsafe. We therefore use row based - logging if mixed or row based logging is available. - TODO: Check if the order of the output of the select statement is - deterministic. Waiting for BUG#42415 - */ - if (lex->ignore) - lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_UPDATE_IGNORE); - - DBUG_ASSERT(select_lex->limit_params.offset_limit == 0); - unit->set_limit(select_lex); - MYSQL_UPDATE_START(thd->query()); - res= up_result= mysql_update(thd, all_tables, - select_lex->item_list, - lex->value_list, - select_lex->where, - select_lex->order_list.elements, - select_lex->order_list.first, - unit->lim.get_select_limit(), - lex->ignore, &found, &updated); - MYSQL_UPDATE_DONE(res, found, updated); - /* 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: + case SQLCOM_DELETE: + case SQLCOM_DELETE_MULTI: { DBUG_ASSERT(first_table == all_tables && first_table != 0); - /* if we switched from normal update, rights are checked */ - if (up_result != 2) - { - WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); - if ((res= multi_update_precheck(thd, all_tables))) - break; - } - else - res= 0; - - unit->set_limit(select_lex); - /* - We can not use mysql_explain_union() because of parameters of - mysql_select in mysql_multi_update so just set the option if needed - */ - if (thd->lex->describe) - { - select_lex->set_explain_type(FALSE); - select_lex->options|= SELECT_DESCRIBE; - } - - res= mysql_multi_update_prepare(thd); + DBUG_ASSERT(lex->m_sql_cmd != NULL); -#ifdef HAVE_REPLICATION - /* Check slave filtering rules */ - if (unlikely(thd->slave_thread && !have_table_map_for_update)) - { - if (all_tables_not_ok(thd, all_tables)) - { - if (res!= 0) - { - res= 0; /* don't care of prev failure */ - thd->clear_error(); /* filters are of highest prior */ - } - /* we warn the slave SQL thread */ - my_error(ER_SLAVE_IGNORED_TABLE, MYF(0)); - break; - } - if (res) - break; - } - else - { -#endif /* HAVE_REPLICATION */ - if (res) - break; - if (opt_readonly && - !(thd->security_ctx->master_access & PRIV_IGNORE_READ_ONLY) && - some_non_temp_table_to_be_updated(thd, all_tables)) - { - my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only"); - break; - } -#ifdef HAVE_REPLICATION - } /* unlikely */ -#endif - { - multi_update *result_obj; - MYSQL_MULTI_UPDATE_START(thd->query()); - res= mysql_multi_update(thd, all_tables, - &select_lex->item_list, - &lex->value_list, - select_lex->where, - select_lex->options, - lex->duplicates, - lex->ignore, - unit, - select_lex, - &result_obj); - if (result_obj) - { - MYSQL_MULTI_UPDATE_DONE(res, result_obj->num_found(), - result_obj->num_updated()); - res= FALSE; /* Ignore errors here */ - delete result_obj; - } - else - { - MYSQL_MULTI_UPDATE_DONE(1, 0, 0); - } - } + res = lex->m_sql_cmd->execute(thd); + thd->abort_on_warning= 0; break; } case SQLCOM_REPLACE: @@ -4766,129 +4646,6 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) break; } - case SQLCOM_DELETE: - { - WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); - select_result *sel_result= NULL; - DBUG_ASSERT(first_table == all_tables && first_table != 0); - WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); - - if ((res= delete_precheck(thd, all_tables))) - break; - DBUG_ASSERT(select_lex->limit_params.offset_limit == 0); - unit->set_limit(select_lex); - - MYSQL_DELETE_START(thd->query()); - Protocol *save_protocol= NULL; - - if (lex->has_returning()) - { - /* This is DELETE ... RETURNING. It will return output to the client */ - if (thd->lex->analyze_stmt) - { - /* - Actually, it is ANALYZE .. DELETE .. RETURNING. We need to produce - output and then discard it. - */ - sel_result= new (thd->mem_root) select_send_analyze(thd); - save_protocol= thd->protocol; - thd->protocol= new Protocol_discard(thd); - } - else - { - if (!lex->result && !(sel_result= new (thd->mem_root) select_send(thd))) - goto error; - } - } - - res = mysql_delete(thd, all_tables, - select_lex->where, &select_lex->order_list, - unit->lim.get_select_limit(), select_lex->options, - lex->result ? lex->result : sel_result); - - if (save_protocol) - { - delete thd->protocol; - thd->protocol= save_protocol; - } - - if (thd->lex->analyze_stmt || thd->lex->describe) - { - if (!res) - res= thd->lex->explain->send_explain(thd); - } - - delete sel_result; - MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func()); - break; - } - case SQLCOM_DELETE_MULTI: - { - WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); - DBUG_ASSERT(first_table == all_tables && first_table != 0); - TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first; - multi_delete *result; - WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); - - if ((res= multi_delete_precheck(thd, all_tables))) - break; - - /* condition will be TRUE on SP re-excuting */ - if (select_lex->item_list.elements != 0) - select_lex->item_list.empty(); - if (add_item_to_list(thd, new (thd->mem_root) Item_null(thd))) - goto error; - - THD_STAGE_INFO(thd, stage_init); - if ((res= open_and_lock_tables(thd, all_tables, TRUE, 0))) - break; - - MYSQL_MULTI_DELETE_START(thd->query()); - if (unlikely(res= mysql_multi_delete_prepare(thd))) - { - MYSQL_MULTI_DELETE_DONE(1, 0); - goto error; - } - - if (likely(!thd->is_fatal_error)) - { - result= new (thd->mem_root) multi_delete(thd, aux_tables, - lex->table_count); - if (likely(result)) - { - if (unlikely(select_lex->vers_setup_conds(thd, aux_tables))) - goto multi_delete_error; - res= mysql_select(thd, - select_lex->get_table_list(), - select_lex->item_list, - select_lex->where, - 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, - (ORDER *)NULL, - (select_lex->options | thd->variables.option_bits | - SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | - OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT, - result, unit, select_lex); - res|= (int)(thd->is_error()); - - MYSQL_MULTI_DELETE_DONE(res, result->num_deleted()); - if (res) - result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ - else - { - if (lex->describe || lex->analyze_stmt) - res= thd->lex->explain->send_explain(thd); - } - multi_delete_error: - delete result; - } - } - else - { - res= TRUE; // Error - MYSQL_MULTI_DELETE_DONE(1, 0); - } - break; - } case SQLCOM_DROP_SEQUENCE: case SQLCOM_DROP_TABLE: { @@ -7772,12 +7529,16 @@ void create_select_for_variable(THD *thd, LEX_CSTRING *var_name) } -void mysql_init_multi_delete(LEX *lex) +void mysql_init_delete(LEX *lex) { - lex->sql_command= SQLCOM_DELETE_MULTI; mysql_init_select(lex); lex->first_select_lex()->limit_params.clear(); lex->unit.lim.clear(); +} + +void mysql_init_multi_delete(LEX *lex) +{ + lex->sql_command= SQLCOM_DELETE_MULTI; lex->first_select_lex()->table_list. save_and_clear(&lex->auxiliary_table_list); lex->query_tables= 0; diff --git a/sql/sql_parse.h b/sql/sql_parse.h index d3cf83b..3f3302b 100644 --- a/sql/sql_parse.h +++ b/sql/sql_parse.h @@ -93,6 +93,7 @@ void mysql_parse(THD *thd, char *rawbuf, uint length, bool mysql_new_select(LEX *lex, bool move_down, SELECT_LEX *sel); void create_select_for_variable(THD *thd, LEX_CSTRING *var_name); void create_table_set_open_action_and_adjust_tables(LEX *lex); +void mysql_init_delete(LEX *lex); void mysql_init_multi_delete(LEX *lex); bool multi_delete_set_locks_and_link_aux_tables(LEX *lex); void create_table_set_open_action_and_adjust_tables(LEX *lex); diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index b10a81d..b9ae058 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -95,10 +95,8 @@ When one supplies long data for a placeholder: #include "sql_base.h" // open_normal_and_derived_tables #include "sql_cache.h" // query_cache_* #include "sql_view.h" // create_view_precheck -#include "sql_delete.h" // mysql_prepare_delete #include "sql_select.h" // for JOIN #include "sql_insert.h" // upgrade_lock_type_for_insert, mysql_prepare_insert -#include "sql_update.h" // mysql_prepare_update #include "sql_db.h" // mysql_opt_change_db, mysql_change_db #include "sql_derived.h" // mysql_derived_prepare, // mysql_handle_derived @@ -1399,160 +1397,6 @@ static bool mysql_test_insert(Prepared_statement *stmt, } -/** - Validate UPDATE statement. - - @param stmt prepared statement - @param tables list of tables used in this query - - @todo - - here we should send types of placeholders to the client. - - @retval - 0 success - @retval - 1 error, error message is set in THD - @retval - 2 convert to multi_update -*/ - -static int mysql_test_update(Prepared_statement *stmt, - TABLE_LIST *table_list) -{ - int res; - THD *thd= stmt->thd; - uint table_count= 0; - TABLE_LIST *update_source_table; - SELECT_LEX *select= stmt->lex->first_select_lex(); -#ifndef NO_EMBEDDED_ACCESS_CHECKS - privilege_t want_privilege(NO_ACL); -#endif - DBUG_ENTER("mysql_test_update"); - - if (update_precheck(thd, table_list) || - open_tables(thd, &table_list, &table_count, MYSQL_OPEN_FORCE_SHARED_MDL)) - goto error; - - if (mysql_handle_derived(thd->lex, DT_INIT)) - goto error; - - if (((update_source_table= unique_table(thd, table_list, - table_list->next_global, 0)) || - table_list->is_multitable())) - { - DBUG_ASSERT(update_source_table || table_list->view != 0); - DBUG_PRINT("info", ("Switch to multi-update")); - /* pass counter value */ - thd->lex->table_count= table_count; - /* convert to multiupdate */ - DBUG_RETURN(2); - } - - /* - thd->fill_derived_tables() is false here for sure (because it is - preparation of PS, so we even do not check it). - */ - if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT)) - goto error; - if (table_list->handle_derived(thd->lex, DT_PREPARE)) - goto error; - - if (!table_list->single_table_updatable()) - { - my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias.str, "UPDATE"); - goto error; - } - -#ifndef NO_EMBEDDED_ACCESS_CHECKS - /* Force privilege re-checking for views after they have been opened. */ - want_privilege= (table_list->view ? UPDATE_ACL : - table_list->grant.want_privilege); -#endif - - if (mysql_prepare_update(thd, table_list, &select->where, - select->order_list.elements, - select->order_list.first)) - goto error; - -#ifndef NO_EMBEDDED_ACCESS_CHECKS - table_list->grant.want_privilege= want_privilege; - table_list->table->grant.want_privilege= want_privilege; - table_list->register_want_access(want_privilege); -#endif - thd->lex->first_select_lex()->no_wrap_view_item= TRUE; - res= setup_fields(thd, Ref_ptr_array(), - select->item_list, MARK_COLUMNS_READ, 0, NULL, 0); - thd->lex->first_select_lex()->no_wrap_view_item= FALSE; - if (res) - goto error; -#ifndef NO_EMBEDDED_ACCESS_CHECKS - /* Check values */ - table_list->grant.want_privilege= - table_list->table->grant.want_privilege= - (SELECT_ACL & ~table_list->table->grant.privilege); - table_list->register_want_access(SELECT_ACL); -#endif - if (setup_fields(thd, Ref_ptr_array(), - stmt->lex->value_list, COLUMNS_READ, 0, NULL, 0) || - check_unique_table(thd, table_list)) - goto error; - /* TODO: here we should send types of placeholders to the client. */ - DBUG_RETURN(0); -error: - DBUG_RETURN(1); -} - - -/** - Validate DELETE statement. - - @param stmt prepared statement - @param tables list of tables used in this query - - @retval - FALSE success - @retval - TRUE error, error message is set in THD -*/ - -static bool mysql_test_delete(Prepared_statement *stmt, - TABLE_LIST *table_list) -{ - uint table_count= 0; - THD *thd= stmt->thd; - LEX *lex= stmt->lex; - bool delete_while_scanning; - DBUG_ENTER("mysql_test_delete"); - - if (delete_precheck(thd, table_list) || - open_tables(thd, &table_list, &table_count, MYSQL_OPEN_FORCE_SHARED_MDL)) - goto error; - - if (mysql_handle_derived(thd->lex, DT_INIT)) - goto error; - if (mysql_handle_derived(thd->lex, DT_MERGE_FOR_INSERT)) - goto error; - if (mysql_handle_derived(thd->lex, DT_PREPARE)) - goto error; - - if (!table_list->single_table_updatable()) - { - my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias.str, "DELETE"); - goto error; - } - if (!table_list->table || !table_list->table->is_created()) - { - my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0), - table_list->view_db.str, table_list->view_name.str); - goto error; - } - - DBUG_RETURN(mysql_prepare_delete(thd, table_list, - &lex->first_select_lex()->where, - &delete_while_scanning)); -error: - DBUG_RETURN(TRUE); -} /** @@ -2135,74 +1979,6 @@ static bool mysql_test_create_view(Prepared_statement *stmt) } -/* - Validate and prepare for execution a multi update statement. - - @param stmt prepared statement - @param tables list of tables used in this query - @param converted converted to multi-update from usual update - - @retval - FALSE success - @retval - TRUE error, error message is set in THD -*/ - -static bool mysql_test_multiupdate(Prepared_statement *stmt, - TABLE_LIST *tables, - bool converted) -{ - /* if we switched from normal update, rights are checked */ - if (!converted && multi_update_precheck(stmt->thd, tables)) - return TRUE; - - return select_like_stmt_test(stmt, &mysql_multi_update_prepare, - OPTION_SETUP_TABLES_DONE); -} - - -/** - Validate and prepare for execution a multi delete statement. - - @param stmt prepared statement - @param tables list of tables used in this query - - @retval - FALSE success - @retval - TRUE error, error message in THD is set. -*/ - -static bool mysql_test_multidelete(Prepared_statement *stmt, - TABLE_LIST *tables) -{ - THD *thd= stmt->thd; - - thd->lex->current_select= thd->lex->first_select_lex(); - if (add_item_to_list(thd, new (thd->mem_root) - Item_null(thd))) - { - my_error(ER_OUTOFMEMORY, MYF(ME_FATAL), 0); - goto error; - } - - if (multi_delete_precheck(thd, tables) || - select_like_stmt_test_with_open(stmt, tables, - &mysql_multi_delete_prepare, - OPTION_SETUP_TABLES_DONE)) - goto error; - if (!tables->table) - { - my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0), - tables->view_db.str, tables->view_name.str); - goto error; - } - return FALSE; -error: - return TRUE; -} - - /** Wrapper for mysql_insert_select_prepare, to make change of local tables after open_normal_and_derived_tables() call. @@ -2484,18 +2260,14 @@ static bool check_prepared_statement(Prepared_statement *stmt) break; case SQLCOM_UPDATE: - res= mysql_test_update(stmt, tables); - /* mysql_test_update returns 2 if we need to switch to multi-update */ - if (res != 2) - break; - /* fall through */ case SQLCOM_UPDATE_MULTI: - res= mysql_test_multiupdate(stmt, tables, res == 2); - break; - case SQLCOM_DELETE: - res= mysql_test_delete(stmt, tables); + case SQLCOM_DELETE_MULTI: + res = lex->m_sql_cmd->prepare(thd); + if (!res) + lex->m_sql_cmd->unprepare(thd); break; + /* The following allow WHERE clause, so they must be tested like SELECT */ case SQLCOM_SHOW_DATABASES: case SQLCOM_SHOW_TABLES: @@ -2632,10 +2404,6 @@ static bool check_prepared_statement(Prepared_statement *stmt) res= mysql_test_set_fields(stmt, tables, &lex->var_list); break; - case SQLCOM_DELETE_MULTI: - res= mysql_test_multidelete(stmt, tables); - break; - case SQLCOM_INSERT_SELECT: case SQLCOM_REPLACE_SELECT: res= mysql_test_insert_select(stmt, tables); @@ -4373,6 +4141,9 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) thd->is_error() || init_param_array(this)); + if (lex->m_sql_cmd) + lex->m_sql_cmd->set_owner(this); + if (thd->security_ctx->password_expired && lex->sql_command != SQLCOM_SET_OPTION && lex->sql_command != SQLCOM_PREPARE && diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 2a56292..7db5daa 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1430,7 +1430,7 @@ JOIN::prepare(TABLE_LIST *tables_init, COND *conds_init, uint og_num, } } - if (setup_fields(thd, ref_ptrs, fields_list, MARK_COLUMNS_READ, + if (setup_fields(thd, ref_ptrs, fields_list, select_lex->item_list_usage, &all_fields, &select_lex->pre_fix, 1)) DBUG_RETURN(-1); thd->lex->current_select->context_analysis_place= save_place; @@ -1720,6 +1720,8 @@ JOIN::prepare(TABLE_LIST *tables_init, COND *conds_init, uint og_num, if (!procedure && result && result->prepare(fields_list, unit_arg)) goto err; /* purecov: inspected */ + select_lex->where_cond_after_prepare= conds; + unit= unit_arg; if (prepare_stage2()) goto err; @@ -29028,7 +29030,8 @@ static bool get_range_limit_read_cost(const JOIN_TAB *tab, @note This function takes into account table->opt_range_condition_rows statistic (that is calculated by the make_join_statistics function). - However, single table procedures such as mysql_update() and mysql_delete() + However, single table procedures such as Sql_cmd_update:update_single_table() + and Sql_cmd_delete::delete_single_table() never call make_join_statistics, so they have to update it manually (@see get_index_for_order()). */ @@ -30463,6 +30466,208 @@ static bool process_direct_rownum_comparison(THD *thd, SELECT_LEX_UNIT *unit, } +static void MYSQL_DML_START(THD *thd) +{ + switch (thd->lex->sql_command) { + + case SQLCOM_UPDATE: + MYSQL_UPDATE_START(thd->query()); + break; + case SQLCOM_UPDATE_MULTI: + MYSQL_MULTI_UPDATE_START(thd->query()); + break; + case SQLCOM_DELETE: + MYSQL_DELETE_START(thd->query()); + break; + case SQLCOM_DELETE_MULTI: + MYSQL_MULTI_DELETE_START(thd->query()); + break; + default: + DBUG_ASSERT(0); + } +} + + +static void MYSQL_DML_DONE(THD *thd, int rc) +{ + switch (thd->lex->sql_command) { + + case SQLCOM_UPDATE: + MYSQL_UPDATE_DONE( + rc, + (rc ? 0 : + ((multi_update*)(((Sql_cmd_dml*)(thd->lex->m_sql_cmd))->get_result())) + ->num_found()), + (rc ? 0 : + ((multi_update*)(((Sql_cmd_dml*)(thd->lex->m_sql_cmd))->get_result())) + ->num_updated())); + break; + case SQLCOM_UPDATE_MULTI: + MYSQL_MULTI_UPDATE_DONE( + rc, + (rc ? 0 : + ((multi_update*)(((Sql_cmd_dml*)(thd->lex->m_sql_cmd))->get_result())) + ->num_found()), + (rc ? 0 : + ((multi_update*)(((Sql_cmd_dml*)(thd->lex->m_sql_cmd))->get_result())) + ->num_updated())); + break; + case SQLCOM_DELETE: + MYSQL_DELETE_DONE(rc, (rc ? 0 : (ulong) (thd->get_row_count_func()))); + break; + case SQLCOM_DELETE_MULTI: + MYSQL_MULTI_DELETE_DONE( + rc, + (rc ? 0 : + ((multi_delete*)(((Sql_cmd_dml*)(thd->lex->m_sql_cmd))->get_result())) + ->num_deleted())); + break; + default: + DBUG_ASSERT(0); + } +} + +bool Sql_cmd_dml::prepare(THD *thd) +{ + lex= thd->lex; + SELECT_LEX_UNIT *unit= &lex->unit; + + DBUG_ASSERT(!is_prepared()); + + // Perform a coarse statement-specific privilege check. + if (precheck(thd)) + goto err; + + MYSQL_DML_START(thd); + + lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED; + + if (open_tables_for_query(thd, lex->query_tables, &table_count, 0, + get_dml_prelocking_strategy())) + { + if (thd->is_error()) + goto err; + (void)unit->cleanup(); + return true; + } + + if (prepare_inner(thd)) + goto err; + + lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_DERIVED; + + set_prepared(); + unit->set_prepared(); + + return false; + +err: + DBUG_ASSERT(thd->is_error()); + DBUG_PRINT("info", ("report_error: %d", thd->is_error())); + + (void)unit->cleanup(); + + return true; +} + +bool Sql_cmd_dml::execute(THD *thd) +{ + lex = thd->lex; + bool res; + + SELECT_LEX_UNIT *unit = &lex->unit; + SELECT_LEX *select_lex= lex->first_select_lex(); + + if (!is_prepared()) + { + if (prepare(thd)) + goto err; + } + else + { + if (precheck(thd)) + goto err; + + MYSQL_DML_START(thd); + + if (open_tables_for_query(thd, lex->query_tables, &table_count, 0, + get_dml_prelocking_strategy())) + goto err; + } + + THD_STAGE_INFO(thd, stage_init); + + /* + Locking of tables is done after preparation but before optimization. + This allows to do better partition pruning and avoid locking unused + partitions. As a consequence, in such a case, prepare stage can rely only + on metadata about tables used and not data from them. + */ + if (!is_empty_query()) + { + if (lock_tables(thd, lex->query_tables, table_count, 0)) + goto err; + } + + unit->set_limit(select_lex); + + // Perform statement-specific execution + res = execute_inner(thd); + + if (res) + goto err; + + res= unit->cleanup(); + + // "unprepare" this object since unit->cleanup actually unprepares + unprepare(thd); + + THD_STAGE_INFO(thd, stage_end); + + MYSQL_DML_DONE(thd, res); + + return res; + +err: + DBUG_ASSERT(thd->is_error() || thd->killed); + MYSQL_DML_DONE(thd, 1); + THD_STAGE_INFO(thd, stage_end); + (void)unit->cleanup(); + + return thd->is_error(); +} + + +bool Sql_cmd_dml::execute_inner(THD *thd) +{ + SELECT_LEX_UNIT *unit = &lex->unit; + SELECT_LEX *select_lex= unit->first_select(); + JOIN *join= select_lex->join; + + if (join->optimize()) + goto err; + + if (thd->lex->describe & DESCRIBE_EXTENDED) + { + join->conds_history= join->conds; + join->having_history= (join->having?join->having:join->tmp_having); + } + + if (unlikely(thd->is_error())) + goto err; + + join->exec(); + + if (thd->lex->describe & DESCRIBE_EXTENDED) + { + select_lex->where= join->conds_history; + select_lex->having= join->having_history; + } + +err: + return join->error; +} + /** @} (end of group Query_Optimizer) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 74ed078..1161e03 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -197,22 +197,11 @@ static bool check_fields(THD *thd, TABLE_LIST *table, List<Item> &items, return true; } - DBUG_ASSERT(thd->lex->sql_command == SQLCOM_UPDATE); - for (List_iterator_fast<Item> it(items); (item=it++);) - { - Field *f= item->field_for_view_update()->field; - vers_select_conds_t &period= table->period_conditions; - if (period.field_start->field == f || period.field_end->field == f) - { - my_error(ER_PERIOD_COLUMNS_UPDATED, MYF(0), - item->name.str, period.name.str); - return true; - } - } } return FALSE; } + bool TABLE::vers_check_update(List<Item> &items) { List_iterator<Item> it(items); @@ -339,36 +328,19 @@ int cut_fields_for_portion_of_time(THD *thd, TABLE *table, return res; } -/* - Process usual UPDATE - - SYNOPSIS - mysql_update() - thd thread handler - fields fields for update - values values of fields for update - conds WHERE clause expression - order_num number of elemen in ORDER BY clause - order ORDER BY clause list - limit limit clause - - RETURN - 0 - OK - 2 - privilege check and openning table passed, but we need to convert to - multi-update because of view substitution - 1 - error -*/ -int mysql_update(THD *thd, - TABLE_LIST *table_list, - List<Item> &fields, - List<Item> &values, - COND *conds, - uint order_num, ORDER *order, - ha_rows limit, - bool ignore, - ha_rows *found_return, ha_rows *updated_return) +bool Sql_cmd_update::update_single_table(THD *thd) { + SELECT_LEX_UNIT *unit = &lex->unit; + SELECT_LEX *select_lex= unit->first_select(); + TABLE_LIST *const table_list = select_lex->get_table_list(); + List<Item> *fields= &select_lex->item_list; + List<Item> *values= &lex->value_list; + COND *conds= select_lex->where_cond_after_prepare; + ORDER *order= select_lex->order_list.first; + ha_rows limit= unit->lim.get_select_limit(); + bool ignore= lex->ignore; + bool using_limit= limit != HA_POS_ERROR; bool safe_update= (thd->variables.option_bits & OPTION_SAFE_UPDATES) && !thd->lex->describe; @@ -380,76 +352,39 @@ int mysql_update(THD *thd, ha_rows dup_key_found; bool need_sort= TRUE; bool reverse= FALSE; -#ifndef NO_EMBEDDED_ACCESS_CHECKS - privilege_t want_privilege(NO_ACL); -#endif - uint table_count= 0; ha_rows updated, updated_or_same, found; key_map old_covering_keys; TABLE *table; SQL_SELECT *select= NULL; SORT_INFO *file_sort= 0; READ_RECORD info; - SELECT_LEX *select_lex= thd->lex->first_select_lex(); ulonglong id; List<Item> all_fields; killed_state killed_status= NOT_KILLED; bool has_triggers, binlog_is_row, do_direct_update= FALSE; Update_plan query_plan(thd->mem_root); Explain_update *explain; - TABLE_LIST *update_source_table; query_plan.index= MAX_KEY; query_plan.using_filesort= FALSE; // For System Versioning (may need to insert new fields to a table). ha_rows rows_inserted= 0; - DBUG_ENTER("mysql_update"); + DBUG_ENTER("Sql_cmd_update::update_single_table"); + THD_STAGE_INFO(thd, stage_init_update); create_explain_query(thd->lex, thd->mem_root); - if (open_tables(thd, &table_list, &table_count, 0)) - DBUG_RETURN(1); - - /* Prepare views so they are handled correctly */ - if (mysql_handle_derived(thd->lex, DT_INIT)) - DBUG_RETURN(1); - - if (table_list->has_period() && table_list->is_view_or_derived()) - { - my_error(ER_IT_IS_A_VIEW, MYF(0), table_list->table_name.str); - DBUG_RETURN(TRUE); - } - - if (((update_source_table=unique_table(thd, table_list, - table_list->next_global, 0)) || - table_list->is_multitable())) - { - DBUG_ASSERT(update_source_table || table_list->view != 0); - 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); - } - if (lock_tables(thd, table_list, table_count, 0)) - DBUG_RETURN(1); - (void) read_statistics_for_tables_if_needed(thd, table_list); + thd->table_map_for_update= 0; - THD_STAGE_INFO(thd, stage_init_update); if (table_list->handle_derived(thd->lex, DT_MERGE_FOR_INSERT)) DBUG_RETURN(1); if (table_list->handle_derived(thd->lex, DT_PREPARE)) DBUG_RETURN(1); + if (setup_ftfuncs(select_lex)) + DBUG_RETURN(1); + table= table_list->table; if (!table_list->single_table_updatable()) @@ -458,85 +393,26 @@ int mysql_update(THD *thd, DBUG_RETURN(1); } - /* Calculate "table->covering_keys" based on the WHERE */ - table->covering_keys= table->s->keys_in_use; table->opt_range_keys.clear_all(); query_plan.select_lex= thd->lex->first_select_lex(); query_plan.table= table; -#ifndef NO_EMBEDDED_ACCESS_CHECKS - /* Force privilege re-checking for views after they have been opened. */ - want_privilege= (table_list->view ? UPDATE_ACL : - table_list->grant.want_privilege); -#endif thd->lex->promote_select_describe_flag_if_needed(); - if (mysql_prepare_update(thd, table_list, &conds, order_num, order)) - DBUG_RETURN(1); - - if (table_list->has_period()) - { - if (!table_list->period_conditions.start.item->const_item() - || !table_list->period_conditions.end.item->const_item()) - { - my_error(ER_NOT_CONSTANT_EXPRESSION, MYF(0), "FOR PORTION OF"); - DBUG_RETURN(true); - } - table->no_cache= true; - } - old_covering_keys= table->covering_keys; // Keys used in WHERE - /* Check the fields we are going to modify */ -#ifndef NO_EMBEDDED_ACCESS_CHECKS - table_list->grant.want_privilege= table->grant.want_privilege= want_privilege; - table_list->register_want_access(want_privilege); -#endif - /* 'Unfix' fields to allow correct marking by the setup_fields function. */ - if (table_list->is_view()) - unfix_fields(fields); - if (setup_fields_with_no_wrap(thd, Ref_ptr_array(), - fields, MARK_COLUMNS_WRITE, 0, 0)) - DBUG_RETURN(1); /* purecov: inspected */ - if (check_fields(thd, table_list, fields, table_list->view)) - { - DBUG_RETURN(1); - } - bool has_vers_fields= table->vers_check_update(fields); - if (check_key_in_view(thd, table_list)) - { - my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias.str, "UPDATE"); - DBUG_RETURN(1); - } + bool has_vers_fields= table->vers_check_update(*fields); if (table->default_field) table->mark_default_fields_for_write(false); -#ifndef NO_EMBEDDED_ACCESS_CHECKS - /* Check values */ - table_list->grant.want_privilege= table->grant.want_privilege= - (SELECT_ACL & ~table->grant.privilege); -#endif - if (setup_fields(thd, Ref_ptr_array(), values, MARK_COLUMNS_READ, 0, NULL, 0)) - { - free_underlaid_joins(thd, select_lex); - DBUG_RETURN(1); /* purecov: inspected */ - } - - if (check_unique_table(thd, table_list)) - DBUG_RETURN(TRUE); - - switch_to_nullable_trigger_fields(fields, table); - switch_to_nullable_trigger_fields(values, table); + switch_to_nullable_trigger_fields(*fields, table); + switch_to_nullable_trigger_fields(*values, table); /* Apply the IN=>EXISTS transformation to all subqueries and optimize them */ if (select_lex->optimize_unflattened_subqueries(false)) DBUG_RETURN(TRUE); - if (select_lex->inner_refs_list.elements && - fix_inner_refs(thd, all_fields, select_lex, select_lex->ref_pointer_array)) - DBUG_RETURN(1); - if (conds) { Item::cond_result cond_value; @@ -776,9 +652,9 @@ int mysql_update(THD *thd, } if (use_direct_update && - !table->file->info_push(INFO_KIND_UPDATE_FIELDS, &fields) && - !table->file->info_push(INFO_KIND_UPDATE_VALUES, &values) && - !table->file->direct_update_rows_init(&fields)) + !table->file->info_push(INFO_KIND_UPDATE_FIELDS, fields) && + !table->file->info_push(INFO_KIND_UPDATE_VALUES, values) && + !table->file->direct_update_rows_init(fields)) { do_direct_update= TRUE; @@ -1027,7 +903,7 @@ int mysql_update(THD *thd, cut_fields_for_portion_of_time(thd, table, table_list->period_conditions); - if (fill_record_n_invoke_before_triggers(thd, table, fields, values, 0, + if (fill_record_n_invoke_before_triggers(thd, table, *fields, *values, 0, TRG_EVENT_UPDATE)) break; /* purecov: inspected */ @@ -1360,9 +1236,9 @@ int mysql_update(THD *thd, thd->lex->current_select->save_leaf_tables(thd); thd->lex->current_select->first_cond_optimization= 0; } - *found_return= found; - *updated_return= updated; - + ((multi_update *)result)->set_found(found); + ((multi_update *)result)->set_updated(updated); + if (unlikely(thd->lex->analyze_stmt)) goto emit_explain_and_leave; @@ -1394,75 +1270,6 @@ int mysql_update(THD *thd, DBUG_RETURN((err2 || thd->is_error()) ? 1 : 0); } -/* - Prepare items in UPDATE statement - - SYNOPSIS - mysql_prepare_update() - thd - thread handler - table_list - global/local table list - conds - conditions - order_num - number of ORDER BY list entries - order - ORDER BY clause list - - RETURN VALUE - FALSE OK - TRUE error -*/ -bool mysql_prepare_update(THD *thd, TABLE_LIST *table_list, - Item **conds, uint order_num, ORDER *order) -{ - Item *fake_conds= 0; -#ifndef NO_EMBEDDED_ACCESS_CHECKS - TABLE *table= table_list->table; -#endif - List<Item> all_fields; - SELECT_LEX *select_lex= thd->lex->first_select_lex(); - DBUG_ENTER("mysql_prepare_update"); - -#ifndef NO_EMBEDDED_ACCESS_CHECKS - table_list->grant.want_privilege= table->grant.want_privilege= - (SELECT_ACL & ~table->grant.privilege); - table_list->register_want_access(SELECT_ACL); -#endif - - thd->lex->allow_sum_func.clear_all(); - - if (table_list->has_period() && - select_lex->period_setup_conds(thd, table_list)) - DBUG_RETURN(true); - - DBUG_ASSERT(table_list->table); - // conds could be cached from previous SP call - DBUG_ASSERT(!table_list->vers_conditions.need_setup() || - !*conds || thd->stmt_arena->is_stmt_execute()); - if (select_lex->vers_setup_conds(thd, table_list)) - DBUG_RETURN(TRUE); - - *conds= select_lex->where; - - /* - We do not call DT_MERGE_FOR_INSERT because it has no sense for simple - (not multi-) update - */ - if (mysql_handle_derived(thd->lex, DT_PREPARE)) - DBUG_RETURN(TRUE); - - if (setup_tables_and_check_access(thd, &select_lex->context, - &select_lex->top_join_list, table_list, - select_lex->leaf_tables, - FALSE, UPDATE_ACL, SELECT_ACL, TRUE) || - setup_conds(thd, table_list, select_lex->leaf_tables, conds) || - select_lex->setup_ref_array(thd, order_num) || - setup_order(thd, select_lex->ref_pointer_array, - table_list, all_fields, all_fields, order) || - setup_ftfuncs(select_lex)) - DBUG_RETURN(TRUE); - - - select_lex->fix_prepare_information(thd, conds, &fake_conds); - DBUG_RETURN(FALSE); -} /** Check that we are not using table that we are updating in a sub select @@ -1690,15 +1497,6 @@ static bool multi_update_check_table_access(THD *thd, TABLE_LIST *table, } -class Multiupdate_prelocking_strategy : public DML_prelocking_strategy -{ - bool done; - bool has_prelocking_list; -public: - void reset(THD *thd); - bool handle_end(THD *thd); -}; - void Multiupdate_prelocking_strategy::reset(THD *thd) { done= false; @@ -1728,7 +1526,13 @@ bool Multiupdate_prelocking_strategy::handle_end(THD *thd) mysql_handle_derived(lex, DT_PREPARE)) DBUG_RETURN(1); - /* + if (table_list->has_period() && table_list->is_view_or_derived()) + { + my_error(ER_IT_IS_A_VIEW, MYF(0), table_list->table_name.str); + DBUG_RETURN(TRUE); + } + + /* setup_tables() need for VIEWs. JOIN::prepare() will call setup_tables() second time, but this call will do nothing (there are check for second call in setup_tables()). @@ -1739,6 +1543,10 @@ bool Multiupdate_prelocking_strategy::handle_end(THD *thd) FALSE, UPDATE_ACL, SELECT_ACL, TRUE)) DBUG_RETURN(1); + if (table_list->has_period() && + select_lex->period_setup_conds(thd, table_list)) + DBUG_RETURN(true); + List<Item> *fields= &lex->first_select_lex()->item_list; if (setup_fields_with_no_wrap(thd, Ref_ptr_array(), *fields, MARK_COLUMNS_WRITE, 0, 0)) @@ -1847,153 +1655,6 @@ bool Multiupdate_prelocking_strategy::handle_end(THD *thd) DBUG_RETURN(0); } -/* - make update specific preparation and checks after opening tables - - SYNOPSIS - mysql_multi_update_prepare() - thd thread handler - - RETURN - FALSE OK - TRUE Error -*/ - -int mysql_multi_update_prepare(THD *thd) -{ - LEX *lex= thd->lex; - TABLE_LIST *table_list= lex->query_tables; - TABLE_LIST *tl; - Multiupdate_prelocking_strategy prelocking_strategy; - uint table_count= lex->table_count; - DBUG_ENTER("mysql_multi_update_prepare"); - - /* - Open tables and create derived ones, but do not lock and fill them yet. - - During prepare phase acquire only S metadata locks instead of SW locks to - keep prepare of multi-UPDATE compatible with concurrent LOCK TABLES WRITE - and global read lock. - - Don't evaluate any subqueries even if constant, because - tables aren't locked yet. - */ - lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED; - if (thd->lex->sql_command == SQLCOM_UPDATE_MULTI) - { - if (open_tables(thd, &table_list, &table_count, - thd->stmt_arena->is_stmt_prepare() ? MYSQL_OPEN_FORCE_SHARED_MDL : 0, - &prelocking_strategy)) - DBUG_RETURN(TRUE); - } - else - { - /* following need for prepared statements, to run next time multi-update */ - thd->lex->sql_command= SQLCOM_UPDATE_MULTI; - prelocking_strategy.reset(thd); - if (prelocking_strategy.handle_end(thd)) - DBUG_RETURN(TRUE); - } - - /* now lock and fill tables */ - if (!thd->stmt_arena->is_stmt_prepare() && - lock_tables(thd, table_list, table_count, 0)) - DBUG_RETURN(TRUE); - - lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_DERIVED; - - (void) read_statistics_for_tables_if_needed(thd, table_list); - /* @todo: downgrade the metadata locks here. */ - - /* - Check that we are not using table that we are updating, but we should - skip all tables of UPDATE SELECT itself - */ - lex->first_select_lex()->exclude_from_table_unique_test= TRUE; - /* We only need SELECT privilege for columns in the values list */ - List_iterator<TABLE_LIST> ti(lex->first_select_lex()->leaf_tables); - while ((tl= ti++)) - { - if (tl->is_jtbm()) - continue; - TABLE *table= tl->table; - TABLE_LIST *tlist; - if (!(tlist= tl->top_table())->derived) - { - tlist->grant.want_privilege= - (SELECT_ACL & ~tlist->grant.privilege); - table->grant.want_privilege= (SELECT_ACL & ~table->grant.privilege); - } - DBUG_PRINT("info", ("table: %s want_privilege: %llx", tl->alias.str, - (longlong) table->grant.want_privilege)); - } - /* - Set exclude_from_table_unique_test value back to FALSE. It is needed for - further check in multi_update::prepare whether to use record cache. - */ - lex->first_select_lex()->exclude_from_table_unique_test= FALSE; - - if (lex->save_prep_leaf_tables()) - DBUG_RETURN(TRUE); - - DBUG_RETURN (FALSE); -} - - -/* - Setup multi-update handling and call SELECT to do the join -*/ - -bool mysql_multi_update(THD *thd, TABLE_LIST *table_list, List<Item> *fields, - List<Item> *values, COND *conds, ulonglong options, - enum enum_duplicates handle_duplicates, - bool ignore, SELECT_LEX_UNIT *unit, - SELECT_LEX *select_lex, multi_update **result) -{ - bool res; - DBUG_ENTER("mysql_multi_update"); - - if (!(*result= new (thd->mem_root) multi_update(thd, table_list, - &thd->lex->first_select_lex()->leaf_tables, - fields, values, handle_duplicates, ignore))) - { - DBUG_RETURN(TRUE); - } - - if ((*result)->init(thd)) - DBUG_RETURN(1); - - thd->abort_on_warning= !ignore && thd->is_strict_mode(); - List<Item> total_list; - - if (setup_tables(thd, &select_lex->context, &select_lex->top_join_list, - table_list, select_lex->leaf_tables, FALSE, FALSE)) - DBUG_RETURN(1); - - if (select_lex->vers_setup_conds(thd, table_list)) - DBUG_RETURN(1); - - res= mysql_select(thd, - table_list, total_list, conds, - select_lex->order_list.elements, - select_lex->order_list.first, NULL, NULL, NULL, - options | SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | - OPTION_SETUP_TABLES_DONE, - *result, unit, select_lex); - - DBUG_PRINT("info",("res: %d report_error: %d", res, (int) thd->is_error())); - res|= thd->is_error(); - if (unlikely(res)) - (*result)->abort_result_set(); - else - { - if (thd->lex->describe || thd->lex->analyze_stmt) - res= thd->lex->explain->send_explain(thd); - } - thd->abort_on_warning= 0; - DBUG_RETURN(res); -} - multi_update::multi_update(THD *thd_arg, TABLE_LIST *table_list, List<TABLE_LIST> *leaves_list, @@ -2029,6 +1690,19 @@ bool multi_update::init(THD *thd) } +bool multi_update::init_for_single_table(THD *thd) +{ + List_iterator_fast<TABLE_LIST> li(*leaves); + TABLE_LIST *tbl; + while ((tbl =li++)) + { + if (updated_leaves.push_back(tbl, thd->mem_root)) + return true; + } + return false; +} + + /* Connect fields with tables and create list of tables that are updated */ @@ -2102,7 +1776,8 @@ int multi_update::prepare(List<Item> ¬_used_values, { table->read_set= &table->def_read_set; bitmap_union(table->read_set, &table->tmp_set); - table->file->prepare_for_insert(1); + if (!(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_PREPARE)) + table->file->prepare_for_insert(1); } } if (unlikely(error)) @@ -3123,3 +2798,212 @@ bool multi_update::send_eof() } DBUG_RETURN(FALSE); } + + +bool Sql_cmd_update::precheck(THD *thd) +{ + if (!multitable) + { + if (update_precheck(thd, lex->query_tables)) + return true; + } + else + { + if (multi_update_precheck(thd, lex->query_tables)) + return true; + } + + WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE); + + return false; + +#ifdef WITH_WSREP +wsrep_error_label: +#endif + return true; +} + + +bool Sql_cmd_update::prepare_inner(THD *thd) +{ + JOIN *join; + int err= 0; + SELECT_LEX *const select_lex = thd->lex->first_select_lex(); + TABLE_LIST *const table_list = select_lex->get_table_list(); + ulonglong select_options= select_lex->options; + bool free_join= 1; + DBUG_ENTER("Sql_cmd_update::prepare_inner"); + + (void) read_statistics_for_tables_if_needed(thd, table_list); + + THD_STAGE_INFO(thd, stage_init_update); + + if (!multitable) + { + if (mysql_handle_derived(lex, DT_INIT)) + DBUG_RETURN(TRUE); + } + + if (table_list->has_period() && table_list->is_view_or_derived()) + { + my_error(ER_IT_IS_A_VIEW, MYF(0), table_list->table_name.str); + DBUG_RETURN(TRUE); + } + + if (!multitable) + { + TABLE_LIST *update_source_table= 0; + + if (((update_source_table=unique_table(thd, table_list, + table_list->next_global, 0)) || + table_list->is_multitable())) + { + DBUG_ASSERT(update_source_table || table_list->view != 0); + 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(TRUE); + } + multitable= true; + } + } + + if(!multitable) + { + if (table_list->is_view_or_derived() && + select_lex->leaf_tables.elements > 1) + multitable = true; + } + + if (!multitable) + { + if (lex->ignore) + lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_UPDATE_IGNORE); + } + + if (!(result= new (thd->mem_root) multi_update(thd, table_list, + &select_lex->leaf_tables, + &select_lex->item_list, + &lex->value_list, + lex->duplicates, + lex->ignore))) + { + DBUG_RETURN(TRUE); + } + + if (((multi_update *)result)->init(thd)) + DBUG_RETURN(TRUE); + + if (setup_tables(thd, &select_lex->context, &select_lex->top_join_list, + table_list, select_lex->leaf_tables, false, false)) + DBUG_RETURN(TRUE); + + if (select_lex->vers_setup_conds(thd, table_list)) + DBUG_RETURN(TRUE); + + { + if (thd->lex->describe) + select_options|= SELECT_DESCRIBE; + + /* + When in EXPLAIN, delay deleting the joins so that they are still + available when we're producing EXPLAIN EXTENDED warning text. + */ + if (select_options & SELECT_DESCRIBE) + free_join= 0; + + select_options|= + SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | OPTION_SETUP_TABLES_DONE; + + if (!(join= new (thd->mem_root) JOIN(thd, select_lex->item_list, + select_options, result))) + DBUG_RETURN(TRUE); + THD_STAGE_INFO(thd, stage_init); + select_lex->join= join; + thd->lex->used_tables=0; + select_lex->item_list_usage= MARK_COLUMNS_WRITE; + if ((err= join->prepare(table_list, select_lex->where, + select_lex->order_list.elements, + select_lex->order_list.first, + false, NULL, NULL, NULL, + select_lex, &lex->unit))) + { + goto err; + } + + } + + if (table_list->has_period()) + { + Item *item; + for (List_iterator_fast<Item> it(select_lex->item_list); (item=it++);) + { + Field *f= item->field_for_view_update()->field; + vers_select_conds_t &period= table_list->period_conditions; + if (period.field_start->field == f || period.field_end->field == f) + { + my_error(ER_PERIOD_COLUMNS_UPDATED, MYF(0), + item->name.str, period.name.str); + DBUG_RETURN(true); + } + } + + if (!table_list->period_conditions.start.item->const_item() + || !table_list->period_conditions.end.item->const_item()) + { + my_error(ER_NOT_CONSTANT_EXPRESSION, MYF(0), "FOR PORTION OF"); + DBUG_RETURN(true); + } + table_list->table->no_cache= true; + } + + + free_join= false; + +err: + + if (free_join) + { + THD_STAGE_INFO(thd, stage_end); + err|= (int)(select_lex->cleanup()); + DBUG_RETURN(err || thd->is_error()); + } + DBUG_RETURN(err); + +} + + +bool Sql_cmd_update::execute_inner(THD *thd) +{ + bool res= 0; + + thd->get_stmt_da()->reset_current_row_for_warning(1); + if (!multitable) + res= update_single_table(thd); + else + { + thd->abort_on_warning= !thd->lex->ignore && thd->is_strict_mode(); + res= Sql_cmd_dml::execute_inner(thd); + } + + res|= thd->is_error(); + if (multitable) + { + if (unlikely(res)) + result->abort_result_set(); + else + { + if (thd->lex->describe || thd->lex->analyze_stmt) + res= thd->lex->explain->send_explain(thd); + } + } + + if (result) + { + res= false; + delete result; + } + + return res; +} diff --git a/sql/sql_update.h b/sql/sql_update.h index 65e44d1..cf33461 100644 --- a/sql/sql_update.h +++ b/sql/sql_update.h @@ -17,6 +17,8 @@ #define SQL_UPDATE_INCLUDED #include "sql_class.h" /* enum_duplicates */ +#include "sql_cmd.h" // Sql_cmd_dml +#include "sql_base.h" class Item; struct TABLE_LIST; @@ -25,20 +27,46 @@ class THD; typedef class st_select_lex SELECT_LEX; typedef class st_select_lex_unit SELECT_LEX_UNIT; -bool mysql_prepare_update(THD *thd, TABLE_LIST *table_list, - Item **conds, uint order_num, ORDER *order); bool check_unique_table(THD *thd, TABLE_LIST *table_list); -int mysql_update(THD *thd,TABLE_LIST *tables,List<Item> &fields, - List<Item> &values,COND *conds, - uint order_num, ORDER *order, ha_rows limit, - bool ignore, ha_rows *found_return, ha_rows *updated_return); -bool mysql_multi_update(THD *thd, TABLE_LIST *table_list, - List<Item> *fields, List<Item> *values, - COND *conds, ulonglong options, - enum enum_duplicates handle_duplicates, bool ignore, - SELECT_LEX_UNIT *unit, SELECT_LEX *select_lex, - multi_update **result); bool records_are_comparable(const TABLE *table); bool compare_record(const TABLE *table); + +class Sql_cmd_update final : public Sql_cmd_dml +{ +public: + Sql_cmd_update(bool multitable_arg) + : multitable(multitable_arg) + { } + + enum_sql_command sql_command_code() const override + { + return multitable ? SQLCOM_UPDATE_MULTI : SQLCOM_UPDATE; + } + + DML_prelocking_strategy *get_dml_prelocking_strategy() + { + return &multiupdate_prelocking_strategy; + } + +protected: + bool precheck(THD *thd) override; + + bool prepare_inner(THD *thd) override; + + bool execute_inner(THD *thd) override; + +private: + bool update_single_table(THD *thd); + + bool multitable; + + DML_prelocking_strategy dml_prelocking_strategy; + Multiupdate_prelocking_strategy multiupdate_prelocking_strategy; + + public: + List<Item> *update_value_list; + +}; + #endif /* SQL_UPDATE_INCLUDED */ diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 5416cec..a587a37 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -69,6 +69,8 @@ #include "my_base.h" #include "sql_type_json.h" #include "json_table.h" +#include "sql_update.h" +#include "sql_delete.h" /* this is to get the bison compilation windows warnings out */ #ifdef _MSC_VER @@ -1682,7 +1684,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); opt_mi_check_type opt_to mi_check_types table_to_table_list table_to_table opt_table_list opt_as handler_rkey_function handler_read_or_scan - single_multi table_wild_list table_wild_one opt_wild + single_multi opt_wild opt_and select_var_list select_var_list_init help opt_extended_describe shutdown @@ -13242,9 +13244,14 @@ update: opt_low_priority opt_ignore update_table_list SET update_list { + bool is_multiupdate= false; + LEX *lex= Lex; SELECT_LEX *slex= Lex->first_select_lex(); if (slex->table_list.elements > 1) + { Lex->sql_command= SQLCOM_UPDATE_MULTI; + is_multiupdate= true; + } else if (slex->get_table_list()->derived) { /* it is single table update and it is update of derived table */ @@ -13252,10 +13259,13 @@ update: slex->get_table_list()->alias.str, "UPDATE"); MYSQL_YYABORT; } + if (!(lex->m_sql_cmd= + new (thd->mem_root) Sql_cmd_update(is_multiupdate))) + MYSQL_YYABORT; /* In case of multi-update setting write lock for all tables may - be too pessimistic. We will decrease lock level if possible in - mysql_multi_update(). + be too pessimistic. We will decrease lock level if possible + later while processing the statement. */ slex->set_lock_for_tables($3, slex->table_list.elements == 1, false); } @@ -13312,12 +13322,11 @@ delete: DELETE_SYM { LEX *lex= Lex; - lex->sql_command= SQLCOM_DELETE; YYPS->m_lock_type= TL_WRITE_DEFAULT; YYPS->m_mdl_type= MDL_SHARED_WRITE; if (Lex->main_select_push()) MYSQL_YYABORT; - mysql_init_select(lex); + mysql_init_delete(lex); lex->ignore= 0; lex->first_select_lex()->order_list.empty(); } @@ -13343,8 +13352,13 @@ delete_part2: opt_delete_options single_multi {} | HISTORY_SYM delete_single_table opt_delete_system_time { - Lex->last_table()->vers_conditions= Lex->vers_conditions; - Lex->pop_select(); //main select + LEX *lex= Lex; + lex->last_table()->vers_conditions= lex->vers_conditions; + lex->pop_select(); //main select + lex->sql_command= SQLCOM_DELETE; + if (!(lex->m_sql_cmd= + new (thd->mem_root) Sql_cmd_delete(false))) + MYSQL_YYABORT; } ; @@ -13378,12 +13392,22 @@ single_multi: delete_limit_clause opt_returning { + LEX *lex= Lex; if ($3) Select->order_list= *($3); - Lex->pop_select(); //main select + lex->pop_select(); //main select + lex->sql_command= SQLCOM_DELETE; + if (!(lex->m_sql_cmd= + new (thd->mem_root) Sql_cmd_delete(false))) + MYSQL_YYABORT; } - | table_wild_list + | table_alias_ref_list { + LEX *lex= Lex; + lex->sql_command= SQLCOM_DELETE_MULTI; + if (!(lex->m_sql_cmd= + new (thd->mem_root) Sql_cmd_delete(true))) + MYSQL_YYABORT; mysql_init_multi_delete(Lex); YYPS->m_lock_type= TL_READ_DEFAULT; YYPS->m_mdl_type= MDL_SHARED_READ; @@ -13395,6 +13419,11 @@ single_multi: } stmt_end {} | FROM table_alias_ref_list { + LEX *lex= Lex; + lex->sql_command= SQLCOM_DELETE_MULTI; + if (!(lex->m_sql_cmd= + new (thd->mem_root) Sql_cmd_delete(true))) + MYSQL_YYABORT; mysql_init_multi_delete(Lex); YYPS->m_lock_type= TL_READ_DEFAULT; YYPS->m_mdl_type= MDL_SHARED_READ; @@ -13430,44 +13459,6 @@ opt_returning: } ; -table_wild_list: - table_wild_one - | table_wild_list ',' table_wild_one - ; - -table_wild_one: - ident opt_wild - { - Table_ident *ti= new (thd->mem_root) Table_ident(&$1); - if (unlikely(ti == NULL)) - MYSQL_YYABORT; - if (unlikely(!Select-> - add_table_to_list(thd, - ti, - NULL, - (TL_OPTION_UPDATING | - TL_OPTION_ALIAS), - YYPS->m_lock_type, - YYPS->m_mdl_type))) - MYSQL_YYABORT; - } - | ident '.' ident opt_wild - { - Table_ident *ti= new (thd->mem_root) Table_ident(thd, &$1, &$3, 0); - if (unlikely(ti == NULL)) - MYSQL_YYABORT; - if (unlikely(!Select-> - add_table_to_list(thd, - ti, - NULL, - (TL_OPTION_UPDATING | - TL_OPTION_ALIAS), - YYPS->m_lock_type, - YYPS->m_mdl_type))) - MYSQL_YYABORT; - } - ; - opt_wild: /* empty */ {} | '.' '*' {} diff --git a/sql/table.h b/sql/table.h index 30517f8..5f8d299 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2338,6 +2338,7 @@ struct TABLE_LIST */ select_unit *derived_result; /* Stub used for materialized derived tables. */ + bool delete_while_scanning; table_map map; /* ID bit of table (1,2,4,8,16...) */ table_map get_map() { diff --git a/storage/spider/mysql-test/spider/r/error_row_number.result b/storage/spider/mysql-test/spider/r/error_row_number.result index cc2b548..ad095fe 100644 --- a/storage/spider/mysql-test/spider/r/error_row_number.result +++ b/storage/spider/mysql-test/spider/r/error_row_number.result @@ -29,7 +29,7 @@ ERROR 23000: Duplicate entry '13' for key 'PRIMARY' get diagnostics condition 1 @n = row_number; select @n; @n -0 +1 drop table spd; connection child2_1; drop database auto_test_remote;
participants (1)
-
IgorBabaev