From 7e14910c2221bdad8d990f0e82962f8a76eee22b Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Fri, 12 Jun 2026 23:55:31 -0400 Subject: [PATCH] parser: fix missing fields in AST + more error resilient parsing --- crates/squawk_fmt/src/fmt.rs | 4 +- .../squawk_fmt/tests/after/select_expr.snap | 1 + .../squawk_fmt/tests/before/select_expr.sql | 1 + .../rewrite_create_table_as_as_select_into.rs | 2 +- crates/squawk_ide/src/collect.rs | 34 ++- crates/squawk_ide/src/expand_selection.rs | 1 + crates/squawk_ide/src/folding_ranges.rs | 1 + crates/squawk_ide/src/resolve.rs | 5 +- .../src/rules/require_concurrent_reindex.rs | 11 +- .../src/generated/syntax_kind.rs | 3 + crates/squawk_parser/src/grammar.rs | 98 +++++--- .../tests/data/ok/alter_publication.sql | 1 + .../tests/data/ok/create_publication.sql | 3 + .../tests/data/ok/deallocate.sql | 2 +- crates/squawk_parser/tests/data/ok/fetch.sql | 2 + crates/squawk_parser/tests/data/ok/move.sql | 2 + .../tests/data/ok/select_operators.sql | 6 + .../tests__alter_publication_ok.snap | 47 ++++ .../tests__create_publication_ok.snap | 78 ++++++ .../tests/snapshots/tests__deallocate_ok.snap | 11 +- .../tests/snapshots/tests__fetch_ok.snap | 31 +++ .../tests/snapshots/tests__move_ok.snap | 31 +++ .../tests/snapshots/tests__reindex_err.snap | 24 +- .../tests/snapshots/tests__reindex_ok.snap | 40 +-- .../snapshots/tests__select_operators_ok.snap | 52 ++++ .../squawk_syntax/src/ast/generated/nodes.rs | 233 ++++++++++++++++-- crates/squawk_syntax/src/ast/node_ext.rs | 10 + crates/squawk_syntax/src/postgresql.ungram | 54 ++-- crates/xtask/src/codegen.rs | 2 + squawk-vscode/syntaxes/pgsql.tmLanguage.json | 2 +- 30 files changed, 683 insertions(+), 109 deletions(-) diff --git a/crates/squawk_fmt/src/fmt.rs b/crates/squawk_fmt/src/fmt.rs index d35411d10..c64d474e2 100644 --- a/crates/squawk_fmt/src/fmt.rs +++ b/crates/squawk_fmt/src/fmt.rs @@ -208,7 +208,9 @@ fn build_expr<'a>(expr: ast::Expr) -> Doc<'a> { doc = doc.append(Doc::space()).append(Doc::text("not")); } doc = doc.append(Doc::space()).append(Doc::text("between")); - if between_expr.symmetric_token().is_some() { + if between_expr.asymmetric_token().is_some() { + doc = doc.append(Doc::space()).append(Doc::text("asymmetric")); + } else if between_expr.symmetric_token().is_some() { doc = doc.append(Doc::space()).append(Doc::text("symmetric")); } doc.append(Doc::space()) diff --git a/crates/squawk_fmt/tests/after/select_expr.snap b/crates/squawk_fmt/tests/after/select_expr.snap index e2f6f3e30..c59beb578 100644 --- a/crates/squawk_fmt/tests/after/select_expr.snap +++ b/crates/squawk_fmt/tests/after/select_expr.snap @@ -10,6 +10,7 @@ select -- between expr 2 between 1 and 3, 2 not between 1 and 3, + 2 between asymmetric 1 and 3, 2 between symmetric 1 and 3, -- bin expr 1 + 1, diff --git a/crates/squawk_fmt/tests/before/select_expr.sql b/crates/squawk_fmt/tests/before/select_expr.sql index ff75cfdcf..f103e5ba4 100644 --- a/crates/squawk_fmt/tests/before/select_expr.sql +++ b/crates/squawk_fmt/tests/before/select_expr.sql @@ -6,6 +6,7 @@ select -- between expr 2 between 1 and 3, 2 not between 1 and 3, + 2 between asymmetric 1 and 3, 2 between symmetric 1 and 3, -- bin expr 1 + 1, diff --git a/crates/squawk_ide/src/code_actions/rewrite_create_table_as_as_select_into.rs b/crates/squawk_ide/src/code_actions/rewrite_create_table_as_as_select_into.rs index 41e75b3fe..7f74510ac 100644 --- a/crates/squawk_ide/src/code_actions/rewrite_create_table_as_as_select_into.rs +++ b/crates/squawk_ide/src/code_actions/rewrite_create_table_as_as_select_into.rs @@ -33,7 +33,7 @@ pub(super) fn rewrite_create_table_as_as_select_into( } // TODO: support more - let ast::SelectVariant::Select(select) = create_table_as.query()? else { + let ast::SelectVariant::Select(select) = create_table_as.query()?.select_variant()? else { return None; }; diff --git a/crates/squawk_ide/src/collect.rs b/crates/squawk_ide/src/collect.rs index 08acee6b9..e48d9dc36 100644 --- a/crates/squawk_ide/src/collect.rs +++ b/crates/squawk_ide/src/collect.rs @@ -96,12 +96,16 @@ fn resolved_to_column_ptrs( columns_from_create_table_impl(db, file, &parent_table, &mut cols, depth); cols } - ResolvedTableName::TableAs(create_table_as) => { - select_columns_with_types(db, file, &create_table_as.query()) - .into_iter() - .map(|(name, _ty)| (name, None)) - .collect() - } + ResolvedTableName::TableAs(create_table_as) => select_columns_with_types( + db, + file, + &create_table_as + .query() + .and_then(|query| query.select_variant()), + ) + .into_iter() + .map(|(name, _ty)| (name, None)) + .collect(), ResolvedTableName::SelectInto(select_into) => { select_into_columns_with_types(db, file, &select_into) .into_iter() @@ -194,9 +198,13 @@ fn resolved_to_columns_with_types( ResolvedTableName::Table(parent_table) => { table_columns_impl(db, file, &parent_table, depth) } - ResolvedTableName::TableAs(create_table_as) => { - select_columns_with_types(db, file, &create_table_as.query()) - } + ResolvedTableName::TableAs(create_table_as) => select_columns_with_types( + db, + file, + &create_table_as + .query() + .and_then(|query| query.select_variant()), + ), ResolvedTableName::SelectInto(select_into) => { select_into_columns_with_types(db, file, &select_into) } @@ -212,7 +220,13 @@ pub(crate) fn create_table_as_columns_with_types( create_table_as: &ast::CreateTableAs, ) -> Vec<(Name, Option)> { for file in list_files(db, file) { - let columns = select_columns_with_types(db, file, &create_table_as.query()); + let columns = select_columns_with_types( + db, + file, + &create_table_as + .query() + .and_then(|query| query.select_variant()), + ); if !columns.is_empty() { return columns; } diff --git a/crates/squawk_ide/src/expand_selection.rs b/crates/squawk_ide/src/expand_selection.rs index c6a2f87f8..7717500e6 100644 --- a/crates/squawk_ide/src/expand_selection.rs +++ b/crates/squawk_ide/src/expand_selection.rs @@ -59,6 +59,7 @@ const DELIMITED_LIST_KINDS: &[SyntaxKind] = &[ SyntaxKind::PARTITION_ITEM_LIST, SyntaxKind::PARTITION_LIST, SyntaxKind::PATH_LIST, + SyntaxKind::REINDEX_OPTION_LIST, SyntaxKind::RETURNING_OPTION_LIST, SyntaxKind::REVOKE_COMMAND_LIST, SyntaxKind::ROLE_REF_LIST, diff --git a/crates/squawk_ide/src/folding_ranges.rs b/crates/squawk_ide/src/folding_ranges.rs index 280e9f9b0..95b5ebc95 100644 --- a/crates/squawk_ide/src/folding_ranges.rs +++ b/crates/squawk_ide/src/folding_ranges.rs @@ -143,6 +143,7 @@ fn fold_kind(kind: SyntaxKind) -> Option { | SyntaxKind::PARTITION_ITEM_LIST | SyntaxKind::PARTITION_LIST | SyntaxKind::PATH_LIST + | SyntaxKind::REINDEX_OPTION_LIST | SyntaxKind::RETURNING_OPTION_LIST | SyntaxKind::REVOKE_COMMAND_LIST | SyntaxKind::ROLE_OPTION_LIST diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index 65d39d843..3dc82d56e 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -2246,7 +2246,7 @@ fn find_column_in_create_table_as_with_skip( ) -> Option> { let file = create_table_as.file_id; let create_table_as = create_table_as.value; - let query = create_table_as.query()?; + let query = create_table_as.query()?.select_variant()?; if let ast::SelectVariant::Values(values) = &query { return resolve_values_column_after_index(file, values, column_name, skip_column_count); @@ -2435,7 +2435,8 @@ fn count_columns_for_table_name( .ancestors() .find_map(ast::CreateTableAs::cast) { - let select = ast_nav::select_from_variant(create_table_as.query()?)?; + let select = + ast_nav::select_from_variant(create_table_as.query()?.select_variant()?)?; if let Some(target_list) = select.select_clause().and_then(|c| c.target_list()) { return Some(target_list.targets().count()); } diff --git a/crates/squawk_linter/src/rules/require_concurrent_reindex.rs b/crates/squawk_linter/src/rules/require_concurrent_reindex.rs index c1052570a..6b3a7e460 100644 --- a/crates/squawk_linter/src/rules/require_concurrent_reindex.rs +++ b/crates/squawk_linter/src/rules/require_concurrent_reindex.rs @@ -16,6 +16,15 @@ fn concurrently_fix(reindex: &ast::Reindex) -> Option { Some(Fix::new("Add `concurrently`", vec![edit])) } +fn has_concurrently(reindex: &ast::Reindex) -> bool { + reindex.concurrently_token().is_some() + || reindex.reindex_option_list().is_some_and(|option_list| { + option_list + .reindex_options() + .any(|option| option.concurrently_token().is_some()) + }) +} + pub(crate) fn require_concurrent_reindex(ctx: &mut Linter, parse: &Parse) { for stmt in parse.tree().stmts() { if let ast::Stmt::Reindex(reindex) = stmt { @@ -23,7 +32,7 @@ pub(crate) fn require_concurrent_reindex(ctx: &mut Linter, parse: &Parse) -> CompletedMarker { let m = p.start(); p.eat(NOT_KW); p.expect(BETWEEN_KW); - p.eat(SYMMETRIC_KW); + let _ = p.eat(SYMMETRIC_KW) || p.eat(ASYMMETRIC_KW); b_expr(p); p.expect(AND_KW); b_expr(p); @@ -8117,9 +8117,15 @@ fn path_name_ref_list(p: &mut Parser<'_>) { fn path_list(p: &mut Parser<'_>) { let m = p.start(); - p.expect(L_PAREN); - path_name_ref_list(p); - p.expect(R_PAREN); + delimited( + p, + L_PAREN, + R_PAREN, + COMMA, + || "unexpected comma".to_string(), + NAME_REF_FIRST, + |p| opt_path_name_ref(p).is_some(), + ); m.complete(p, PATH_LIST); } @@ -8951,33 +8957,39 @@ fn alter_vertex_edge_table(p: &mut Parser<'_>) { } fn drop_edge_tables(p: &mut Parser<'_>) { - assert!(p.at(DROP_KW)); + assert!(p.at(DROP_KW) && p.nth_at_ts(1, EDGE)); let m = p.start(); p.bump(DROP_KW); p.bump_any(); // EDGE/RELATIONSHIP p.expect(TABLES_KW); - p.expect(L_PAREN); - path_name_ref(p); - while p.eat(COMMA) { - path_name_ref(p); - } - p.expect(R_PAREN); + delimited( + p, + L_PAREN, + R_PAREN, + COMMA, + || "unexpected comma".to_string(), + NAME_REF_FIRST, + |p| opt_path_name_ref(p).is_some(), + ); opt_cascade_or_restrict(p); m.complete(p, DROP_EDGE_TABLES); } fn drop_vertex_tables(p: &mut Parser<'_>) { - assert!(p.at(DROP_KW)); + assert!(p.at(DROP_KW) && p.nth_at_ts(1, VERTEX)); let m = p.start(); p.bump(DROP_KW); p.bump_any(); // VERTEX/NODE p.expect(TABLES_KW); - p.expect(L_PAREN); - path_name_ref(p); - while p.eat(COMMA) { - path_name_ref(p); - } - p.expect(R_PAREN); + delimited( + p, + L_PAREN, + R_PAREN, + COMMA, + || "unexpected comma".to_string(), + NAME_REF_FIRST, + |p| opt_path_name_ref(p).is_some(), + ); opt_cascade_or_restrict(p); m.complete(p, DROP_VERTEX_TABLES); } @@ -10090,8 +10102,11 @@ fn opt_except_table_clause(p: &mut Parser<'_>) { || "unexpected comma".to_string(), RELATION_NAME_FIRST.union(TokenSet::new(&[TABLE_KW])), |p| { + let m = p.start(); p.eat(TABLE_KW); - opt_relation_name(p).is_some() + let result = opt_relation_name(p).is_some(); + m.complete(p, EXCEPT_TABLE_NAME); + result }, ); m.complete(p, EXCEPT_TABLE_CLAUSE); @@ -10290,13 +10305,19 @@ fn create_statistics(p: &mut Parser<'_>) -> CompletedMarker { } fn opt_paren_name_ref_list(p: &mut Parser<'_>) -> bool { - if p.eat(L_PAREN) { - name_ref_list(p); - p.expect(R_PAREN); - true - } else { - false + if !p.at(L_PAREN) { + return false; } + delimited( + p, + L_PAREN, + R_PAREN, + COMMA, + || "unexpected comma".to_string(), + NAME_REF_FIRST, + |p| name_ref(p).is_some(), + ); + true } // CREATE SUBSCRIPTION subscription_name @@ -12324,7 +12345,8 @@ const REINDEX_OPTION_FIRST: TokenSet = TokenSet::new(&[CONCURRENTLY_KW, VERBOSE_ // TABLESPACE new_tablespace // VERBOSE [ boolean ] fn opt_reindex_option(p: &mut Parser<'_>) -> bool { - match p.current() { + let m = p.start(); + let parsed = match p.current() { CONCURRENTLY_KW | VERBOSE_KW => { p.bump_any(); opt_bool_literal(p); @@ -12336,7 +12358,13 @@ fn opt_reindex_option(p: &mut Parser<'_>) -> bool { true } _ => false, + }; + if parsed { + m.complete(p, REINDEX_OPTION); + } else { + m.abandon(p); } + parsed } // REINDEX [ ( option [, ...] ) ] { INDEX | TABLE | SCHEMA } [ CONCURRENTLY ] name @@ -12346,6 +12374,7 @@ fn reindex(p: &mut Parser<'_>) -> CompletedMarker { let m = p.start(); p.bump(REINDEX_KW); if p.at(L_PAREN) { + let options = p.start(); delimited( p, L_PAREN, @@ -12355,6 +12384,7 @@ fn reindex(p: &mut Parser<'_>) -> CompletedMarker { REINDEX_OPTION_FIRST, opt_reindex_option, ); + options.complete(p, REINDEX_OPTION_LIST); } let name_required = match p.current() { // { INDEX | TABLE | SCHEMA } @@ -12689,7 +12719,7 @@ fn opt_direction(p: &mut Parser<'_>) -> bool { RELATIVE_KW => { let m = p.start(); p.bump(RELATIVE_KW); - if opt_numeric_literal(p).is_none() { + if b_expr(p).is_none() { p.error("expected count") } m.complete(p, RELATIVE); @@ -12697,7 +12727,7 @@ fn opt_direction(p: &mut Parser<'_>) -> bool { ABSOLUTE_KW => { let m = p.start(); p.bump(ABSOLUTE_KW); - if opt_numeric_literal(p).is_none() { + if b_expr(p).is_none() { p.error("expected count") } m.complete(p, ABSOLUTE); @@ -12705,22 +12735,22 @@ fn opt_direction(p: &mut Parser<'_>) -> bool { FORWARD_KW => { let m = p.start(); p.bump(FORWARD_KW); - if !p.eat(ALL_KW) { - let _ = opt_numeric_literal(p); + if !p.eat(ALL_KW) && p.at_ts(EXPR_FIRST) { + let _ = b_expr(p); } m.complete(p, FORWARD); } BACKWARD_KW => { let m = p.start(); p.bump(BACKWARD_KW); - if !p.eat(ALL_KW) { - let _ = opt_numeric_literal(p); + if !p.eat(ALL_KW) && p.at_ts(EXPR_FIRST) { + let _ = b_expr(p); } m.complete(p, BACKWARD); } // count - _ if p.at_ts(NUMERIC_FIRST) => { - if opt_numeric_literal(p).is_none() { + _ if p.at_ts(NUMERIC_FIRST) || p.at(MINUS) || p.at(PLUS) => { + if b_expr(p).is_none() { p.error("expected count") } } diff --git a/crates/squawk_parser/tests/data/ok/alter_publication.sql b/crates/squawk_parser/tests/data/ok/alter_publication.sql index 864b222db..236338fa9 100644 --- a/crates/squawk_parser/tests/data/ok/alter_publication.sql +++ b/crates/squawk_parser/tests/data/ok/alter_publication.sql @@ -24,3 +24,4 @@ alter publication p owner to current_user; -- rename alter publication p rename to q; +alter publication p set all tables except (table t, u, table k); diff --git a/crates/squawk_parser/tests/data/ok/create_publication.sql b/crates/squawk_parser/tests/data/ok/create_publication.sql index a308e7f61..8bc4d1579 100644 --- a/crates/squawk_parser/tests/data/ok/create_publication.sql +++ b/crates/squawk_parser/tests/data/ok/create_publication.sql @@ -21,3 +21,6 @@ create publication p -- multiple tables create publication pub for table chats, users; + +create publication p for all tables except (table only t); +create publication p for all tables except (table t, u, table k); diff --git a/crates/squawk_parser/tests/data/ok/deallocate.sql b/crates/squawk_parser/tests/data/ok/deallocate.sql index 5339a06a5..125b4690c 100644 --- a/crates/squawk_parser/tests/data/ok/deallocate.sql +++ b/crates/squawk_parser/tests/data/ok/deallocate.sql @@ -1,7 +1,7 @@ -- pg_docs deallocate prepare all; - deallocate all; +deallocate prepare foo; deallocate foo; diff --git a/crates/squawk_parser/tests/data/ok/fetch.sql b/crates/squawk_parser/tests/data/ok/fetch.sql index 80ec2c1a1..1f2eb9dae 100644 --- a/crates/squawk_parser/tests/data/ok/fetch.sql +++ b/crates/squawk_parser/tests/data/ok/fetch.sql @@ -11,9 +11,11 @@ FETCH LAST FROM cursor_name; FETCH ABSOLUTE 10 FROM cursor_name; FETCH RELATIVE 10 FROM cursor_name; FETCH 10 FROM cursor_name; +FETCH -3 FROM cursor_name; FETCH ALL FROM cursor_name; FETCH FORWARD FROM cursor_name; FETCH FORWARD 10 FROM cursor_name; +FETCH FORWARD +3 IN cursor_name; FETCH FORWARD ALL FROM cursor_name; FETCH BACKWARD FROM cursor_name; FETCH BACKWARD 10 FROM cursor_name; diff --git a/crates/squawk_parser/tests/data/ok/move.sql b/crates/squawk_parser/tests/data/ok/move.sql index 3a4c534db..be20b00f0 100644 --- a/crates/squawk_parser/tests/data/ok/move.sql +++ b/crates/squawk_parser/tests/data/ok/move.sql @@ -11,9 +11,11 @@ move LAST FROM cursor_name; move ABSOLUTE 10 FROM cursor_name; move RELATIVE 10 FROM cursor_name; move 10 FROM cursor_name; +move -5 IN cursor_name; move ALL FROM cursor_name; move FORWARD FROM cursor_name; move FORWARD 10 FROM cursor_name; +move FORWARD +5 IN cursor_name; move FORWARD ALL FROM cursor_name; move BACKWARD FROM cursor_name; move BACKWARD 10 FROM cursor_name; diff --git a/crates/squawk_parser/tests/data/ok/select_operators.sql b/crates/squawk_parser/tests/data/ok/select_operators.sql index 3096cdfd5..d091561c6 100644 --- a/crates/squawk_parser/tests/data/ok/select_operators.sql +++ b/crates/squawk_parser/tests/data/ok/select_operators.sql @@ -254,6 +254,12 @@ select 2 between foo() and bar(); select 2 not between 1 and 3; select 2 not between foo() and bar(); +-- between asymmetric (inclusive of the range endpoints, explicit default) +select 2 between asymmetric 1 and 3; + +-- not between asymmetric (not between, explicit default) +select 2 not between asymmetric 1 and 3; + -- between symmetric (between, after sorting the two endpoint values) select 2 between symmetric 3 and 1; diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_publication_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_publication_ok.snap index b78995ac7..bacb56619 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_publication_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_publication_ok.snap @@ -422,3 +422,50 @@ SOURCE_FILE IDENT "q" SEMICOLON ";" WHITESPACE "\n\n" + ALTER_PUBLICATION + ALTER_KW "alter" + WHITESPACE " " + PUBLICATION_KW "publication" + WHITESPACE " " + NAME_REF + IDENT "p" + WHITESPACE " " + SET_KW "set" + WHITESPACE " " + ALL_KW "all" + WHITESPACE " " + TABLES_KW "tables" + WHITESPACE " " + EXCEPT_TABLE_CLAUSE + EXCEPT_KW "except" + WHITESPACE " " + L_PAREN "(" + EXCEPT_TABLE_NAME + TABLE_KW "table" + WHITESPACE " " + RELATION_NAME + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + COMMA "," + WHITESPACE " " + EXCEPT_TABLE_NAME + RELATION_NAME + PATH + PATH_SEGMENT + NAME_REF + IDENT "u" + COMMA "," + WHITESPACE " " + EXCEPT_TABLE_NAME + TABLE_KW "table" + WHITESPACE " " + RELATION_NAME + PATH + PATH_SEGMENT + NAME_REF + IDENT "k" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_publication_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_publication_ok.snap index c5f97b4ee..8c80213c4 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_publication_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_publication_ok.snap @@ -269,4 +269,82 @@ SOURCE_FILE NAME_REF IDENT "users" SEMICOLON ";" + WHITESPACE "\n\n" + CREATE_PUBLICATION + CREATE_KW "create" + WHITESPACE " " + PUBLICATION_KW "publication" + WHITESPACE " " + NAME + IDENT "p" + WHITESPACE " " + FOR_KW "for" + WHITESPACE " " + ALL_KW "all" + WHITESPACE " " + TABLES_KW "tables" + WHITESPACE " " + EXCEPT_TABLE_CLAUSE + EXCEPT_KW "except" + WHITESPACE " " + L_PAREN "(" + EXCEPT_TABLE_NAME + TABLE_KW "table" + WHITESPACE " " + RELATION_NAME + ONLY_KW "only" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_PUBLICATION + CREATE_KW "create" + WHITESPACE " " + PUBLICATION_KW "publication" + WHITESPACE " " + NAME + IDENT "p" + WHITESPACE " " + FOR_KW "for" + WHITESPACE " " + ALL_KW "all" + WHITESPACE " " + TABLES_KW "tables" + WHITESPACE " " + EXCEPT_TABLE_CLAUSE + EXCEPT_KW "except" + WHITESPACE " " + L_PAREN "(" + EXCEPT_TABLE_NAME + TABLE_KW "table" + WHITESPACE " " + RELATION_NAME + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + COMMA "," + WHITESPACE " " + EXCEPT_TABLE_NAME + RELATION_NAME + PATH + PATH_SEGMENT + NAME_REF + IDENT "u" + COMMA "," + WHITESPACE " " + EXCEPT_TABLE_NAME + TABLE_KW "table" + WHITESPACE " " + RELATION_NAME + PATH + PATH_SEGMENT + NAME_REF + IDENT "k" + R_PAREN ")" + SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__deallocate_ok.snap b/crates/squawk_parser/tests/snapshots/tests__deallocate_ok.snap index ddd477461..3e2d61c18 100644 --- a/crates/squawk_parser/tests/snapshots/tests__deallocate_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__deallocate_ok.snap @@ -12,13 +12,22 @@ SOURCE_FILE WHITESPACE " " ALL_KW "all" SEMICOLON ";" - WHITESPACE "\n\n" + WHITESPACE "\n" DEALLOCATE DEALLOCATE_KW "deallocate" WHITESPACE " " ALL_KW "all" SEMICOLON ";" WHITESPACE "\n\n" + DEALLOCATE + DEALLOCATE_KW "deallocate" + WHITESPACE " " + PREPARE_KW "prepare" + WHITESPACE " " + NAME_REF + IDENT "foo" + SEMICOLON ";" + WHITESPACE "\n" DEALLOCATE DEALLOCATE_KW "deallocate" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__fetch_ok.snap b/crates/squawk_parser/tests/snapshots/tests__fetch_ok.snap index ed3cb5de0..543276483 100644 --- a/crates/squawk_parser/tests/snapshots/tests__fetch_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__fetch_ok.snap @@ -141,6 +141,20 @@ SOURCE_FILE IDENT "cursor_name" SEMICOLON ";" WHITESPACE "\n" + FETCH + FETCH_KW "FETCH" + WHITESPACE " " + PREFIX_EXPR + MINUS "-" + LITERAL + INT_NUMBER "3" + WHITESPACE " " + FROM_KW "FROM" + WHITESPACE " " + NAME_REF + IDENT "cursor_name" + SEMICOLON ";" + WHITESPACE "\n" FETCH FETCH_KW "FETCH" WHITESPACE " " @@ -180,6 +194,23 @@ SOURCE_FILE IDENT "cursor_name" SEMICOLON ";" WHITESPACE "\n" + FETCH + FETCH_KW "FETCH" + WHITESPACE " " + FORWARD + FORWARD_KW "FORWARD" + WHITESPACE " " + PREFIX_EXPR + PLUS "+" + LITERAL + INT_NUMBER "3" + WHITESPACE " " + IN_KW "IN" + WHITESPACE " " + NAME_REF + IDENT "cursor_name" + SEMICOLON ";" + WHITESPACE "\n" FETCH FETCH_KW "FETCH" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__move_ok.snap b/crates/squawk_parser/tests/snapshots/tests__move_ok.snap index c7c815fbe..e4a46bb3f 100644 --- a/crates/squawk_parser/tests/snapshots/tests__move_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__move_ok.snap @@ -141,6 +141,20 @@ SOURCE_FILE IDENT "cursor_name" SEMICOLON ";" WHITESPACE "\n" + MOVE + MOVE_KW "move" + WHITESPACE " " + PREFIX_EXPR + MINUS "-" + LITERAL + INT_NUMBER "5" + WHITESPACE " " + IN_KW "IN" + WHITESPACE " " + NAME_REF + IDENT "cursor_name" + SEMICOLON ";" + WHITESPACE "\n" MOVE MOVE_KW "move" WHITESPACE " " @@ -180,6 +194,23 @@ SOURCE_FILE IDENT "cursor_name" SEMICOLON ";" WHITESPACE "\n" + MOVE + MOVE_KW "move" + WHITESPACE " " + FORWARD + FORWARD_KW "FORWARD" + WHITESPACE " " + PREFIX_EXPR + PLUS "+" + LITERAL + INT_NUMBER "5" + WHITESPACE " " + IN_KW "IN" + WHITESPACE " " + NAME_REF + IDENT "cursor_name" + SEMICOLON ";" + WHITESPACE "\n" MOVE MOVE_KW "move" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__reindex_err.snap b/crates/squawk_parser/tests/snapshots/tests__reindex_err.snap index 881436df4..58ccf4215 100644 --- a/crates/squawk_parser/tests/snapshots/tests__reindex_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__reindex_err.snap @@ -8,16 +8,20 @@ SOURCE_FILE REINDEX REINDEX_KW "reindex" WHITESPACE " " - L_PAREN "(" - CONCURRENTLY_KW "concurrently" - WHITESPACE " " - VERBOSE_KW "verbose" - WHITESPACE " " - TABLESPACE_KW "tablespace" - WHITESPACE " " - NAME - IDENT "t" - R_PAREN ")" + REINDEX_OPTION_LIST + L_PAREN "(" + REINDEX_OPTION + CONCURRENTLY_KW "concurrently" + WHITESPACE " " + REINDEX_OPTION + VERBOSE_KW "verbose" + WHITESPACE " " + REINDEX_OPTION + TABLESPACE_KW "tablespace" + WHITESPACE " " + NAME + IDENT "t" + R_PAREN ")" WHITESPACE " " INDEX_KW "index" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__reindex_ok.snap b/crates/squawk_parser/tests/snapshots/tests__reindex_ok.snap index 6710fc6c7..975e25f00 100644 --- a/crates/squawk_parser/tests/snapshots/tests__reindex_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__reindex_ok.snap @@ -45,24 +45,28 @@ SOURCE_FILE REINDEX REINDEX_KW "reindex" WHITESPACE " " - L_PAREN "(" - CONCURRENTLY_KW "concurrently" - WHITESPACE " " - LITERAL - TRUE_KW "true" - COMMA "," - WHITESPACE " " - TABLESPACE_KW "tablespace" - WHITESPACE " " - NAME - IDENT "fooo" - COMMA "," - WHITESPACE " " - VERBOSE_KW "verbose" - WHITESPACE " " - LITERAL - FALSE_KW "false" - R_PAREN ")" + REINDEX_OPTION_LIST + L_PAREN "(" + REINDEX_OPTION + CONCURRENTLY_KW "concurrently" + WHITESPACE " " + LITERAL + TRUE_KW "true" + COMMA "," + WHITESPACE " " + REINDEX_OPTION + TABLESPACE_KW "tablespace" + WHITESPACE " " + NAME + IDENT "fooo" + COMMA "," + WHITESPACE " " + REINDEX_OPTION + VERBOSE_KW "verbose" + WHITESPACE " " + LITERAL + FALSE_KW "false" + R_PAREN ")" WHITESPACE " " DATABASE_KW "database" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap index 2f3b7889e..6725af127 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap @@ -2553,6 +2553,58 @@ SOURCE_FILE R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- between asymmetric (inclusive of the range endpoints, explicit default)" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BETWEEN_EXPR + LITERAL + INT_NUMBER "2" + WHITESPACE " " + BETWEEN_KW "between" + WHITESPACE " " + ASYMMETRIC_KW "asymmetric" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + AND_KW "and" + WHITESPACE " " + LITERAL + INT_NUMBER "3" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- not between asymmetric (not between, explicit default)" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + BETWEEN_EXPR + LITERAL + INT_NUMBER "2" + WHITESPACE " " + NOT_KW "not" + WHITESPACE " " + BETWEEN_KW "between" + WHITESPACE " " + ASYMMETRIC_KW "asymmetric" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + AND_KW "and" + WHITESPACE " " + LITERAL + INT_NUMBER "3" + SEMICOLON ";" + WHITESPACE "\n\n" COMMENT "-- between symmetric (between, after sorting the two endpoint values)" WHITESPACE "\n" SELECT diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index 310b044e7..dd15d7e4e 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -13,7 +13,7 @@ pub struct Absolute { } impl Absolute { #[inline] - pub fn literal(&self) -> Option { + pub fn expr(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -3347,7 +3347,7 @@ pub struct Backward { } impl Backward { #[inline] - pub fn literal(&self) -> Option { + pub fn expr(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -3439,6 +3439,10 @@ impl BetweenExpr { support::token(&self.syntax, SyntaxKind::AND_KW) } #[inline] + pub fn asymmetric_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ASYMMETRIC_KW) + } + #[inline] pub fn between_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::BETWEEN_KW) } @@ -6425,7 +6429,7 @@ impl CreateTableAs { support::child(&self.syntax) } #[inline] - pub fn query(&self) -> Option { + pub fn query(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -10198,7 +10202,7 @@ pub struct ExceptTableClause { } impl ExceptTableClause { #[inline] - pub fn relation_names(&self) -> AstChildren { + pub fn except_table_names(&self) -> AstChildren { support::children(&self.syntax) } #[inline] @@ -10219,6 +10223,21 @@ impl ExceptTableClause { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ExceptTableName { + pub(crate) syntax: SyntaxNode, +} +impl ExceptTableName { + #[inline] + pub fn relation_name(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn table_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::TABLE_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExceptTables { pub(crate) syntax: SyntaxNode, @@ -10635,19 +10654,19 @@ impl Fetch { support::child(&self.syntax) } #[inline] - pub fn first(&self) -> Option { + pub fn expr(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn forward(&self) -> Option { + pub fn first(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn last(&self) -> Option { + pub fn forward(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn literal(&self) -> Option { + pub fn last(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -10966,7 +10985,7 @@ pub struct Forward { } impl Forward { #[inline] - pub fn literal(&self) -> Option { + pub fn expr(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -14102,19 +14121,19 @@ impl Move { support::child(&self.syntax) } #[inline] - pub fn first(&self) -> Option { + pub fn expr(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn forward(&self) -> Option { + pub fn first(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn last(&self) -> Option { + pub fn forward(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn literal(&self) -> Option { + pub fn last(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -14178,6 +14197,10 @@ pub struct NamedArg { pub(crate) syntax: SyntaxNode, } impl NamedArg { + #[inline] + pub fn colon_eq(&self) -> Option { + support::child(&self.syntax) + } #[inline] pub fn expr(&self) -> Option { support::child(&self.syntax) @@ -16743,6 +16766,10 @@ impl Reindex { support::child(&self.syntax) } #[inline] + pub fn reindex_option_list(&self) -> Option { + support::child(&self.syntax) + } + #[inline] pub fn semicolon_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::SEMICOLON) } @@ -16776,6 +16803,52 @@ impl Reindex { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ReindexOption { + pub(crate) syntax: SyntaxNode, +} +impl ReindexOption { + #[inline] + pub fn literal(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn name(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn concurrently_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::CONCURRENTLY_KW) + } + #[inline] + pub fn tablespace_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::TABLESPACE_KW) + } + #[inline] + pub fn verbose_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::VERBOSE_KW) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ReindexOptionList { + pub(crate) syntax: SyntaxNode, +} +impl ReindexOptionList { + #[inline] + pub fn reindex_options(&self) -> AstChildren { + support::children(&self.syntax) + } + #[inline] + pub fn l_paren_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::L_PAREN) + } + #[inline] + pub fn r_paren_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::R_PAREN) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RelationName { pub(crate) syntax: SyntaxNode, @@ -16809,7 +16882,7 @@ pub struct Relative { } impl Relative { #[inline] - pub fn literal(&self) -> Option { + pub fn expr(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -19704,6 +19777,26 @@ pub struct Table { pub(crate) syntax: SyntaxNode, } impl Table { + #[inline] + pub fn fetch_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn limit_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn locking_clauses(&self) -> AstChildren { + support::children(&self.syntax) + } + #[inline] + pub fn offset_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn order_by_clause(&self) -> Option { + support::child(&self.syntax) + } #[inline] pub fn relation_name(&self) -> Option { support::child(&self.syntax) @@ -20370,6 +20463,10 @@ impl Update { support::child(&self.syntax) } #[inline] + pub fn where_current_of(&self) -> Option { + support::child(&self.syntax) + } + #[inline] pub fn with_clause(&self) -> Option { support::child(&self.syntax) } @@ -20590,6 +20687,22 @@ pub struct Values { pub(crate) syntax: SyntaxNode, } impl Values { + #[inline] + pub fn fetch_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn limit_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn offset_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn order_by_clause(&self) -> Option { + support::child(&self.syntax) + } #[inline] pub fn row_list(&self) -> Option { support::child(&self.syntax) @@ -21852,6 +21965,12 @@ pub enum Constraint { UniqueConstraint(UniqueConstraint), } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum CreateTableAsQuery { + Execute(Execute), + SelectVariant(SelectVariant), +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ElementTableLabelAndProperties { LabelAndPropertiesList(LabelAndPropertiesList), @@ -27372,6 +27491,24 @@ impl AstNode for ExceptTableClause { &self.syntax } } +impl AstNode for ExceptTableName { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::EXCEPT_TABLE_NAME + } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} impl AstNode for ExceptTables { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -32304,6 +32441,42 @@ impl AstNode for Reindex { &self.syntax } } +impl AstNode for ReindexOption { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::REINDEX_OPTION + } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl AstNode for ReindexOptionList { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::REINDEX_OPTION_LIST + } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} impl AstNode for RelationName { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -37411,6 +37584,38 @@ impl From for Constraint { Constraint::UniqueConstraint(node) } } +impl AstNode for CreateTableAsQuery { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, SyntaxKind::EXECUTE) + } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + SyntaxKind::EXECUTE => CreateTableAsQuery::Execute(Execute { syntax }), + _ => { + if let Some(result) = SelectVariant::cast(syntax) { + return Some(CreateTableAsQuery::SelectVariant(result)); + } + return None; + } + }; + Some(res) + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + match self { + CreateTableAsQuery::Execute(it) => &it.syntax, + CreateTableAsQuery::SelectVariant(it) => it.syntax(), + } + } +} +impl From for CreateTableAsQuery { + #[inline] + fn from(node: Execute) -> CreateTableAsQuery { + CreateTableAsQuery::Execute(node) + } +} impl AstNode for ElementTableLabelAndProperties { #[inline] fn can_cast(kind: SyntaxKind) -> bool { diff --git a/crates/squawk_syntax/src/ast/node_ext.rs b/crates/squawk_syntax/src/ast/node_ext.rs index 2acaa719b..c19e4707c 100644 --- a/crates/squawk_syntax/src/ast/node_ext.rs +++ b/crates/squawk_syntax/src/ast/node_ext.rs @@ -569,6 +569,16 @@ impl ast::WithQuery { } } +impl ast::CreateTableAsQuery { + #[inline] + pub fn select_variant(&self) -> Option { + match self { + ast::CreateTableAsQuery::Execute(_) => None, + ast::CreateTableAsQuery::SelectVariant(select_variant) => Some(select_variant.clone()), + } + } +} + impl ast::SelectVariant { #[inline] pub fn target_list(&self) -> Option { diff --git a/crates/squawk_syntax/src/postgresql.ungram b/crates/squawk_syntax/src/postgresql.ungram index 47c8a8fc4..0cacf5725 100644 --- a/crates/squawk_syntax/src/postgresql.ungram +++ b/crates/squawk_syntax/src/postgresql.ungram @@ -367,7 +367,7 @@ Literal = ) NamedArg = - NameRef FatArrow Expr + NameRef (FatArrow | ColonEq) Expr JsonFormatClause = 'format' 'json' JsonEncodingClause? @@ -1364,11 +1364,20 @@ RowList = Values = WithClause? - 'values' RowList ';'? + 'values' RowList + OrderByClause? + LimitClause? + OffsetClause? + FetchClause? ';'? Table = WithClause? - 'table' RelationName ';'? + 'table' RelationName + OrderByClause? + LockingClause* + LimitClause? + FetchClause? + OffsetClause? ';'? Insert = WithClause? @@ -1446,7 +1455,7 @@ Update = Alias? SetClause FromClause? - WhereClause? + (WhereClause | WhereCurrentOf)? ReturningClause? ';'? ForPortionOf = @@ -1545,6 +1554,10 @@ WithData = WithNoData = 'with' 'no' 'data' +CreateTableAsQuery = + SelectVariant +| Execute + CreateTableAs = 'create' Persistence? @@ -1557,7 +1570,7 @@ CreateTableAs = OnCommit? Tablespace? 'as' - query:SelectVariant + query:CreateTableAsQuery (WithData | WithNoData)? ';'? CreateMaterializedView = @@ -1600,7 +1613,7 @@ SliceExpr = base:Expr '[' start:Expr? ':' end:Expr? ']' BetweenExpr = - target:Expr 'not'? 'between' 'symmetric'? start:Expr 'and' end:Expr + target:Expr 'not'? 'between' ('symmetric' | 'asymmetric')? start:Expr 'and' end:Expr JsonTableColumn = Name 'for' 'ordinality' @@ -2874,7 +2887,11 @@ CreatePublication = WithParams? ';'? ExceptTableClause = - 'except' 'table' '(' (RelationName (',' RelationName)*) ')' + 'except' '(' 'table' ( ExceptTableName (',' ExceptTableName)*) ')' + +ExceptTableName = + 'table'? + RelationName PublicationObject = 'table' 'only'? (Path | '(' Path ')') '*'? ColumnList? WhereConditionClause? @@ -3340,7 +3357,14 @@ SetTransaction = ) ';'? Reindex = - 'reindex' ('table' | 'index' | 'schema' | 'database' | 'system')? 'concurrently'? Path? ';'? + 'reindex' ReindexOptionList? ('table' | 'index' | 'schema' | 'database' | 'system')? 'concurrently'? Path? ';'? + +ReindexOptionList = + '(' (ReindexOption (',' ReindexOption)*)? ')' + +ReindexOption = + ('concurrently' | 'verbose') Literal? +| 'tablespace' Name CreateView = 'create' OrReplace? Persistence? 'recursive'? 'view' Path ColumnList? @@ -3375,7 +3399,7 @@ CheckpointOption = Name Expr? Deallocate = - 'deallocate' ('prepare' NameRef | 'all') ';'? + 'deallocate' 'prepare'? (NameRef | 'all') ';'? Load = 'load' Literal ';'? @@ -3411,7 +3435,7 @@ Move = | Absolute | Forward | Backward - | Literal + | Expr )? ('from' | 'in')? NameRef ';'? @@ -3427,7 +3451,7 @@ Fetch = | Absolute | Forward | Backward - | Literal + | Expr )? ('from' | 'in')? NameRef ';'? @@ -3444,16 +3468,16 @@ Last = 'last' Relative = - 'relative' Literal + 'relative' Expr Absolute = - 'absolute' Literal + 'absolute' Expr Forward = - 'forward' ('all' | Literal)? + 'forward' ('all' | Expr)? Backward = - 'backward' ('all' | Literal)? + 'backward' ('all' | Expr)? Close = 'close' ('all' | NameRef) ';'? diff --git a/crates/xtask/src/codegen.rs b/crates/xtask/src/codegen.rs index 6d0439750..d138ecc6f 100644 --- a/crates/xtask/src/codegen.rs +++ b/crates/xtask/src/codegen.rs @@ -943,8 +943,10 @@ const KEYWORD_PHRASES: &[&str] = &[ // Multi-word entries must come before their single-word components so the // regex engine matches the longest form first. const KEYWORD_OPERATORS: &[&str] = &[ + "not between asymmetric", "not between symmetric", "is not distinct from", + "between asymmetric", "between symmetric", "is distinct from", "not similar to", diff --git a/squawk-vscode/syntaxes/pgsql.tmLanguage.json b/squawk-vscode/syntaxes/pgsql.tmLanguage.json index 3d83fb955..cdd1dd92a 100644 --- a/squawk-vscode/syntaxes/pgsql.tmLanguage.json +++ b/squawk-vscode/syntaxes/pgsql.tmLanguage.json @@ -164,7 +164,7 @@ "name": "keyword.other.pgsql" }, { - "match": "(?i)\\b(not\\s+between\\s+symmetric|is\\s+not\\s+distinct\\s+from|between\\s+symmetric|is\\s+distinct\\s+from|not\\s+similar\\s+to|at\\s+time\\s+zone|not\\s+between|similar\\s+to|not\\s+ilike|not\\s+like|overlaps|between|collate|notnull|is\\s+not|not\\s+in|isnull|ilike|like|and|not|in|is|or)\\b", + "match": "(?i)\\b(not\\s+between\\s+asymmetric|not\\s+between\\s+symmetric|is\\s+not\\s+distinct\\s+from|between\\s+asymmetric|between\\s+symmetric|is\\s+distinct\\s+from|not\\s+similar\\s+to|at\\s+time\\s+zone|not\\s+between|similar\\s+to|not\\s+ilike|not\\s+like|overlaps|between|collate|notnull|is\\s+not|not\\s+in|isnull|ilike|like|and|not|in|is|or)\\b", "name": "keyword.operator.pgsql" }, {