Skip to content

Commit 9496916

Browse files
MDEV-37220 Allow UPDATE/DELETE to read from a CTE
We extend from the SQL standard to match the functionality of other databases that allow the inclusion of a CTE definition prior to update and delete statements. These CTEs are currently read only, like other derived tables, so cannot have their columns updated in updates set clause, nor have rows removed in the delete statement.
1 parent fd15fd2 commit 9496916

File tree

9 files changed

+1452
-16
lines changed

9 files changed

+1452
-16
lines changed

mysql-test/main/cte_update_delete.result

Lines changed: 982 additions & 0 deletions
Large diffs are not rendered by default.

mysql-test/main/cte_update_delete.test

Lines changed: 402 additions & 0 deletions
Large diffs are not rendered by default.

sql/sql_cte.cc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ bool LEX::resolve_references_to_cte(TABLE_LIST *tables,
153153
{
154154
With_element *with_elem= 0;
155155

156+
/*
157+
Add back the tables collected during definition of the CTEs, but cleared
158+
during mysql_init_delete
159+
*/
160+
if (save_list.elements)
161+
{
162+
for (TABLE_LIST *saved= save_list.first; saved; saved= saved->next_global)
163+
add_to_query_tables(saved);
164+
save_list.empty();
165+
last_table()->next_global= nullptr;
166+
}
167+
156168
for (TABLE_LIST *tbl= tables; tbl != *tables_last; tbl= tbl->next_global)
157169
{
158170
if (tbl->derived)
@@ -212,6 +224,19 @@ bool LEX::resolve_references_to_cte(TABLE_LIST *tables,
212224
tbl->schema_table= 0;
213225
if (tbl->derived)
214226
{
227+
#if 0 // can't do this here, we might not be updating this table
228+
/*
229+
Check this now as name resolution in prelocking_strategy::handle_end
230+
can cause odd and misleading error messages
231+
*/
232+
if ((thd->lex->sql_command == SQLCOM_UPDATE ||
233+
thd->lex->sql_command == SQLCOM_UPDATE_MULTI) &&
234+
thd->lex->first_select_lex()->get_table_list() == tbl)
235+
{
236+
my_error(ER_NON_UPDATABLE_TABLE, MYF(0), tbl->alias.str, "UPDATE");
237+
return true;
238+
}
239+
#endif
215240
tbl->derived->first_select()->set_linkage(DERIVED_TABLE_TYPE);
216241
tbl->select_lex->add_statistics(tbl->derived);
217242
}

sql/sql_lex.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3348,7 +3348,13 @@ struct LEX: public Query_tables_list
33483348
uint select_stack_outer_barrier;
33493349

33503350
SQL_I_List<ORDER> proc_list;
3351-
SQL_I_List<TABLE_LIST> auxiliary_table_list, save_list;
3351+
SQL_I_List<TABLE_LIST> auxiliary_table_list;
3352+
/*
3353+
save_list is used by
3354+
- Parsing CREATE TABLE t0 (...) UNION = (t1, t2, t3)
3355+
- CTEs for DELETE, see mysql_init_delete().
3356+
*/
3357+
SQL_I_List<TABLE_LIST> save_list;
33523358
Column_definition *last_field;
33533359
Table_function_json_table *json_table;
33543360
Item_sum *in_sum_func;

sql/sql_parse.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6070,6 +6070,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt)
60706070
thd->variables.default_master_connection.str)
60716071
thd->lex->mi.connection_name= null_clex_str;
60726072

6073+
lex->save_list.empty();
60736074
if (lex->sql_command != SQLCOM_SET_OPTION)
60746075
DEBUG_SYNC(thd, "end_of_statement");
60756076
DBUG_RETURN(res || thd->is_error());
@@ -7619,6 +7620,12 @@ void create_select_for_variable(THD *thd, LEX_CSTRING *var_name)
76197620

76207621
void mysql_init_delete(LEX *lex)
76217622
{
7623+
if (lex->with_cte_resolution)
7624+
{
7625+
// Save and clear lex->query_tables.
7626+
lex->save_list.insert(lex->query_tables, &lex->query_tables);
7627+
lex->query_tables_last= &lex->query_tables;
7628+
}
76227629
lex->init_select();
76237630
lex->first_select_lex()->limit_params.clear();
76247631
lex->unit.lim.clear();

sql/sql_select.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23812,7 +23812,8 @@ free_tmp_table(THD *thd, TABLE *entry)
2381223812

2381323813
if (entry->pos_in_table_list && entry->pos_in_table_list->table)
2381423814
{
23815-
DBUG_ASSERT(entry->pos_in_table_list->table == entry);
23815+
DBUG_ASSERT(entry->pos_in_table_list->table == entry ||
23816+
entry->pos_in_table_list->merged_for_insert);
2381623817
entry->pos_in_table_list->table= NULL;
2381723818
}
2381823819

sql/sql_update.cc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,7 +1886,7 @@ int multi_update::prepare(List<Item> &not_used_values,
18861886
{
18871887
TABLE_LIST *table_ref;
18881888
SQL_I_List<TABLE_LIST> update_list;
1889-
Item_field *item;
1889+
Item *item;
18901890
List_iterator_fast<Item> field_it(*fields);
18911891
List_iterator_fast<Item> value_it(*values);
18921892
uint i, max_fields;
@@ -1975,15 +1975,16 @@ int multi_update::prepare(List<Item> &not_used_values,
19751975

19761976
/* Split fields into fields_for_table[] and values_by_table[] */
19771977

1978-
while ((item= (Item_field *) field_it++))
1978+
while ((item= field_it++))
19791979
{
1980+
Item_field *item_field= (Item_field *)item->real_item();
19801981
Item *value= value_it++;
1981-
uint offset= item->field->table->pos_in_table_list->shared;
1982+
uint offset= item_field->field->table->pos_in_table_list->shared;
19821983

1983-
if (value->associate_with_target_field(thd, item))
1984+
if (value->associate_with_target_field(thd, item_field))
19841985
DBUG_RETURN(1);
19851986

1986-
fields_for_table[offset]->push_back(item, thd->mem_root);
1987+
fields_for_table[offset]->push_back(item_field, thd->mem_root);
19871988
values_for_table[offset]->push_back(value, thd->mem_root);
19881989
}
19891990
if (unlikely(thd->is_fatal_error))

sql/sql_yacc.yy

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,7 +1974,7 @@ rule:
19741974
'-' '+' '*' '/' '%' '(' ')'
19751975
',' '!' '{' '}' '&' '|'
19761976

1977-
%type <with_clause> with_clause
1977+
%type <with_clause> with_clause opt_with_clause
19781978

19791979
%type <with_element_head> with_element_head
19801980

@@ -14092,15 +14092,19 @@ update_table_list:
1409214092
/* Update rows in a table */
1409314093

1409414094
update:
14095+
opt_with_clause
1409514096
UPDATE_SYM opt_optimizer_hint
1409614097
{
1409714098
LEX *lex= Lex;
1409814099
if (Lex->main_select_push())
1409914100
MYSQL_YYABORT;
1410014101
lex->init_select();
14101-
Lex->first_select_lex()->set_optimizer_hints($2);
14102+
Lex->first_select_lex()->set_optimizer_hints($3);
1410214103
lex->sql_command= SQLCOM_UPDATE;
1410314104
lex->duplicates= DUP_ERROR;
14105+
Lex->first_select_lex()->master_unit()->set_with_clause($1);
14106+
if ($1)
14107+
$1->attach_to(Lex->first_select_lex());
1410414108
}
1410514109
opt_low_priority opt_ignore update_table_list
1410614110
SET update_list
@@ -14128,12 +14132,12 @@ update:
1412814132
be too pessimistic. We will decrease lock level if possible
1412914133
later while processing the statement.
1413014134
*/
14131-
slex->set_lock_for_tables($4, slex->table_list.elements == 1, false);
14135+
slex->set_lock_for_tables($5, slex->table_list.elements == 1, false);
1413214136
}
1413314137
opt_where_clause opt_order_clause delete_limit_clause
1413414138
{
14135-
if ($11)
14136-
Select->order_list= *($11);
14139+
if ($12)
14140+
Select->order_list= *($12);
1413714141
} stmt_end {}
1413814142
;
1413914143

@@ -14181,6 +14185,7 @@ opt_low_priority:
1418114185
/* Delete rows from a table */
1418214186

1418314187
delete:
14188+
opt_with_clause
1418414189
DELETE_SYM opt_optimizer_hint
1418514190
{
1418614191
LEX *lex= Lex;
@@ -14191,7 +14196,10 @@ delete:
1419114196
mysql_init_delete(lex);
1419214197
lex->ignore= 0;
1419314198
lex->first_select_lex()->order_list.empty();
14194-
lex->first_select_lex()->set_optimizer_hints($2);
14199+
lex->first_select_lex()->set_optimizer_hints($3);
14200+
lex->first_select_lex()->master_unit()->set_with_clause($1);
14201+
if ($1)
14202+
$1->attach_to(lex->first_select_lex());
1419514203
}
1419614204
delete_part2
1419714205
{
@@ -15895,6 +15903,11 @@ temporal_literal:
1589515903
}
1589615904
;
1589715905

15906+
opt_with_clause:
15907+
/* empty */ { $$= NULL; }
15908+
| with_clause { $$= $1; }
15909+
;
15910+
1589815911
with_clause:
1589915912
WITH opt_recursive
1590015913
{

sql/table.cc

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6190,16 +6190,15 @@ bool TABLE_LIST::create_field_translation(THD *thd)
61906190
It's needed because some items in the select list, like IN subselects,
61916191
might be substituted for optimized ones.
61926192
*/
6193-
if (is_view() && get_unit()->prepared && !field_translation_updated)
6193+
if (is_merged_derived() &&
6194+
get_unit()->prepared && !field_translation_updated)
61946195
{
61956196
field_translation_updated= TRUE;
61966197
if (static_cast<uint>(field_translation_end - field_translation) <
61976198
select->item_list.elements)
61986199
goto allocate;
61996200
while ((item= it++))
6200-
{
62016201
field_translation[field_count++].item= item;
6202-
}
62036202
}
62046203

62056204
DBUG_RETURN(FALSE);

0 commit comments

Comments
 (0)