From c19c5b2c21ed3ac3a4e0d6033cf9cffaa2be741f Mon Sep 17 00:00:00 2001 From: Joseph Young <80432516+jpy-git@users.noreply.github.com> Date: Sun, 6 Mar 2022 08:52:24 +0000 Subject: [PATCH] Postgres: Complete DELETE FROM grammar (#2791) * Postgres: Complete DELETE FROM grammar * Add DELETE statement to WITH grammar * Add WITH RECURSIVE example Co-authored-by: Barry Pollard --- src/sqlfluff/dialects/dialect_ansi.py | 1 + src/sqlfluff/dialects/dialect_postgres.py | 63 +-- .../dialects/postgres/postgres_delete.sql | 41 +- .../dialects/postgres/postgres_delete.yml | 415 +++++++++++++++--- 4 files changed, 411 insertions(+), 109 deletions(-) diff --git a/src/sqlfluff/dialects/dialect_ansi.py b/src/sqlfluff/dialects/dialect_ansi.py index 0ca6abc917d..03737e02347 100644 --- a/src/sqlfluff/dialects/dialect_ansi.py +++ b/src/sqlfluff/dialects/dialect_ansi.py @@ -2159,6 +2159,7 @@ class SelectStatementSegment(BaseSegment): NonWithNonSelectableGrammar=OneOf( Ref("UpdateStatementSegment"), Ref("InsertStatementSegment"), + Ref("DeleteStatementSegment"), ), # Things that behave like select statements, which can form part of set expressions. NonSetSelectableGrammar=OneOf( diff --git a/src/sqlfluff/dialects/dialect_postgres.py b/src/sqlfluff/dialects/dialect_postgres.py index 16580463a1a..d51a0f3d504 100644 --- a/src/sqlfluff/dialects/dialect_postgres.py +++ b/src/sqlfluff/dialects/dialect_postgres.py @@ -3671,40 +3671,6 @@ class ValuesClauseSegment(BaseSegment): ) -@postgres_dialect.segment() -class DeleteUsingClauseSegment(BaseSegment): - """USING clause.""" - - type = "using_clause" - match_grammar = StartsWith( - "USING", - terminator="WHERE", - enforce_whitespace_preceding_terminator=True, - ) - - parse_grammar = Sequence( - "USING", - Indent, - Delimited( - Ref("TableExpressionSegment"), - ), - Dedent, - ) - - -@postgres_dialect.segment() -class FromClauseTerminatingUsingWhereSegment( - ansi_dialect.get_segment("FromClauseSegment") # type: ignore -): - """Copy `FROM` terminator statement to support `USING` in specific circumstances.""" - - match_grammar = StartsWith( - "FROM", - terminator=OneOf(Ref.keyword("USING"), Ref.keyword("WHERE")), - enforce_whitespace_preceding_terminator=True, - ) - - @postgres_dialect.segment(replace=True) class DeleteStatementSegment(BaseSegment): """A `DELETE` statement. @@ -3713,14 +3679,26 @@ class DeleteStatementSegment(BaseSegment): """ type = "delete_statement" - # TODO Implement WITH RECURSIVE match_grammar = StartsWith("DELETE") parse_grammar = Sequence( "DELETE", + "FROM", Ref.keyword("ONLY", optional=True), - Ref("FromClauseTerminatingUsingWhereSegment"), - # TODO Implement Star and As Alias - Ref("DeleteUsingClauseSegment", optional=True), + Ref("TableReferenceSegment"), + Ref("StarSegment", optional=True), + Ref("AsAliasExpressionSegment", optional=True), + Sequence( + "USING", + Indent, + Delimited( + Sequence( + Ref("TableExpressionSegment"), + Ref("AsAliasExpressionSegment", optional=True), + ), + ), + Dedent, + optional=True, + ), OneOf( Sequence("WHERE", "CURRENT", "OF", Ref("ObjectReferenceSegment")), Ref("WhereClauseSegment"), @@ -3730,11 +3708,12 @@ class DeleteStatementSegment(BaseSegment): "RETURNING", OneOf( Ref("StarSegment"), - Sequence( - Ref("ExpressionSegment"), - Ref("AliasSegment", optional=True), + Delimited( + Sequence( + Ref("ExpressionSegment"), + Ref("AsAliasExpressionSegment", optional=True), + ), ), - optional=True, ), optional=True, ), diff --git a/test/fixtures/dialects/postgres/postgres_delete.sql b/test/fixtures/dialects/postgres/postgres_delete.sql index a9b2004bd63..0ef08f56a5b 100644 --- a/test/fixtures/dialects/postgres/postgres_delete.sql +++ b/test/fixtures/dialects/postgres/postgres_delete.sql @@ -1,12 +1,41 @@ -DELETE FROM films WHERE kind <> 'Musical'; - DELETE FROM films; -DELETE FROM tasks WHERE status = 'DONE' RETURNING *; +DELETE FROM ONLY films; + +DELETE FROM films *; + +DELETE FROM films AS f; + +DELETE FROM films USING producers + WHERE producer_id = producers.id AND producers.name = 'foo'; + +DELETE FROM films AS f USING producers AS p + WHERE f.producer_id = p.id AND p.name = 'foo'; + +DELETE FROM films AS f USING producers AS p, actors AS a + WHERE f.producer_id = p.id AND p.name = 'foo' + AND f.actor_id = a.id AND a.name = 'joe cool'; DELETE FROM tasks WHERE CURRENT OF c_tasks; -DELETE FROM some_table -USING other_table -WHERE other_table.col = some_table.col +DELETE FROM films WHERE kind <> 'Musical'; + +DELETE FROM tasks WHERE status = 'DONE' RETURNING *; + +DELETE FROM tasks WHERE status = 'DONE' RETURNING actor_id; + +DELETE FROM tasks WHERE status = 'DONE' RETURNING actor_id as a_id; + +DELETE FROM tasks WHERE status = 'DONE' RETURNING actor_id, producer_id; +DELETE FROM tasks WHERE status = 'DONE' RETURNING actor_id as a_id, producer_id as p_id; + +WITH test as (select foo from bar) +DELETE FROM films; + +WITH RECURSIVE t(n) AS ( + VALUES (1) + UNION ALL + SELECT n+1 FROM t WHERE n < 100 +) +DELETE FROM films; diff --git a/test/fixtures/dialects/postgres/postgres_delete.yml b/test/fixtures/dialects/postgres/postgres_delete.yml index c5ed948b3db..2a34fc32fdf 100644 --- a/test/fixtures/dialects/postgres/postgres_delete.yml +++ b/test/fixtures/dialects/postgres/postgres_delete.yml @@ -3,19 +3,192 @@ # computed by SQLFluff when running the tests. Please run # `python test/generate_parse_fixture_yml.py` to generate them after adding or # altering SQL files. -_hash: 7fb777ecc5b4fc3836714dd400198a54b212075c86a388190bf8d05c6c4ac6f4 +_hash: 6f33596d65a52bb5afc936ca0a834076f307b646e39228d695d9f86738df69dd file: - statement: delete_statement: - keyword: DELETE - from_clause: - keyword: FROM - from_expression: - from_expression_element: - table_expression: - table_reference: - identifier: films - where_clause: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: films +- statement_terminator: ; +- statement: + delete_statement: + - keyword: DELETE + - keyword: FROM + - keyword: ONLY + - table_reference: + identifier: films +- statement_terminator: ; +- statement: + delete_statement: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: films + - star: '*' +- statement_terminator: ; +- statement: + delete_statement: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: films + - alias_expression: + keyword: AS + identifier: f +- statement_terminator: ; +- statement: + delete_statement: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: films + - keyword: USING + - table_expression: + table_reference: + identifier: producers + - where_clause: + keyword: WHERE + expression: + - column_reference: + identifier: producer_id + - comparison_operator: + raw_comparison_operator: '=' + - column_reference: + - identifier: producers + - dot: . + - identifier: id + - binary_operator: AND + - column_reference: + - identifier: producers + - dot: . + - identifier: name + - comparison_operator: + raw_comparison_operator: '=' + - literal: "'foo'" +- statement_terminator: ; +- statement: + delete_statement: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: films + - alias_expression: + keyword: AS + identifier: f + - keyword: USING + - table_expression: + table_reference: + identifier: producers + - alias_expression: + keyword: AS + identifier: p + - where_clause: + keyword: WHERE + expression: + - column_reference: + - identifier: f + - dot: . + - identifier: producer_id + - comparison_operator: + raw_comparison_operator: '=' + - column_reference: + - identifier: p + - dot: . + - identifier: id + - binary_operator: AND + - column_reference: + - identifier: p + - dot: . + - identifier: name + - comparison_operator: + raw_comparison_operator: '=' + - literal: "'foo'" +- statement_terminator: ; +- statement: + delete_statement: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: films + - alias_expression: + keyword: AS + identifier: f + - keyword: USING + - table_expression: + table_reference: + identifier: producers + - alias_expression: + keyword: AS + identifier: p + - comma: ',' + - table_expression: + table_reference: + identifier: actors + - alias_expression: + keyword: AS + identifier: a + - where_clause: + keyword: WHERE + expression: + - column_reference: + - identifier: f + - dot: . + - identifier: producer_id + - comparison_operator: + raw_comparison_operator: '=' + - column_reference: + - identifier: p + - dot: . + - identifier: id + - binary_operator: AND + - column_reference: + - identifier: p + - dot: . + - identifier: name + - comparison_operator: + raw_comparison_operator: '=' + - literal: "'foo'" + - binary_operator: AND + - column_reference: + - identifier: f + - dot: . + - identifier: actor_id + - comparison_operator: + raw_comparison_operator: '=' + - column_reference: + - identifier: a + - dot: . + - identifier: id + - binary_operator: AND + - column_reference: + - identifier: a + - dot: . + - identifier: name + - comparison_operator: + raw_comparison_operator: '=' + - literal: "'joe cool'" +- statement_terminator: ; +- statement: + delete_statement: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: tasks + - keyword: WHERE + - keyword: CURRENT + - keyword: OF + - object_reference: + identifier: c_tasks +- statement_terminator: ; +- statement: + delete_statement: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: films + - where_clause: keyword: WHERE expression: column_reference: @@ -27,25 +200,27 @@ file: - statement_terminator: ; - statement: delete_statement: - keyword: DELETE - from_clause: - keyword: FROM - from_expression: - from_expression_element: - table_expression: - table_reference: - identifier: films + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: tasks + - where_clause: + keyword: WHERE + expression: + column_reference: + identifier: status + comparison_operator: + raw_comparison_operator: '=' + literal: "'DONE'" + - keyword: RETURNING + - star: '*' - statement_terminator: ; - statement: delete_statement: - keyword: DELETE - - from_clause: - keyword: FROM - from_expression: - from_expression_element: - table_expression: - table_reference: - identifier: tasks + - keyword: FROM + - table_reference: + identifier: tasks - where_clause: keyword: WHERE expression: @@ -55,49 +230,167 @@ file: raw_comparison_operator: '=' literal: "'DONE'" - keyword: RETURNING - - star: '*' + - expression: + column_reference: + identifier: actor_id - statement_terminator: ; - statement: delete_statement: - keyword: DELETE - - from_clause: - keyword: FROM - from_expression: - from_expression_element: - table_expression: - table_reference: - identifier: tasks - - keyword: WHERE - - keyword: CURRENT - - keyword: OF - - object_reference: - identifier: c_tasks + - keyword: FROM + - table_reference: + identifier: tasks + - where_clause: + keyword: WHERE + expression: + column_reference: + identifier: status + comparison_operator: + raw_comparison_operator: '=' + literal: "'DONE'" + - keyword: RETURNING + - expression: + column_reference: + identifier: actor_id + - alias_expression: + keyword: as + identifier: a_id - statement_terminator: ; - statement: delete_statement: - keyword: DELETE - from_clause: - keyword: FROM - from_expression: - from_expression_element: - table_expression: - table_reference: - identifier: some_table - using_clause: - keyword: USING - table_expression: - table_reference: - identifier: other_table - where_clause: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: tasks + - where_clause: keyword: WHERE expression: - - column_reference: - - identifier: other_table - - dot: . - - identifier: col - - comparison_operator: + column_reference: + identifier: status + comparison_operator: raw_comparison_operator: '=' - - column_reference: - - identifier: some_table - - dot: . - - identifier: col + literal: "'DONE'" + - keyword: RETURNING + - expression: + column_reference: + identifier: actor_id + - comma: ',' + - expression: + column_reference: + identifier: producer_id +- statement_terminator: ; +- statement: + delete_statement: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: tasks + - where_clause: + keyword: WHERE + expression: + column_reference: + identifier: status + comparison_operator: + raw_comparison_operator: '=' + literal: "'DONE'" + - keyword: RETURNING + - expression: + column_reference: + identifier: actor_id + - alias_expression: + keyword: as + identifier: a_id + - comma: ',' + - expression: + column_reference: + identifier: producer_id + - alias_expression: + keyword: as + identifier: p_id +- statement_terminator: ; +- statement: + with_compound_statement: + keyword: WITH + common_table_expression: + identifier: test + keyword: as + bracketed: + start_bracket: ( + select_statement: + select_clause: + keyword: select + select_clause_element: + column_reference: + identifier: foo + from_clause: + keyword: from + from_expression: + from_expression_element: + table_expression: + table_reference: + identifier: bar + end_bracket: ) + delete_statement: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: films +- statement_terminator: ; +- statement: + with_compound_statement: + - keyword: WITH + - keyword: RECURSIVE + - common_table_expression: + - identifier: t + - bracketed: + start_bracket: ( + identifier_list: + identifier: n + end_bracket: ) + - keyword: AS + - bracketed: + start_bracket: ( + set_expression: + values_clause: + keyword: VALUES + delimited_values: + tuple_value: + bracketed: + start_bracket: ( + scalar_value: + literal: '1' + end_bracket: ) + set_operator: + - keyword: UNION + - keyword: ALL + select_statement: + select_clause: + keyword: SELECT + select_clause_element: + expression: + column_reference: + identifier: n + binary_operator: + + literal: '1' + from_clause: + keyword: FROM + from_expression: + from_expression_element: + table_expression: + table_reference: + identifier: t + where_clause: + keyword: WHERE + expression: + column_reference: + identifier: n + comparison_operator: + raw_comparison_operator: < + literal: '100' + end_bracket: ) + - delete_statement: + - keyword: DELETE + - keyword: FROM + - table_reference: + identifier: films +- statement_terminator: ;