diff options
-rw-r--r-- | mysql-test/r/delete_returning.result | 235 | ||||
-rw-r--r-- | mysql-test/t/delete_returning.test | 189 | ||||
-rw-r--r-- | sql/lex.h | 1 | ||||
-rw-r--r-- | sql/sql_class.cc | 3 | ||||
-rw-r--r-- | sql/sql_delete.cc | 45 | ||||
-rw-r--r-- | sql/sql_delete.h | 7 | ||||
-rw-r--r-- | sql/sql_parse.cc | 11 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 5 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 7 |
9 files changed, 487 insertions, 16 deletions
diff --git a/mysql-test/r/delete_returning.result b/mysql-test/r/delete_returning.result new file mode 100644 index 00000000000..7cc551c23ed --- /dev/null +++ b/mysql-test/r/delete_returning.result @@ -0,0 +1,235 @@ +CREATE TABLE t1 (a int, b varchar(32)); +INSERT INTO t1 VALUES +(7,'ggggggg'), (1,'a'), (3,'ccc'), +(4,'dddd'), (1,'A'), (2,'BB'), (4,'DDDD'), +(5,'EEEEE'), (7,'GGGGGGG'), (2,'bb'); +CREATE TABLE t1c SELECT * FROM t1; +CREATE TABLE t2 (c int); +INSERT INTO t2 VALUES +(4), (5), (7), (1); +CREATE TABLE t2c SELECT * FROM t2; +CREATE VIEW v1 AS SELECT a, UPPER(b) FROM t1; +DELETE FROM t1 WHERE a=2 RETURNING * ; +a b +2 BB +2 bb +SELECT * FROM t1; +a b +7 ggggggg +1 a +3 ccc +4 dddd +1 A +4 DDDD +5 EEEEE +7 GGGGGGG +INSERT INTO t1 VALUES (2,'BB'), (2,'bb'); +DELETE FROM t1 WHERE a=2 RETURNING b; +b +bb +BB +SELECT * FROM t1; +a b +7 ggggggg +1 a +3 ccc +4 dddd +1 A +4 DDDD +5 EEEEE +7 GGGGGGG +DELETE FROM t1 WHERE a=2 RETURNING c; +ERROR 42S22: Unknown column 'c' in 'field list' +INSERT INTO t1 VALUES (2,'BB'), (2,'bb'); +DELETE FROM t1 WHERE a=2 RETURNING a, UPPER(b); +a UPPER(b) +2 BB +2 BB +SELECT * FROM t1; +a b +7 ggggggg +1 a +3 ccc +4 dddd +1 A +4 DDDD +5 EEEEE +7 GGGGGGG +INSERT INTO t1 VALUES (2,'BB'), (2,'bb'); +DELETE FROM t1 WHERE a=6 RETURNING b; +b +SELECT * FROM t1; +a b +7 ggggggg +1 a +3 ccc +4 dddd +1 A +2 bb +4 DDDD +5 EEEEE +7 GGGGGGG +2 BB +DELETE FROM t1 WHERE a=2 RETURNING MAX(b); +ERROR HY000: Invalid use of group function +DELETE FROM t1 WHERE a < 5 RETURNING a, (SELECT MIN(c) FROM t2 WHERE c=a+1); +a (SELECT MIN(c) FROM t2 WHERE c=a+1) +1 NULL +3 4 +4 5 +1 NULL +2 NULL +4 5 +2 NULL +SELECT * FROM t1; +a b +7 ggggggg +5 EEEEE +7 GGGGGGG +DELETE FROM t1; +INSERT INTO t1 SELECT * FROM t1c; +DELETE FROM t2 WHERE c < 5 +RETURNING (SELECT GROUP_CONCAT(b) FROM t1 GROUP BY a HAVING a=c); +(SELECT GROUP_CONCAT(b) FROM t1 GROUP BY a HAVING a=c) +dddd,DDDD +a,A +SELECT * FROM t2; +c +5 +7 +DELETE FROM t2; +INSERT INTO t2 SELECT * FROM t2c; +CREATE FUNCTION f(arg INT) RETURNS TEXT +BEGIN +RETURN (SELECT GROUP_CONCAT(b) FROM t1 WHERE a=arg); +END| +DELETE FROM t2 WHERE c < 5 RETURNING f(c); +f(c) +dddd,DDDD +a,A +SELECT * FROM t2; +c +5 +7 +DELETE FROM t2; +INSERT INTO t2 SELECT * FROM t2c; +DROP FUNCTION f; +DELETE FROM v1 WHERE a < 5 RETURNING * ; +a UPPER(b) +1 A +3 CCC +4 DDDD +1 A +2 BB +4 DDDD +2 BB +SELECT * FROM t1; +a b +7 ggggggg +5 EEEEE +7 GGGGGGG +DELETE FROM t1; +INSERT INTO t1 SELECT * FROM t1c; +CREATE VIEW v11(a,c) AS SELECT a, COUNT(b) FROM t1 GROUP BY a; +DELETE FROM v11 WHERE a < 5 RETURNING * ; +ERROR HY000: The target table v11 of the DELETE is not updatable +DROP VIEW v11; +PREPARE stmt FROM +"DELETE FROM t1 WHERE a=2 ORDER BY b LIMIT 1 RETURNING a, UPPER(b)"; +EXECUTE stmt; +a UPPER(b) +2 BB +SELECT * FROM t1; +a b +7 ggggggg +1 a +3 ccc +4 dddd +1 A +4 DDDD +5 EEEEE +7 GGGGGGG +2 bb +EXECUTE stmt; +a UPPER(b) +2 BB +SELECT * FROM t1; +a b +7 ggggggg +1 a +3 ccc +4 dddd +1 A +4 DDDD +5 EEEEE +7 GGGGGGG +DEALLOCATE PREPARE stmt; +DELETE FROM t1; +INSERT INTO t1 SELECT * FROM t1c; +FLUSH PRIVILEGES; +CREATE DATABASE mysqltest; +CREATE TABLE mysqltest.t1 SELECT * FROM t1; +GRANT DELETE ON mysqltest.* TO mysqltest_1@localhost; +GRANT SELECT(b) ON mysqltest.t1 TO mysqltest_1@localhost; +DELETE FROM mysqltest.t1 WHERE a=2 RETURNING b; +ERROR 42000: SELECT command denied to user 'mysqltest_1'@'localhost' for column 'a' in table 't1' +DELETE FROM mysqltest.t1 RETURNING b; +b +ggggggg +a +ccc +dddd +A +BB +DDDD +EEEEE +GGGGGGG +bb +SELECT * FROM mysqltest.t1; +a b +INSERT INTO mysqltest.t1 SELECT * FROM t1; +GRANT SELECT(a) ON mysqltest.t1 TO mysqltest_1@localhost; +DELETE FROM mysqltest.t1 WHERE a=2 RETURNING b; +b +bb +BB +SELECT * FROM mysqltest.t1; +a b +7 GGGGGGG +5 EEEEE +4 DDDD +1 A +4 dddd +3 ccc +1 a +7 ggggggg +INSERT INTO mysqltest.t1 SELECT * FROM t1; +CREATE VIEW mysqltest.v1(a) AS SELECT a FROM mysqltest.t1; +GRANT SELECT, INSERT ON mysqltest.t1 TO mysqltest_1@localhost; +DELETE FROM mysqltest.v1; +SELECT * FROM mysqltest.t1; +a b +INSERT INTO mysqltest.t1 SELECT * FROM t1; +DELETE FROM mysqltest.v1 RETURNING a; +ERROR 42000: SELECT command denied to user 'mysqltest_1'@'localhost' for column 'a' in table 'v1' +GRANT SELECT ON mysqltest.* TO mysqltest_1@localhost; +DELETE FROM mysqltest.v1 RETURNING a; +a +7 +1 +3 +4 +1 +2 +4 +5 +7 +2 +SELECT * FROM mysqltest.t1; +a b +INSERT INTO mysqltest.t1 SELECT * FROM t1; +DROP DATABASE mysqltest; +DROP USER mysqltest_1@localhost; +DROP VIEW v1; +DROP TABLE t1,t2; +DROP TABLE t1c,t2c; diff --git a/mysql-test/t/delete_returning.test b/mysql-test/t/delete_returning.test new file mode 100644 index 00000000000..53c3ee23a75 --- /dev/null +++ b/mysql-test/t/delete_returning.test @@ -0,0 +1,189 @@ +# +# Tests for DELETE FROM <table> ... RETURNING <expr>,... +# + +CREATE TABLE t1 (a int, b varchar(32)); +INSERT INTO t1 VALUES + (7,'ggggggg'), (1,'a'), (3,'ccc'), + (4,'dddd'), (1,'A'), (2,'BB'), (4,'DDDD'), + (5,'EEEEE'), (7,'GGGGGGG'), (2,'bb'); + +CREATE TABLE t1c SELECT * FROM t1; + +CREATE TABLE t2 (c int); +INSERT INTO t2 VALUES + (4), (5), (7), (1); + +CREATE TABLE t2c SELECT * FROM t2; + +CREATE VIEW v1 AS SELECT a, UPPER(b) FROM t1; + +# DELETE FROM <table> ... RETURNING * + +DELETE FROM t1 WHERE a=2 RETURNING * ; +SELECT * FROM t1; + +INSERT INTO t1 VALUES (2,'BB'), (2,'bb'); + +# DELETE FROM <table> ... RETURNING <col> + +DELETE FROM t1 WHERE a=2 RETURNING b; +SELECT * FROM t1; + +# DELETE FROM <table> ... RETURNING <not existing col> +--error ER_BAD_FIELD_ERROR +DELETE FROM t1 WHERE a=2 RETURNING c; + +INSERT INTO t1 VALUES (2,'BB'), (2,'bb'); + +# DELETE FROM <table> ... RETURNING <col>, <expr> + +DELETE FROM t1 WHERE a=2 RETURNING a, UPPER(b); +SELECT * FROM t1; + +INSERT INTO t1 VALUES (2,'BB'), (2,'bb'); + +# DELETE FROM <table> ... RETURNING <col> with no rows to be deleted + +DELETE FROM t1 WHERE a=6 RETURNING b; +SELECT * FROM t1; + +# DELETE FROM <table> ... RETURNING <expr with aggr function> + +--error ER_INVALID_GROUP_FUNC_USE +DELETE FROM t1 WHERE a=2 RETURNING MAX(b); + +# DELETE FROM <table> ... RETURNING <expr with subquery> + +DELETE FROM t1 WHERE a < 5 RETURNING a, (SELECT MIN(c) FROM t2 WHERE c=a+1); +SELECT * FROM t1; + +DELETE FROM t1; +INSERT INTO t1 SELECT * FROM t1c; + +DELETE FROM t2 WHERE c < 5 + RETURNING (SELECT GROUP_CONCAT(b) FROM t1 GROUP BY a HAVING a=c); +SELECT * FROM t2; + +DELETE FROM t2; +INSERT INTO t2 SELECT * FROM t2c; + +# DELETE FROM <table> ... RETURNING <expr with function invocation> + +DELIMITER |; + +CREATE FUNCTION f(arg INT) RETURNS TEXT +BEGIN + RETURN (SELECT GROUP_CONCAT(b) FROM t1 WHERE a=arg); +END| + +DELIMITER ;| + +DELETE FROM t2 WHERE c < 5 RETURNING f(c); +SELECT * FROM t2; + +DELETE FROM t2; +INSERT INTO t2 SELECT * FROM t2c; + +DROP FUNCTION f; + +# DELETE FROM <view> ... RETURNING <col>, <col> + +DELETE FROM v1 WHERE a < 5 RETURNING * ; +SELECT * FROM t1; + +DELETE FROM t1; +INSERT INTO t1 SELECT * FROM t1c; + +# DELETE FROM <view> ... RETURNING <expr> + +CREATE VIEW v11(a,c) AS SELECT a, COUNT(b) FROM t1 GROUP BY a; +-- error ER_NON_UPDATABLE_TABLE +DELETE FROM v11 WHERE a < 5 RETURNING * ; +DROP VIEW v11; + +# prepared DELETE FROM <table> ... RETURNING <expr> + +PREPARE stmt FROM +"DELETE FROM t1 WHERE a=2 ORDER BY b LIMIT 1 RETURNING a, UPPER(b)"; +EXECUTE stmt; +SELECT * FROM t1; +EXECUTE stmt; +SELECT * FROM t1; +DEALLOCATE PREPARE stmt; + +DELETE FROM t1; +INSERT INTO t1 SELECT * FROM t1c; + +# DELETE FROM <table> ... RETURNING ... with checking privileges + +FLUSH PRIVILEGES; + +connect (root,localhost,root,,test); +connection root; +--disable_warnings +CREATE DATABASE mysqltest; +--enable_warnings + +CREATE TABLE mysqltest.t1 SELECT * FROM t1; +GRANT DELETE ON mysqltest.* TO mysqltest_1@localhost; + +GRANT SELECT(b) ON mysqltest.t1 TO mysqltest_1@localhost; + +connect (user1,localhost,mysqltest_1,,test); + +connection user1; + +--error ER_COLUMNACCESS_DENIED_ERROR +DELETE FROM mysqltest.t1 WHERE a=2 RETURNING b; + +DELETE FROM mysqltest.t1 RETURNING b; + +connection root; +SELECT * FROM mysqltest.t1; +INSERT INTO mysqltest.t1 SELECT * FROM t1; + +GRANT SELECT(a) ON mysqltest.t1 TO mysqltest_1@localhost; + +DELETE FROM mysqltest.t1 WHERE a=2 RETURNING b; + +SELECT * FROM mysqltest.t1; +INSERT INTO mysqltest.t1 SELECT * FROM t1; + +connection root; +CREATE VIEW mysqltest.v1(a) AS SELECT a FROM mysqltest.t1; +GRANT SELECT, INSERT ON mysqltest.t1 TO mysqltest_1@localhost; + +connection user1; + +DELETE FROM mysqltest.v1; +SELECT * FROM mysqltest.t1; + +INSERT INTO mysqltest.t1 SELECT * FROM t1; + +--error ER_COLUMNACCESS_DENIED_ERROR +DELETE FROM mysqltest.v1 RETURNING a; + +connection root; +GRANT SELECT ON mysqltest.* TO mysqltest_1@localhost; + +connection user1; +DELETE FROM mysqltest.v1 RETURNING a; +SELECT * FROM mysqltest.t1; + +INSERT INTO mysqltest.t1 SELECT * FROM t1; + +connection root; +--disable_warnings +DROP DATABASE mysqltest; +--enable_warnings + +disconnect user1; +DROP USER mysqltest_1@localhost; + +# Cleanup +DROP VIEW v1; +DROP TABLE t1,t2; +DROP TABLE t1c,t2c; + + diff --git a/sql/lex.h b/sql/lex.h index e269c3e4040..a4bbf74e715 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -483,6 +483,7 @@ static SYMBOL symbols[] = { { "RESTRICT", SYM(RESTRICT)}, { "RESUME", SYM(RESUME_SYM)}, { "RETURN", SYM(RETURN_SYM)}, + { "RETURNING", SYM(RETURNING_SYM)}, { "RETURNS", SYM(RETURNS_SYM)}, { "REVOKE", SYM(REVOKE)}, { "RIGHT", SYM(RIGHT)}, diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 75c422ecbf0..08e8fbe7361 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2344,7 +2344,8 @@ int select_send::send_data(List<Item> &items) Protocol *protocol= thd->protocol; DBUG_ENTER("select_send::send_data"); - if (unit->offset_limit_cnt) + /* unit is not set when using 'delete ... returning' */ + if (unit && unit->offset_limit_cnt) { // using limit offset,count unit->offset_limit_cnt--; DBUG_RETURN(FALSE); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 32215e91225..62aa20e90f4 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -49,7 +49,8 @@ */ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, - SQL_I_List<ORDER> *order_list, ha_rows limit, ulonglong options) + SQL_I_List<ORDER> *order_list, ha_rows limit, + ulonglong options, select_result *result) { bool will_batch; int error, loc_error; @@ -67,6 +68,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SELECT_LEX *select_lex= &thd->lex->select_lex; killed_state killed_status= NOT_KILLED; THD::enum_binlog_query_type query_type= THD::ROW_QUERY_TYPE; + bool with_select= !select_lex->item_list.is_empty(); DBUG_ENTER("mysql_delete"); if (open_and_lock_tables(thd, table_list, TRUE, 0)) @@ -91,9 +93,12 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, thd_proc_info(thd, "init"); table->map=1; - if (mysql_prepare_delete(thd, table_list, &conds)) + if (mysql_prepare_delete(thd, table_list, select_lex->with_wild, + select_lex->item_list, &conds)) DBUG_RETURN(TRUE); + (void) result->prepare(select_lex->item_list, NULL); + if (thd->lex->current_select->first_cond_optimization) { thd->lex->current_select->save_leaf_tables(thd); @@ -155,9 +160,9 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, - We should not be binlogging this statement in row-based, and - there should be no delete triggers associated with the table. */ - if (!using_limit && const_cond_result && - (!thd->is_current_stmt_binlog_format_row() && - !(table->triggers && table->triggers->has_delete_triggers()))) + if (!with_select && !using_limit && const_cond_result && + (!thd->is_current_stmt_binlog_format_row() && + !(table->triggers && table->triggers->has_delete_triggers()))) { /* Update the table->file->stats.records number */ table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); @@ -326,9 +331,16 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, else will_batch= !table->file->start_bulk_delete(); - table->mark_columns_needed_for_delete(); + if (with_select) + { + if (result->send_result_set_metadata(select_lex->item_list, + Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + goto cleanup; + } + while (!(error=info.read_record(&info)) && !thd->killed && ! thd->is_error()) { @@ -348,6 +360,12 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, break; } + if (with_select && result->send_data(select_lex->item_list) < 0) + { + error=1; + break; + } + if (!(error= table->file->ha_delete_row(table->record[0]))) { deleted++; @@ -454,7 +472,10 @@ cleanup: if (error < 0 || (thd->lex->ignore && !thd->is_error() && !thd->is_fatal_error)) { - my_ok(thd, deleted); + if (!with_select) + my_ok(thd, deleted); + else + result->send_eof(); DBUG_PRINT("info",("%ld records deleted",(long) deleted)); } DBUG_RETURN(error >= 0 || thd->is_error()); @@ -468,13 +489,16 @@ cleanup: mysql_prepare_delete() thd - thread handler table_list - global/local table list + wild_num - number of wildcards used in optional SELECT clause + field_list - list of items in optional SELECT clause conds - conditions RETURN VALUE FALSE OK TRUE error */ -int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds) + int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, + uint wild_num, List<Item> &field_list, Item **conds) { Item *fake_conds= 0; SELECT_LEX *select_lex= &thd->lex->select_lex; @@ -486,7 +510,10 @@ int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, Item **conds) &thd->lex->select_lex.top_join_list, table_list, select_lex->leaf_tables, FALSE, - DELETE_ACL, SELECT_ACL, TRUE) || + DELETE_ACL, SELECT_ACL, TRUE)) + DBUG_RETURN(TRUE); + if ((wild_num && setup_wild(thd, table_list, field_list, NULL, wild_num)) || + setup_fields(thd, NULL, field_list, MARK_COLUMNS_READ, NULL, 0) || setup_conds(thd, table_list, select_lex->leaf_tables, conds) || setup_ftfuncs(select_lex)) DBUG_RETURN(TRUE); diff --git a/sql/sql_delete.h b/sql/sql_delete.h index 6147e0ea367..9cd09dc5722 100644 --- a/sql/sql_delete.h +++ b/sql/sql_delete.h @@ -21,12 +21,15 @@ class THD; struct TABLE_LIST; class Item; +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); +int mysql_prepare_delete(THD *thd, TABLE_LIST *table_list, + uint wild_num, List<Item> &field_list, Item **conds); bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, - SQL_I_List<ORDER> *order, ha_rows rows, ulonglong options); + SQL_I_List<ORDER> *order, ha_rows rows, + ulonglong options, select_result *result); #endif /* SQL_DELETE_INCLUDED */ diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 97bc98a5b67..5a4f3ad0907 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3243,6 +3243,7 @@ end_with_restore_list: } case SQLCOM_DELETE: { + select_result *sel_result=lex->result; DBUG_ASSERT(first_table == all_tables && first_table != 0); if ((res= delete_precheck(thd, all_tables))) break; @@ -3250,9 +3251,13 @@ end_with_restore_list: unit->set_limit(select_lex); MYSQL_DELETE_START(thd->query()); - res = mysql_delete(thd, all_tables, select_lex->where, - &select_lex->order_list, - unit->select_limit_cnt, select_lex->options); + if (!(sel_result= lex->result) && !(sel_result= new select_send())) + return 1; + res = mysql_delete(thd, all_tables, + select_lex->where, &select_lex->order_list, + unit->select_limit_cnt, select_lex->options, + sel_result); + delete sel_result; MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func()); break; } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 331a84c5955..e8965842140 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1459,7 +1459,10 @@ static bool mysql_test_delete(Prepared_statement *stmt, goto error; } - DBUG_RETURN(mysql_prepare_delete(thd, table_list, &lex->select_lex.where)); + DBUG_RETURN(mysql_prepare_delete(thd, table_list, + lex->select_lex.with_wild, + lex->select_lex.item_list, + &lex->select_lex.where)); error: DBUG_RETURN(TRUE); } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index f3abc872587..a10612e7266 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1261,6 +1261,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token RESTORE_SYM %token RESTRICT %token RESUME_SYM +%token RETURNING_SYM %token RETURNS_SYM /* SQL-2003-R */ %token RETURN_SYM /* SQL-2003-R */ %token REVOKE /* SQL-2003-R */ @@ -11428,6 +11429,7 @@ single_multi: } where_clause opt_order_clause delete_limit_clause {} + opt_select_expressions {} | table_wild_list { mysql_init_multi_delete(Lex); @@ -11452,6 +11454,11 @@ single_multi: } ; +opt_select_expressions: + /* empty */ + | RETURNING_SYM select_item_list + ; + table_wild_list: table_wild_one | table_wild_list ',' table_wild_one |