summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Babaev <igor@askmonty.org>2022-06-18 16:28:48 -0700
committerIgor Babaev <igor@askmonty.org>2023-03-15 17:35:22 -0700
commit3a9358a4106a1bd0ae2414f6f0bda50afdd65f0a (patch)
tree694757fe8504081ad88a4e1fbe27d7934ee350bb
parent7ca89af6f8faf1f8ec6ede01a9353ac499d37711 (diff)
downloadmariadb-git-3a9358a4106a1bd0ae2414f6f0bda50afdd65f0a.tar.gz
MDEV-28883 Re-design the upper level of handling UPDATE and DELETE statements
This patch introduces a new way of handling UPDATE and DELETE commands at the top level after the parsing phase. This new way of processing update and delete statements can be seen in the implementation of the prepare() and execute() methods from the new Sql_cmd_dml class. This class derived from the Sql_cmd class can be considered as an interface class for processing such commands as SELECT, INSERT, UPDATE, DELETE and other comands manipulating data in tables. With this patch processing of update and delete statements after parsing proceeds by the following 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 The implementation of the method Sql_cmd_dml::execute() adheres this schema. The virtual functions of the class Sql_cmd_dml used for precheck of the access rights, context analysis, optimization and execution allow to adjust this schema for processing data manipulation statements of any types. This schema of processing data manipulation statements is taken from the current MySQL code. Moreover the definition the class Sql_cmd_dml introduced in this patch is almost a full replica of such class in the existing MySQL. However the implementation of the derived classes for update and delete statements is quite different. This implementation employs the JOIN class for all kinds of update and delete statements. It allows to perform main bulk of context analysis actions by the function JOIN::prepare(). This guarantees that characteristics and properties of the statement tree discovered for optimization phase when doing context analysis are the same for single-table and multi-table updates and deletes. With this patch the following functions are gone: mysql_prepare_update(), mysql_multi_update_prepare(), mysql_update(), mysql_multi_update(), mysql_prepare_delete(), mysql_multi_delete_prepare(), mysql_delete(). The code within these functions have been used as much as possible though. The functions mysql_test_update() and mysql_test_delete() are also not needed anymore. The method Sql_cmd_dml::prepare() serves processing - update/delete statement - PREPARE stmt FROM "<update/delete statement>" - EXECUTE stmt when stmt is prepared from update/delete statement. Approved by Oleksandr Byelkin <sanja@mariadb.com>
-rw-r--r--mysql-test/main/analyze_stmt_privileges2.result9
-rw-r--r--mysql-test/main/analyze_stmt_privileges2.test15
-rw-r--r--mysql-test/main/myisam_explain_non_select_all.result9
-rw-r--r--mysql-test/main/opt_trace.result14
-rw-r--r--mysql-test/main/opt_trace_security.test6
-rw-r--r--mysql-test/main/order_by.result8
-rw-r--r--mysql-test/main/update.result2
-rw-r--r--mysql-test/main/update_use_source.result1
-rw-r--r--mysql-test/main/update_use_source.test2
-rw-r--r--mysql-test/main/view_grant.result1
-rw-r--r--mysql-test/main/view_grant.test1
-rw-r--r--mysql-test/suite/funcs_1/r/is_collations.result2
-rw-r--r--mysql-test/suite/funcs_1/t/is_collations.test2
-rw-r--r--mysql-test/suite/period/r/update.result2
-rw-r--r--mysql-test/suite/period/t/update.test2
-rw-r--r--sql/ha_partition.cc6
-rw-r--r--sql/handler.h5
-rw-r--r--sql/opt_range.cc2
-rw-r--r--sql/opt_subselect.cc2
-rw-r--r--sql/opt_trace.cc3
-rw-r--r--sql/sql_base.cc34
-rw-r--r--sql/sql_base.h15
-rw-r--r--sql/sql_class.h3
-rw-r--r--sql/sql_cmd.h207
-rw-r--r--sql/sql_delete.cc629
-rw-r--r--sql/sql_delete.h69
-rw-r--r--sql/sql_lex.cc40
-rw-r--r--sql/sql_lex.h39
-rw-r--r--sql/sql_parse.cc261
-rw-r--r--sql/sql_parse.h1
-rw-r--r--sql/sql_prepare.cc247
-rw-r--r--sql/sql_select.cc271
-rw-r--r--sql/sql_update.cc673
-rw-r--r--sql/sql_update.h81
-rw-r--r--sql/sql_yacc.yy87
-rw-r--r--sql/table.h1
-rw-r--r--storage/spider/mysql-test/spider/r/error_row_number.result2
37 files changed, 1467 insertions, 1287 deletions
diff --git a/mysql-test/main/analyze_stmt_privileges2.result b/mysql-test/main/analyze_stmt_privileges2.result
index 3f09b1722b6..244fc5a739e 100644
--- a/mysql-test/main/analyze_stmt_privileges2.result
+++ b/mysql-test/main/analyze_stmt_privileges2.result
@@ -3016,6 +3016,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;
@@ -4743,6 +4744,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;
@@ -4841,12 +4843,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 a0f1f4908b6..8b011c2b592 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 c8c26d76a70..3470b3a9143 100644
--- a/mysql-test/main/myisam_explain_non_select_all.result
+++ b/mysql-test/main/myisam_explain_non_select_all.result
@@ -218,18 +218,17 @@ INSERT INTO t2 VALUES (1), (2), (3);
#
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);
@@ -2498,9 +2497,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/opt_trace.result b/mysql-test/main/opt_trace.result
index 41e2afbd43b..3a5aa6cda5f 100644
--- a/mysql-test/main/opt_trace.result
+++ b/mysql-test/main/opt_trace.result
@@ -4262,6 +4262,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": {
@@ -4293,7 +4303,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": {
@@ -4336,7 +4346,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.test b/mysql-test/main/opt_trace_security.test
index cf45a64d30b..9a4d281d656 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 96f5d9a49c3..a6d148679b6 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 f5edf1c6be3..15efd7e4362 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 d61bff04e0d..3f5274185ab 100644
--- a/mysql-test/main/update_use_source.result
+++ b/mysql-test/main/update_use_source.result
@@ -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 1b765138a18..3c32a2541b0 100644
--- a/mysql-test/main/update_use_source.test
+++ b/mysql-test/main/update_use_source.test
@@ -238,7 +238,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
+# 'order by 1' should be considered as in 'select * from v1 order 1'
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 1c720f215f8..cfb8f7df60e 100644
--- a/mysql-test/main/view_grant.result
+++ b/mysql-test/main/view_grant.result
@@ -676,6 +676,7 @@ ERROR 42000: UPDATE command denied to user 'readonly'@'localhost' for table `mys
UPDATE mysqltest1.v_ts SET x= 200;
ERROR 42000: UPDATE command denied to user 'readonly'@'localhost' for table `mysqltest1`.`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 9094c616a1f..a7990b44636 100644
--- a/mysql-test/main/view_grant.test
+++ b/mysql-test/main/view_grant.test
@@ -809,6 +809,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 979b477b8bc..6007742a2a1 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 db34a7b77b1..aa199b512bc 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 f726b4c07cf..004b9976f51 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 3f4dd2bdc68..fd67dc328c1 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 fdb4726a930..3799c79ffaa 100644
--- a/sql/ha_partition.cc
+++ b/sql/ha_partition.cc
@@ -4698,8 +4698,8 @@ exit:
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 &&
@@ -4812,7 +4812,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 0810d1a503a..464c4a32651 100644
--- a/sql/handler.h
+++ b/sql/handler.h
@@ -248,7 +248,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()
@@ -1807,7 +1807,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 02b5770aa8c..242ddb9af19 100644
--- a/sql/opt_range.cc
+++ b/sql/opt_range.cc
@@ -11928,7 +11928,7 @@ ha_rows check_quick_select(PARAM *param, uint idx, ha_rows limit,
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, limit, cost);
param->quick_rows[keynr]= rows;
diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc
index 1d0b1ff1874..e0d67ac9c96 100644
--- a/sql/opt_subselect.cc
+++ b/sql/opt_subselect.cc
@@ -699,6 +699,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 1008690ccaa..3f2433fd8b9 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 fa2b144e06f..4b253386a90 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -1106,7 +1106,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);
@@ -5698,6 +5702,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.
@@ -7841,6 +7867,9 @@ int setup_wild(THD *thd, TABLE_LIST *tables, List<Item> &fields,
if (!(*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
@@ -8111,7 +8140,7 @@ bool setup_table_attributes(THD *thd, TABLE_LIST *table_list,
uint &tablenr)
{
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)
{
@@ -8126,7 +8155,6 @@ bool setup_table_attributes(THD *thd, TABLE_LIST *table_list,
}
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 6e17d8214ad..f4a49d99125 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 @@ public:
};
+
+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 089b9ce5dbe..b54f35a4539 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -7133,6 +7133,7 @@ public:
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);
@@ -7141,6 +7142,8 @@ public:
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 2623b3703d3..985be776713 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 @@ public:
};
+class Prepared_statement;
+
/**
@class Sql_cmd - Representation of an SQL command.
@@ -180,10 +183,28 @@ public:
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 global context the processed statement
+ @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 global context the processed statement
+ @returns false if success, true if error
*/
virtual bool execute(THD *thd) = 0;
@@ -192,8 +213,40 @@ public:
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 global context of the processed statement
+
+ @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() = default;
+ Sql_cmd() : m_prepared(false), m_owner(nullptr)
+ {}
virtual ~Sql_cmd()
{
@@ -203,10 +256,152 @@ protected:
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 - 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 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 commands.
+*/
+class Sql_cmd_dml : public Sql_cmd
+{
+public:
+
+ /**
+ @brief Check whether the statement changes the contents of used tables
+ @return true if this is data change statement, false otherwise
+ */
+ virtual bool is_data_change_stmt() const { return true; }
+
+ /**
+ @brief Perform context analysis of the statement
+ @param thd global context the processed statement
+ @returns false on success, true on error
+ */
+ virtual bool prepare(THD *thd);
+
+ /**
+ Execute the processed statement once
+ @param thd global context the processed statement
+ @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)
+ {}
+
+ /**
+ @brief Check whether query is guaranteed to return no data
+ @return true if query is guaranteed to return no data, false otherwise
+
+ @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;
+ }
+
+ /**
+ @brief Set statement as returning no data
+ */
+ void set_empty_query() { m_empty_query = true; }
+
+ /**
+ @brief Perform precheck of table privileges for the specific command
+ @param thd global context the processed statement
+ @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
+ @param thd global context the processed statement
+ @returns false if success, true if error
+
+ @note
+ This function is called from prepare()
+ */
+ virtual bool prepare_inner(THD *thd) = 0;
+
+ /**
+ @brief Perform the command-specific actions of optimization and excution
+ @param thd global context the processed statement
+ @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 */
+};
+
+
class Sql_cmd_show_slave_status: public Sql_cmd
{
protected:
diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc
index 0c0f05aab07..0f926ab65b9 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;
@@ -295,123 +295,86 @@ int TABLE::delete_row()
/**
- Implement DELETE SQL word.
+ @brief Special handling of single-table deletes after prepare phase
- @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().
+ @param thd global context the processed statement
+ @returns false on success, true on error
*/
-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);
@@ -1017,90 +980,6 @@ got_error:
}
-/*
- 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
@@ -1113,106 +992,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),
@@ -1667,3 +1446,319 @@ 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;
+}
+
+
+/**
+ @brief Perform context analysis for delete statements
+
+ @param thd global context the processed statement
+ @returns false on success, true on error
+
+ @note
+ The main bulk of the context analysis actions for a delete statement
+ is performed by a call of JOIN::prepare().
+*/
+
+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 (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)
+ {
+ /*
+ 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 (!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);
+
+}
+
+
+/**
+ @brief Perform optimization and execution actions needed for deletes
+
+ @param thd global context the processed statement
+ @returns false on success, true on error
+*/
+
+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 520524c72cc..e1d5044ef43 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,66 @@ 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 - class used for any DELETE statements
+ This class is derived from Sql_cmd_dml and contains implementations
+ for abstract virtual function of the latter such as precheck() and
+ prepare_inner(). It also overrides the implementation of execute_inner()
+ providing a special handling for single-table delete statements that
+ are not converted to multi-table delete.
+ The class provides an object of the DML_prelocking_strategy class
+ for the virtual function get_dml_prelocking_strategy().
+*/
+class Sql_cmd_delete final : public Sql_cmd_dml
+{
+public:
+ Sql_cmd_delete(bool multitable_arg)
+ : multitable(multitable_arg), save_protocol(NULL) {}
+
+ 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:
+ /**
+ @brief Perform precheck of table privileges for delete statements
+ */
+ bool precheck(THD *thd) override;
+
+ /**
+ @brief Perform context analysis for delete statements
+ */
+ bool prepare_inner(THD *thd) override;
+
+ /**
+ @brief Perform optimization and execution actions needed for deletes
+ */
+ bool execute_inner(THD *thd) override;
+
+ private:
+ /**
+ @biefSpecial handling of single-table deletes after prepare phase
+ */
+ bool delete_from_single_table(THD *thd);
+
+ /*
+ True if the statement is a multitable delete or converted to such.
+ For a single-table delete this flag is set to true if the statement
+ is supposed to be converted to multi-table delete.
+ */
+ bool multitable;
+
+ /* The prelocking strategy used when opening the used tables */
+ DML_prelocking_strategy dml_prelocking_strategy;
+
+ List<Item> empty_list; /**< auxiliary empty list used by prepare_inner() */
+ Protocol *save_protocol; /**< needed for ANALYZE .. DELETE .. RETURNING */
+};
#endif /* SQL_DELETE_INCLUDED */
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index ab16a9d0b8b..60268e938f4 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -1303,6 +1303,8 @@ void LEX::start(THD *thd_arg)
wild= 0;
exchange= 0;
+ table_count= 0;
+
DBUG_VOID_RETURN;
}
@@ -3031,6 +3033,7 @@ void st_select_lex::init_select()
curr_tvc_name= 0;
versioned_tables= 0;
nest_flags= 0;
+ item_list_usage= MARK_COLUMNS_READ;
}
/*
@@ -3288,34 +3291,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
@@ -3537,7 +3512,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())
{
@@ -4083,6 +4058,13 @@ 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;
+ /* Fall through */
+
default:
return FALSE;
}
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index 2ddd40568f9..941fedd3580 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -37,6 +37,7 @@
#include "json_table.h" // Json_table_column
#include "sql_schema.h"
#include "table.h"
+#include "sql_class.h" // enum enum_column_usage
/* Used for flags of nesting constructs */
#define SELECT_NESTING_MAP_SIZE 64
@@ -875,6 +876,8 @@ public:
{
}
+ 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;
@@ -1007,6 +1010,7 @@ public:
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);
@@ -1112,6 +1116,7 @@ public:
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,
@@ -1222,6 +1227,7 @@ public:
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
@@ -1754,12 +1760,40 @@ public:
Sroutine_hash_entry **sroutines_list_own_last;
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
+ needs to be passed from open_tables() to lock_tables() which are
+ separated by some amount of code.
+ */
+ uint table_count;
+
/*
These constructor and destructor serve for creation/destruction
of Query_tables_list instances which are used as backup storage.
*/
- Query_tables_list() = default;
- ~Query_tables_list() = default;
+ Query_tables_list() : lock_tables_state(LTS_NOT_LOCKED) {}
+ ~Query_tables_list() {}
/* Initializes (or resets) Query_tables_list object for "real" use. */
void reset_query_tables_list(bool init);
@@ -3388,6 +3422,7 @@ public:
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 7a171174a72..67353a1f082 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,
@@ -3445,7 +3443,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();
@@ -3457,7 +3454,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
@@ -3579,7 +3575,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;
@@ -4388,130 +4383,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:
@@ -4773,129 +4653,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_update);
- 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:
{
@@ -7733,12 +7490,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;
lex->init_select();
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 eeb7f832adb..f6c2c086ae6 100644
--- a/sql/sql_parse.h
+++ b/sql/sql_parse.h
@@ -92,6 +92,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 1e84471eef2..22780c894f8 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
@@ -1402,162 +1400,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_update= 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);
-}
-
-
-/**
Validate SELECT statement.
In case of success, if this query is not EXPLAIN, send column list info
@@ -2137,74 +1979,6 @@ err:
}
-/*
- 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.
@@ -2486,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:
@@ -2634,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 +4139,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 26987c9072e..fdce2979f92 100644
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -1486,7 +1486,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;
@@ -1775,6 +1775,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;
@@ -30846,7 +30848,8 @@ static bool get_range_limit_read_cost(const POSITION *pos,
@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()).
*/
@@ -32341,6 +32344,270 @@ bool JOIN::transform_all_conds_and_on_exprs_in_join_list(
return false;
}
+
+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);
+ }
+}
+
+
+/*
+ @brief Perform actions needed before locking tables for a DML statement
+
+ @param thd global context the processed statement
+ @returns false if success, true if error
+
+ @details
+ This function calls the precheck() procedure fo the processed statement,
+ then is opens tables used in the statement and finally it calls the function
+ prepare_inner() that is specific for the type of the statement.
+
+ @note
+ The function are used when processing:
+ - a DML statement
+ - PREPARE stmt FROM <DML "statement>"
+ - EXECUTE stmt when stmt is prepared from a DML statement.
+*/
+
+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;
+}
+
+
+/**
+ @brief Execute a DML statement
+
+ @param thd global context the processed statement
+ @returns false if success, true if error
+
+ @details
+ The function assumes that each type of a DML statement has its own
+ implementation of the virtunal functions precheck(). It is also
+ assumed that that the virtual function execute execute_inner() is to be
+ overridden by the implementations for specific commands.
+
+ @note
+ Currently only UPDATE and DELETE statement are executed using this function.
+*/
+
+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())
+ {
+ /*
+ This is called when processing
+ - a DML statement
+ - PREPARE stmt FROM <DML "statement>"
+ - EXECUTE stmt when stmt is prepared from a DML statement.
+ The call will invoke open_tables_for_query()
+ */
+ if (prepare(thd))
+ goto err;
+ }
+ else // This branch currently is never used for DML commands
+ {
+ 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();
+}
+
+
+/**
+ @brief Generic implemention of optimization and execution phases
+ @param thd global context the processed statement
+ @returns false if success, true if error
+
+ @note
+ This implementation assumes that the processed DML statement is represented
+ as a SELECT_LEX or SELECT_LEX_UNIT tree with attached corresponding
+ JOIN structures. Any JOIN structure is constructed at the prepare phase.
+ When created at the top level join it is provided with an object of a class
+ derived from select_result_sink. The pointer to the object is saved in
+ the this->result field. For different types of DML statements different
+ derived classes are used for this object. The class of this object determines
+ additional specific actions performed at the phases of context analysis,
+ optimization and execution.
+*/
+
+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 4173d6d82ec..7781e0101d6 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,25 @@ 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
+/**
+ @brief Special handling of single-table updates after prepare phase
- RETURN
- 0 - OK
- 2 - privilege check and openning table passed, but we need to convert to
- multi-update because of view substitution
- 1 - error
+ @param thd global context the processed statement
+ @returns false on success, true on 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 +358,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_update= 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");
+ thd->table_map_for_update= 0;
- 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_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,89 +399,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 (table_list->table->check_assignability_explicit_fields(fields, values,
- ignore))
- DBUG_RETURN(true);
-
- 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;
@@ -782,9 +660,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;
@@ -1033,7 +911,7 @@ update_begin:
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 */
@@ -1366,9 +1244,9 @@ update_end:
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;
@@ -1400,75 +1278,6 @@ emit_explain_and_leave:
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
@@ -1696,15 +1505,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;
@@ -1734,7 +1534,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()).
@@ -1745,6 +1551,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))
@@ -1853,153 +1663,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_update;
- 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,
@@ -2035,6 +1698,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
*/
@@ -2110,7 +1786,8 @@ int multi_update::prepare(List<Item> &not_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))
@@ -3132,3 +2809,237 @@ bool multi_update::send_eof()
}
DBUG_RETURN(FALSE);
}
+
+
+/**
+ @brief Perform precheck of table privileges for update statements
+
+ @param thd global context the processed statement
+ @returns false on success, true on error
+*/
+
+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;
+}
+
+
+/**
+ @brief Perform context analysis for update statements
+
+ @param thd global context the processed statement
+ @returns false on success, true on error
+
+ @note
+ The main bulk of the context analysis actions for and update statement
+ is performed by a call of JOIN::prepare().
+*/
+
+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);
+
+}
+
+
+/**
+ @brief Perform optimization and execution actions needed for updates
+
+ @param thd global context the processed statement
+ @returns false on success, true on error
+*/
+
+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 65e44d112a4..d0fc7cb01e1 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,75 @@ 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 - class used for any UPDATE statements
+
+ This class is derived from Sql_cmd_dml and contains implementations
+ for abstract virtual function of the latter such as precheck() and
+ prepare_inner(). It also overrides the implementation of execute_inner()
+ providing a special handling for single-table update statements that
+ are not converted to multi-table updates.
+ The class provides an object of the Multiupdate_prelocking_strategy class
+ for the virtual function get_dml_prelocking_strategy().
+*/
+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:
+ /**
+ @brief Perform precheck of table privileges for update statements
+ */
+ bool precheck(THD *thd) override;
+
+ /**
+ @brief Perform context analysis for update statements
+ */
+ bool prepare_inner(THD *thd) override;
+
+ /**
+ @brief Perform optimization and execution actions needed for updates
+ */
+ bool execute_inner(THD *thd) override;
+
+private:
+
+ /**
+ @brief Special handling of single-table updates after prepare phase
+ */
+ bool update_single_table(THD *thd);
+
+ /*
+ True if the statement is a multi-table update or converted to such.
+ For a single-table update this flag is set to true if the statement
+ is supposed to be converted to multi-table update.
+ */
+ bool multitable;
+
+ /* The prelocking strategy used when opening the used tables */
+ Multiupdate_prelocking_strategy multiupdate_prelocking_strategy;
+
+ public:
+ /* The list of the updating expressions used in the set clause */
+ List<Item> *update_value_list;
+
+};
+
#endif /* SQL_UPDATE_INCLUDED */
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 2927b6ce8de..f70e5d895cf 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
@@ -1683,7 +1685,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
@@ -13262,9 +13264,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 */
@@ -13272,10 +13279,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);
}
@@ -13332,12 +13342,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;
- lex->init_select();
+ mysql_init_delete(lex);
lex->ignore= 0;
lex->first_select_lex()->order_list.empty();
}
@@ -13363,9 +13372,14 @@ 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
- if (Lex->check_main_unit_semantics())
+ 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;
+ if (lex->check_main_unit_semantics())
MYSQL_YYABORT;
}
;
@@ -13400,14 +13414,24 @@ 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;
if (Lex->check_main_unit_semantics())
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;
@@ -13419,6 +13443,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;
@@ -13468,44 +13497,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 d9c1231db6a..cad6720570d 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -2469,6 +2469,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 cc2b54878a0..ad095fe8ebd 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;