From a2b871eada29ccccbd47d951ef33d9f56706fc72 Mon Sep 17 00:00:00 2001 From: Faizaan Madhani Date: Tue, 27 Sep 2022 17:04:18 -0400 Subject: [PATCH] sql: add support for `DELETE FROM ... USING` to parser Previously, the statement `DELETE FROM .. USING` would return an unimplemented error. This commit adds production rules in the parser to handle the `USING` clause in a `DELETE` statement, however usage will return an error as support has not been implemented in `optbuilder`. Release note: None --- docs/generated/sql/bnf/delete_stmt.bnf | 2 +- docs/generated/sql/bnf/stmt_block.bnf | 166 +++++++++++---------- pkg/sql/opt/optbuilder/delete.go | 2 +- pkg/sql/opt/optbuilder/mutation_builder.go | 12 +- pkg/sql/parser/sql.y | 14 +- pkg/sql/parser/testdata/delete | 50 +++++++ pkg/sql/sem/tree/delete.go | 5 + pkg/sql/sem/tree/pretty.go | 8 +- 8 files changed, 170 insertions(+), 89 deletions(-) diff --git a/docs/generated/sql/bnf/delete_stmt.bnf b/docs/generated/sql/bnf/delete_stmt.bnf index 24c383ee0c15..1fb9827344f9 100644 --- a/docs/generated/sql/bnf/delete_stmt.bnf +++ b/docs/generated/sql/bnf/delete_stmt.bnf @@ -1,2 +1,2 @@ delete_stmt ::= - ( ( 'WITH' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) | 'WITH' 'RECURSIVE' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) ) | ) 'DELETE' 'FROM' ( ( ( 'ONLY' | ) table_name opt_index_flags ( '*' | ) ) | ( ( 'ONLY' | ) table_name opt_index_flags ( '*' | ) ) table_alias_name | ( ( 'ONLY' | ) table_name opt_index_flags ( '*' | ) ) 'AS' table_alias_name ) ( ( 'WHERE' a_expr ) | ) ( sort_clause | ) ( limit_clause | ) ( 'RETURNING' target_list | 'RETURNING' 'NOTHING' | ) + ( ( 'WITH' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) | 'WITH' 'RECURSIVE' ( ( common_table_expr ) ( ( ',' common_table_expr ) )* ) ) | ) 'DELETE' 'FROM' ( ( ( 'ONLY' | ) table_name opt_index_flags ( '*' | ) ) | ( ( 'ONLY' | ) table_name opt_index_flags ( '*' | ) ) table_alias_name | ( ( 'ONLY' | ) table_name opt_index_flags ( '*' | ) ) 'AS' table_alias_name ) opt_using_clause ( ( 'WHERE' a_expr ) | ) ( sort_clause | ) ( limit_clause | ) ( 'RETURNING' target_list | 'RETURNING' 'NOTHING' | ) diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index 200677b447cc..af80317983e5 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -193,7 +193,7 @@ create_stmt ::= | create_external_connection_stmt delete_stmt ::= - opt_with_clause 'DELETE' 'FROM' table_expr_opt_alias_idx opt_where_clause opt_sort_clause opt_limit_clause returning_clause + opt_with_clause 'DELETE' 'FROM' table_expr_opt_alias_idx opt_using_clause opt_where_clause opt_sort_clause opt_limit_clause returning_clause drop_stmt ::= drop_ddl_stmt @@ -597,6 +597,10 @@ table_expr_opt_alias_idx ::= | table_name_opt_idx table_alias_name | table_name_opt_idx 'AS' table_alias_name +opt_using_clause ::= + 'USING' from_list + | + opt_sort_clause ::= sort_clause | @@ -1769,6 +1773,9 @@ with_clause ::= table_name_opt_idx ::= opt_only table_name opt_index_flags opt_descendant +from_list ::= + ( table_ref ) ( ( ',' table_ref ) )* + sort_clause ::= 'ORDER' 'BY' sortby_list @@ -1969,9 +1976,6 @@ set_clause ::= single_set_clause | multiple_set_clause -from_list ::= - ( table_ref ) ( ( ',' table_ref ) )* - simple_db_object_name ::= db_object_name_component @@ -2485,6 +2489,16 @@ opt_descendant ::= '*' | +table_ref ::= + relation_expr opt_index_flags opt_ordinality opt_alias_clause + | select_with_parens opt_ordinality opt_alias_clause + | 'LATERAL' select_with_parens opt_ordinality opt_alias_clause + | joined_table + | '(' joined_table ')' opt_ordinality alias_clause + | func_table opt_ordinality opt_func_alias_clause + | 'LATERAL' func_table opt_ordinality opt_alias_clause + | '[' row_source_extension_stmt ']' opt_ordinality opt_alias_clause + sortby_list ::= ( sortby ) ( ( ',' sortby ) )* @@ -2600,16 +2614,6 @@ single_set_clause ::= multiple_set_clause ::= '(' insert_column_list ')' '=' in_expr -table_ref ::= - relation_expr opt_index_flags opt_ordinality opt_alias_clause - | select_with_parens opt_ordinality opt_alias_clause - | 'LATERAL' select_with_parens opt_ordinality opt_alias_clause - | joined_table - | '(' joined_table ')' opt_ordinality alias_clause - | func_table opt_ordinality opt_func_alias_clause - | 'LATERAL' func_table opt_ordinality opt_alias_clause - | '[' row_source_extension_stmt ']' opt_ordinality opt_alias_clause - type_func_name_crdb_extra_keyword ::= 'FAMILY' @@ -3058,6 +3062,43 @@ common_table_expr ::= index_flags_param_list ::= ( index_flags_param ) ( ( ',' index_flags_param ) )* +opt_ordinality ::= + 'WITH' 'ORDINALITY' + | + +opt_alias_clause ::= + alias_clause + | + +joined_table ::= + '(' joined_table ')' + | table_ref 'CROSS' opt_join_hint 'JOIN' table_ref + | table_ref join_type opt_join_hint 'JOIN' table_ref join_qual + | table_ref 'JOIN' table_ref join_qual + | table_ref 'NATURAL' join_type opt_join_hint 'JOIN' table_ref + | table_ref 'NATURAL' 'JOIN' table_ref + +alias_clause ::= + 'AS' table_alias_name opt_col_def_list_no_types + | table_alias_name opt_col_def_list_no_types + +func_table ::= + func_expr_windowless + | 'ROWS' 'FROM' '(' rowsfrom_list ')' + +opt_func_alias_clause ::= + func_alias_clause + | + +row_source_extension_stmt ::= + delete_stmt + | explain_stmt + | insert_stmt + | select_stmt + | show_stmt + | update_stmt + | upsert_stmt + sortby ::= a_expr opt_asc_desc opt_nulls_order | 'PRIMARY' 'KEY' table_name opt_asc_desc @@ -3117,43 +3158,6 @@ var_list ::= schema_wildcard ::= wildcard_pattern -opt_ordinality ::= - 'WITH' 'ORDINALITY' - | - -opt_alias_clause ::= - alias_clause - | - -joined_table ::= - '(' joined_table ')' - | table_ref 'CROSS' opt_join_hint 'JOIN' table_ref - | table_ref join_type opt_join_hint 'JOIN' table_ref join_qual - | table_ref 'JOIN' table_ref join_qual - | table_ref 'NATURAL' join_type opt_join_hint 'JOIN' table_ref - | table_ref 'NATURAL' 'JOIN' table_ref - -alias_clause ::= - 'AS' table_alias_name opt_col_def_list_no_types - | table_alias_name opt_col_def_list_no_types - -func_table ::= - func_expr_windowless - | 'ROWS' 'FROM' '(' rowsfrom_list ')' - -opt_func_alias_clause ::= - func_alias_clause - | - -row_source_extension_stmt ::= - delete_stmt - | explain_stmt - | insert_stmt - | select_stmt - | show_stmt - | update_stmt - | upsert_stmt - type_func_name_no_crdb_extra_keyword ::= 'AUTHORIZATION' | 'COLLATION' @@ -3483,6 +3487,30 @@ index_flags_param ::= | 'FORCE_ZIGZAG' | 'FORCE_ZIGZAG' '=' index_name +opt_join_hint ::= + 'HASH' + | 'MERGE' + | 'LOOKUP' + | 'INVERTED' + | + +join_type ::= + 'FULL' join_outer + | 'LEFT' join_outer + | 'RIGHT' join_outer + | 'INNER' + +join_qual ::= + 'USING' '(' name_list ')' + | 'ON' a_expr + +rowsfrom_list ::= + ( rowsfrom_item ) ( ( ',' rowsfrom_item ) )* + +func_alias_clause ::= + 'AS' table_alias_name opt_col_def_list + | table_alias_name opt_col_def_list + opt_asc_desc ::= 'ASC' | 'DESC' @@ -3515,30 +3543,6 @@ opt_nowait_or_skip ::= wildcard_pattern ::= name '.' '*' -opt_join_hint ::= - 'HASH' - | 'MERGE' - | 'LOOKUP' - | 'INVERTED' - | - -join_type ::= - 'FULL' join_outer - | 'LEFT' join_outer - | 'RIGHT' join_outer - | 'INNER' - -join_qual ::= - 'USING' '(' name_list ')' - | 'ON' a_expr - -rowsfrom_list ::= - ( rowsfrom_item ) ( ( ',' rowsfrom_item ) )* - -func_alias_clause ::= - 'AS' table_alias_name opt_col_def_list - | table_alias_name opt_col_def_list - func_arg ::= func_arg_class param_name func_arg_type | param_name func_arg_class func_arg_type @@ -3752,12 +3756,6 @@ func_as ::= col_def_list_no_types ::= ( name ) ( ( ',' name ) )* -group_by_item ::= - a_expr - -window_definition ::= - window_name 'AS' window_specification - join_outer ::= 'OUTER' | @@ -3768,6 +3766,12 @@ rowsfrom_item ::= opt_col_def_list ::= '(' col_def_list ')' +group_by_item ::= + a_expr + +window_definition ::= + window_name 'AS' window_specification + func_arg_class ::= 'IN' diff --git a/pkg/sql/opt/optbuilder/delete.go b/pkg/sql/opt/optbuilder/delete.go index 26448035c5d9..16d9cbcb3f4d 100644 --- a/pkg/sql/opt/optbuilder/delete.go +++ b/pkg/sql/opt/optbuilder/delete.go @@ -62,7 +62,7 @@ func (b *Builder) buildDelete(del *tree.Delete, inScope *scope) (outScope *scope // ORDER BY LIMIT // // All columns from the delete table will be projected. - mb.buildInputForDelete(inScope, del.Table, del.Where, del.Limit, del.OrderBy) + mb.buildInputForDelete(inScope, del.Table, del.Where, del.Using, del.Limit, del.OrderBy) // Build the final delete statement, including any returned expressions. if resultsNeeded(del.Returning) { diff --git a/pkg/sql/opt/optbuilder/mutation_builder.go b/pkg/sql/opt/optbuilder/mutation_builder.go index df9312a9a341..5dba82f2f80e 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder.go +++ b/pkg/sql/opt/optbuilder/mutation_builder.go @@ -384,7 +384,12 @@ func (mb *mutationBuilder) buildInputForUpdate( // All columns from the table to update are added to fetchColList. // TODO(andyk): Do needed column analysis to project fewer columns if possible. func (mb *mutationBuilder) buildInputForDelete( - inScope *scope, texpr tree.TableExpr, where *tree.Where, limit *tree.Limit, orderBy tree.OrderBy, + inScope *scope, + texpr tree.TableExpr, + where *tree.Where, + using tree.TableExprs, + limit *tree.Limit, + orderBy tree.OrderBy, ) { var indexFlags *tree.IndexFlags if source, ok := texpr.(*tree.AliasedTableExpr); ok && source.IndexFlags != nil { @@ -425,6 +430,11 @@ func (mb *mutationBuilder) buildInputForDelete( mb.b.buildOrderBy(mb.outScope, projectionsScope, orderByScope) mb.b.constructProjectForScope(mb.outScope, projectionsScope) + // USING + if using != nil { + panic("DELETE USING is unimplemented so should not be used") + } + // LIMIT if limit != nil { mb.b.buildLimit(limit, inScope, projectionsScope) diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index 420ee4e38183..b4b8035c28c7 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -1375,7 +1375,7 @@ func (u *sqlSymUnion) functionObjs() tree.FuncObjs { %type <*tree.Limit> select_limit opt_select_limit %type relation_expr_list %type returning_clause -%type opt_using_clause +%type opt_using_clause %type opt_clear_data %type <[]tree.SequenceOption> sequence_option_list opt_sequence_option_list @@ -4824,6 +4824,7 @@ opt_changefeed_sink: // %Category: DML // %Text: DELETE FROM [WHERE ] // [ORDER BY ] +// [USING ] // [LIMIT ] // [RETURNING ] // %SeeAlso: WEBDOCS/delete.html @@ -4833,6 +4834,7 @@ delete_stmt: $$.val = &tree.Delete{ With: $1.with(), Table: $4.tblExpr(), + Using: $5.tblExprs(), Where: tree.NewWhere(tree.AstWhere, $6.expr()), OrderBy: $7.orderBy(), Limit: $8.limit(), @@ -4842,8 +4844,14 @@ delete_stmt: | opt_with_clause DELETE error // SHOW HELP: DELETE opt_using_clause: - USING from_list { return unimplementedWithIssueDetail(sqllex, 40963, "delete using") } -| /* EMPTY */ { } + USING from_list + { + $$.val = $2.tblExprs() + } +| /* EMPTY */ + { + $$.val = tree.TableExprs{} + } // %Help: DISCARD - reset the session to its initial state diff --git a/pkg/sql/parser/testdata/delete b/pkg/sql/parser/testdata/delete index 98576008e4fa..d715bff0057b 100644 --- a/pkg/sql/parser/testdata/delete +++ b/pkg/sql/parser/testdata/delete @@ -125,3 +125,53 @@ DELETE FROM a WHERE a = b -- normalized! DELETE FROM a WHERE ((a) = (b)) -- fully parenthesized DELETE FROM a WHERE a = b -- literals removed DELETE FROM _ WHERE _ = _ -- identifiers removed + +parse +DELETE FROM a USING b +---- +DELETE FROM a USING b +DELETE FROM a USING b -- fully parenthesized +DELETE FROM a USING b -- literals removed +DELETE FROM _ USING _ -- identifiers removed + +parse +DELETE FROM a USING b WHERE c = d +---- +DELETE FROM a USING b WHERE c = d +DELETE FROM a USING b WHERE ((c) = (d)) -- fully parenthesized +DELETE FROM a USING b WHERE c = d -- literals removed +DELETE FROM _ USING _ WHERE _ = _ -- identifiers removed + +parse +DELETE FROM a USING b WHERE c = d AND e = f +---- +DELETE FROM a USING b WHERE (c = d) AND (e = f) -- normalized! +DELETE FROM a USING b WHERE ((((c) = (d))) AND (((e) = (f)))) -- fully parenthesized +DELETE FROM a USING b WHERE (c = d) AND (e = f) -- literals removed +DELETE FROM _ USING _ WHERE (_ = _) AND (_ = _) -- identifiers removed + +parse +DELETE FROM a USING b, c WHERE d > e AND e < f +---- +DELETE FROM a USING b, c WHERE (d > e) AND (e < f) -- normalized! +DELETE FROM a USING b, c WHERE ((((d) > (e))) AND (((e) < (f)))) -- fully parenthesized +DELETE FROM a USING b, c WHERE (d > e) AND (e < f) -- literals removed +DELETE FROM _ USING _, _ WHERE (_ > _) AND (_ < _) -- identifiers removed + +parse +DELETE FROM a USING b, c, d AS other WHERE e = f AND g = h OR i = j +---- +DELETE FROM a USING b, c, d AS other WHERE ((e = f) AND (g = h)) OR (i = j) -- normalized! +DELETE FROM a USING b, c, d AS other WHERE ((((((e) = (f))) AND (((g) = (h))))) OR (((i) = (j)))) -- fully parenthesized +DELETE FROM a USING b, c, d AS other WHERE ((e = f) AND (g = h)) OR (i = j) -- literals removed +DELETE FROM _ USING _, _, _ AS _ WHERE ((_ = _) AND (_ = _)) OR (_ = _) -- identifiers removed + +parse +DELETE FROM a USING b AS one, c AS two, d AS three, e AS four WHERE f != g AND g = h RETURNING e +---- +DELETE FROM a USING b AS one, c AS two, d AS three, e AS four WHERE (f != g) AND (g = h) RETURNING e -- normalized! +DELETE FROM a USING b AS one, c AS two, d AS three, e AS four WHERE ((((f) != (g))) AND (((g) = (h)))) RETURNING (e) -- fully parenthesized +DELETE FROM a USING b AS one, c AS two, d AS three, e AS four WHERE (f != g) AND (g = h) RETURNING e -- literals removed +DELETE FROM _ USING _ AS _, _ AS _, _ AS _, _ AS _ WHERE (_ != _) AND (_ = _) RETURNING _ -- identifiers removed + + diff --git a/pkg/sql/sem/tree/delete.go b/pkg/sql/sem/tree/delete.go index 8f24d8edfe8b..998dba9d48b8 100644 --- a/pkg/sql/sem/tree/delete.go +++ b/pkg/sql/sem/tree/delete.go @@ -25,6 +25,7 @@ type Delete struct { Table TableExpr Where *Where OrderBy OrderBy + Using TableExprs Limit *Limit Returning ReturningClause } @@ -34,6 +35,10 @@ func (node *Delete) Format(ctx *FmtCtx) { ctx.FormatNode(node.With) ctx.WriteString("DELETE FROM ") ctx.FormatNode(node.Table) + if len(node.Using) > 0 { + ctx.WriteString(" USING ") + ctx.FormatNode(&node.Using) + } if node.Where != nil { ctx.WriteByte(' ') ctx.FormatNode(node.Where) diff --git a/pkg/sql/sem/tree/pretty.go b/pkg/sql/sem/tree/pretty.go index 49319434e24c..b56c73517a72 100644 --- a/pkg/sql/sem/tree/pretty.go +++ b/pkg/sql/sem/tree/pretty.go @@ -1161,10 +1161,14 @@ func (node *Update) doc(p *PrettyCfg) pretty.Doc { } func (node *Delete) doc(p *PrettyCfg) pretty.Doc { - items := make([]pretty.TableRow, 0, 6) + items := make([]pretty.TableRow, 0, 7) items = append(items, node.With.docRow(p), - p.row("DELETE FROM", p.Doc(node.Table)), + p.row("DELETE FROM", p.Doc(node.Table))) + if len(node.Using) > 0 { + items = append(items, p.row("USING", p.Doc(&node.Using))) + } + items = append(items, node.Where.docRow(p), node.OrderBy.docRow(p)) items = append(items, node.Limit.docTable(p)...)