From f1d3cf41d77daa7ffe134b188667b9178d8c3986 Mon Sep 17 00:00:00 2001 From: Dave Tapley Date: Wed, 16 Nov 2022 22:01:58 +0000 Subject: [PATCH] Support CREATE OR REPLACE MATERIALIZED VIEW https://github.com/sqlfluff/sqlfluff/issues/4069 --- src/sqlfluff/dialects/dialect_postgres.py | 29 ++++++++ .../postgres_create_materialized_view.sql | 4 ++ .../postgres_create_materialized_view.yml | 26 ++++++- .../dialects/postgres/postgres_select.sql | 4 ++ .../dialects/postgres/postgres_select.yml | 68 ++++++++++++++++++- 5 files changed, 129 insertions(+), 2 deletions(-) diff --git a/src/sqlfluff/dialects/dialect_postgres.py b/src/sqlfluff/dialects/dialect_postgres.py index b11063058a6..bce66b9281a 100644 --- a/src/sqlfluff/dialects/dialect_postgres.py +++ b/src/sqlfluff/dialects/dialect_postgres.py @@ -22,6 +22,7 @@ Sequence, SymbolSegment, StartsWith, + StringParser, ) from sqlfluff.core.parser.segments.base import BracketedSegment @@ -40,6 +41,14 @@ postgres_dialect = ansi_dialect.copy_as("postgres") +postgres_dialect.insert_lexer_matchers( + # JSON Operators: https://www.postgresql.org/docs/9.5/functions-json.html + [ + StringLexer("right_arrow", "=>", CodeSegment), + ], + before="equals", +) + postgres_dialect.insert_lexer_matchers( # JSON Operators: https://www.postgresql.org/docs/9.5/functions-json.html [ @@ -236,6 +245,7 @@ Ref("NakedIdentifierFullSegment"), ), CascadeRestrictGrammar=OneOf("CASCADE", "RESTRICT"), + RightArrowSegment=StringParser("=>", SymbolSegment, type="right_arrow"), ) postgres_dialect.replace( @@ -275,6 +285,10 @@ CodeSegment, type="function_name_identifier", ), + FunctionContentsExpressionGrammar=OneOf( + Ref("ExpressionSegment"), + Ref("NamedArgumentSegment"), + ), QuotedLiteralSegment=OneOf( # Postgres allows newline-concatenated string literals (#1488). # Since these string literals can have comments between them, @@ -2029,6 +2043,7 @@ class CreateMaterializedViewStatementSegment(BaseSegment): match_grammar = Sequence( "CREATE", + Sequence("OR", "REPLACE", optional=True), "MATERIALIZED", "VIEW", Ref("IfNotExistsGrammar", optional=True), @@ -4615,3 +4630,17 @@ class ColumnReferenceSegment(ObjectReferenceSegment): ), allow_gaps=False, ) + + +class NamedArgumentSegment(BaseSegment): + """Named argument to a function. + + https://www.postgresql.org/docs/current/sql-syntax-calling-funcs.html#SQL-SYNTAX-CALLING-FUNCS-NAMED + """ + + type = "named_argument" + match_grammar = Sequence( + Ref("NakedIdentifierSegment"), + Ref("RightArrowSegment"), + Ref("ExpressionSegment"), + ) diff --git a/test/fixtures/dialects/postgres/postgres_create_materialized_view.sql b/test/fixtures/dialects/postgres/postgres_create_materialized_view.sql index 15d295970c9..5e454f4afee 100644 --- a/test/fixtures/dialects/postgres/postgres_create_materialized_view.sql +++ b/test/fixtures/dialects/postgres/postgres_create_materialized_view.sql @@ -150,4 +150,8 @@ CREATE MATERIALIZED VIEW my_mat_view WITH (left.right) AS SELECT a +FROM my_table; + +CREATE OR REPLACE MATERIALIZED VIEW my_mat_view AS +SELECT a FROM my_table; \ No newline at end of file diff --git a/test/fixtures/dialects/postgres/postgres_create_materialized_view.yml b/test/fixtures/dialects/postgres/postgres_create_materialized_view.yml index 8a0ccab8abb..ccf0025f468 100644 --- a/test/fixtures/dialects/postgres/postgres_create_materialized_view.yml +++ b/test/fixtures/dialects/postgres/postgres_create_materialized_view.yml @@ -3,7 +3,7 @@ # 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: 372bd8f828b87dd66ddcf9a78448d50ea8a681bef794b750256f3786a988f06e +_hash: 241ca7aedfaff431b1ffae209bc65103e8caf6896b36324a8d4fb38b27c4bfb4 file: - statement: create_materialized_view_statement: @@ -704,3 +704,27 @@ file: table_reference: naked_identifier: my_table - statement_terminator: ; +- statement: + create_materialized_view_statement: + - keyword: CREATE + - keyword: OR + - keyword: REPLACE + - keyword: MATERIALIZED + - keyword: VIEW + - table_reference: + naked_identifier: my_mat_view + - keyword: AS + - select_statement: + select_clause: + keyword: SELECT + select_clause_element: + column_reference: + naked_identifier: a + from_clause: + keyword: FROM + from_expression: + from_expression_element: + table_expression: + table_reference: + naked_identifier: my_table +- statement_terminator: ; diff --git a/test/fixtures/dialects/postgres/postgres_select.sql b/test/fixtures/dialects/postgres/postgres_select.sql index 86798d02403..207f3fc3a5b 100644 --- a/test/fixtures/dialects/postgres/postgres_select.sql +++ b/test/fixtures/dialects/postgres/postgres_select.sql @@ -65,3 +65,7 @@ SELECT FROM test1; -- keywords can be used as column names without quotes if qualified select id, start, periods.end from periods; + +SELECT concat_lower_or_upper('Hello', 'World', true); +SELECT concat_lower_or_upper(a => 'Hello', b => 'World'); +SELECT concat_lower_or_upper('Hello', 'World', uppercase => true); diff --git a/test/fixtures/dialects/postgres/postgres_select.yml b/test/fixtures/dialects/postgres/postgres_select.yml index a656783342d..2d32db391e7 100644 --- a/test/fixtures/dialects/postgres/postgres_select.yml +++ b/test/fixtures/dialects/postgres/postgres_select.yml @@ -3,7 +3,7 @@ # 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: d6880cc6b5b12a791d3b0e258410ad4b4d37331250e9650447f5a0df2e059c39 +_hash: 4c2313e0a846e6f2e7aae0410ba8e11c9815f03fdf9e8cb2869c93e945b38e6f file: - statement: select_statement: @@ -612,3 +612,69 @@ file: table_reference: naked_identifier: periods - statement_terminator: ; +- statement: + select_statement: + select_clause: + keyword: SELECT + select_clause_element: + function: + function_name: + function_name_identifier: concat_lower_or_upper + bracketed: + - start_bracket: ( + - expression: + quoted_literal: "'Hello'" + - comma: ',' + - expression: + quoted_literal: "'World'" + - comma: ',' + - expression: + boolean_literal: 'true' + - end_bracket: ) +- statement_terminator: ; +- statement: + select_statement: + select_clause: + keyword: SELECT + select_clause_element: + function: + function_name: + function_name_identifier: concat_lower_or_upper + bracketed: + - start_bracket: ( + - named_argument: + naked_identifier: a + right_arrow: => + expression: + quoted_literal: "'Hello'" + - comma: ',' + - named_argument: + naked_identifier: b + right_arrow: => + expression: + quoted_literal: "'World'" + - end_bracket: ) +- statement_terminator: ; +- statement: + select_statement: + select_clause: + keyword: SELECT + select_clause_element: + function: + function_name: + function_name_identifier: concat_lower_or_upper + bracketed: + - start_bracket: ( + - expression: + quoted_literal: "'Hello'" + - comma: ',' + - expression: + quoted_literal: "'World'" + - comma: ',' + - named_argument: + naked_identifier: uppercase + right_arrow: => + expression: + boolean_literal: 'true' + - end_bracket: ) +- statement_terminator: ;