From 534aaa99d777cfde29d8de4b55b468a35c979ffe Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Mon, 23 Jun 2025 14:38:07 -0400 Subject: [PATCH 1/4] parser: improve error recovery also fill out the AST more --- .../src/rules/prefer_timestamptz.rs | 6 +- .../src/generated/syntax_kind.rs | 1 + crates/squawk_parser/src/grammar.rs | 798 +++++++----------- .../tests/data/err/alter_table.sql | 8 + .../squawk_parser/tests/data/err/select.sql | 7 + .../snapshots/tests__alter_table_err.snap | 86 ++ .../snapshots/tests__alter_table_ok.snap | 199 +++-- .../tests/snapshots/tests__comment_ok.snap | 3 +- .../snapshots/tests__create_aggregate_ok.snap | 3 +- .../snapshots/tests__create_table_err.snap | 89 +- .../snapshots/tests__create_table_ok.snap | 64 +- .../tests__create_table_pg17_ok.snap | 56 +- .../snapshots/tests__drop_aggregate_ok.snap | 6 +- .../tests/snapshots/tests__misc_ok.snap | 4 +- .../snapshots/tests__security_label_ok.snap | 6 +- .../tests/snapshots/tests__select_err.snap | 77 +- .../tests/snapshots/tests__select_ok.snap | 20 +- .../squawk_syntax/src/ast/generated/nodes.rs | 43 +- crates/squawk_syntax/src/lib.rs | 95 +++ crates/squawk_syntax/src/postgresql.ungram | 6 +- ...est__drop_aggregate_params_validation.snap | 6 +- ...syntax__test__join_clauses_validation.snap | 10 +- 22 files changed, 886 insertions(+), 707 deletions(-) diff --git a/crates/squawk_linter/src/rules/prefer_timestamptz.rs b/crates/squawk_linter/src/rules/prefer_timestamptz.rs index efe2ee96..6f35a0f4 100644 --- a/crates/squawk_linter/src/rules/prefer_timestamptz.rs +++ b/crates/squawk_linter/src/rules/prefer_timestamptz.rs @@ -32,10 +32,8 @@ pub fn is_not_allowed_timestamp(ty: &ast::Type) -> bool { ast::Type::BitType(_) => false, ast::Type::DoubleType(_) => false, ast::Type::TimeType(time_type) => { - if let Some(ty_name) = time_type.name_ref() { - if ty_name.text() == "timestamp" && time_type.with_timezone().is_none() { - return true; - } + if time_type.timestamp_token().is_some() && time_type.with_timezone().is_none() { + return true; } false } diff --git a/crates/squawk_parser/src/generated/syntax_kind.rs b/crates/squawk_parser/src/generated/syntax_kind.rs index 30eef395..c6b6855f 100644 --- a/crates/squawk_parser/src/generated/syntax_kind.rs +++ b/crates/squawk_parser/src/generated/syntax_kind.rs @@ -811,6 +811,7 @@ pub enum SyntaxKind { JOIN_LEFT, JOIN_RIGHT, JOIN_TYPE, + JOIN_USING_CLAUSE, JSON_BEHAVIOR_CLAUSE, JSON_FORMAT_CLAUSE, JSON_KEYS_UNIQUE_CLAUSE, diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index 2d106347..42731705 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -46,34 +46,26 @@ fn literal(p: &mut Parser<'_>) -> Option { // array[1,2,3] // array(select 1) fn array_expr(p: &mut Parser<'_>, m: Option) -> CompletedMarker { + assert!(p.at(L_PAREN) || p.at(L_BRACK)); let m = m.unwrap_or_else(|| p.start()); - // `[` or `(` - let closing = if p.eat(L_PAREN) { - R_PAREN + if p.at(L_PAREN) && p.nth_at_ts(1, SELECT_FIRST) { + p.expect(L_PAREN); + select(p, None, &SelectRestrictions::default()); + p.expect(R_PAREN); } else { - p.expect(L_BRACK); - R_BRACK - }; - while !p.at(EOF) && !p.at(closing) { - if p.at_ts(SELECT_FIRST) - && (select(p, None, &SelectRestrictions::default()).is_none() - || p.at(EOF) - || p.at(closing)) - { - break; - } - if expr(p).is_none() { - break; - } - if p.at(COMMA) && p.nth_at(1, closing) { - p.err_and_bump("unexpected trailing comma"); - break; - } - if !p.at(closing) && !p.expect(COMMA) { - break; - } + // `[` or `(` + let closing = if p.at(L_PAREN) { R_PAREN } else { R_BRACK }; + let opening = if p.at(L_PAREN) { L_PAREN } else { L_BRACK }; + delimited( + p, + opening, + closing, + COMMA, + || "unexpected comma".to_string(), + EXPR_FIRST, + |p| opt_expr(p).is_some(), + ); } - p.expect(closing); m.complete(p, ARRAY_EXPR) } @@ -277,14 +269,7 @@ fn overlay_fn(p: &mut Parser<'_>) -> CompletedMarker { p.error("expected an expression"); } } else if p.eat(COMMA) { - while !p.at(EOF) { - if expr(p).is_none() { - p.error("expected an expression"); - } - if !p.eat(COMMA) { - break; - } - } + expr_list(p); } }) } @@ -392,15 +377,7 @@ fn substring_fn(p: &mut Parser<'_>) -> CompletedMarker { } } _ if p.eat(COMMA) => { - // normal function call - while !p.at(EOF) { - if expr(p).is_none() { - p.error("expected an expression"); - } - if !p.eat(COMMA) { - break; - } - } + expr_list(p); } _ => {} } @@ -1428,7 +1405,7 @@ fn delimited( first_set: TokenSet, mut parser: impl FnMut(&mut Parser<'_>) -> bool, ) { - p.bump(bra); + p.expect(bra); while !p.at(ket) && !p.at(EOF) { if p.at(delim) { // Recover if an argument is missing and only got a delimiter, @@ -1718,29 +1695,14 @@ fn opt_type_name_with(p: &mut Parser<'_>, type_args_enabled: bool) -> Option char_type(p), TIMESTAMP_KW | TIME_KW => { - let name_ref = p.start(); p.bump_any(); - name_ref.complete(p, NAME_REF); if p.eat(L_PAREN) { if expr(p).is_none() { p.error("expected an expression"); } p.expect(R_PAREN); } - let m = p.start(); - if p.at(WITH_KW) || p.at(WITHOUT_KW) { - let kind = if p.eat(WITH_KW) { - WITH_TIMEZONE - } else { - p.bump(WITHOUT_KW); - WITHOUT_TIMEZONE - }; - p.expect(TIME_KW); - p.expect(ZONE_KW); - m.complete(p, kind); - } else { - m.abandon(p); - } + opt_with_timezone(p); TIME_TYPE } INTERVAL_KW => { @@ -1765,6 +1727,23 @@ fn opt_type_name_with(p: &mut Parser<'_>, type_args_enabled: bool) -> Option) { + let m = p.start(); + if p.at(WITH_KW) || p.at(WITHOUT_KW) { + let kind = if p.eat(WITH_KW) { + WITH_TIMEZONE + } else { + p.bump(WITHOUT_KW); + WITHOUT_TIMEZONE + }; + p.expect(TIME_KW); + p.expect(ZONE_KW); + m.complete(p, kind); + } else { + m.abandon(p); + } +} + fn opt_type_name(p: &mut Parser<'_>) -> bool { opt_type_name_with(p, true).is_some() } @@ -2125,6 +2104,13 @@ fn expr(p: &mut Parser) -> Option { expr_bp(p, 1, &Restrictions::default()) } +fn opt_expr(p: &mut Parser<'_>) -> Option { + if !p.at_ts(EXPR_FIRST) { + return None; + } + expr(p) +} + // Based on the Postgres grammar b_expr, it's expr without `AND`, `NOT`, `IS`, // and `IN` #[must_use] @@ -2693,7 +2679,7 @@ const JOIN_TYPE_FIRST: TokenSet = // LEFT [ OUTER ] JOIN // RIGHT [ OUTER ] JOIN // FULL [ OUTER ] JOIN -fn join_type(p: &mut Parser<'_>) -> Option { +fn join_type(p: &mut Parser<'_>) { assert!(p.at_ts(JOIN_TYPE_FIRST)); let m = p.start(); let kind = match p.current() { @@ -2727,10 +2713,11 @@ fn join_type(p: &mut Parser<'_>) -> Option { } _ => { p.error("expected join type"); - return None; + m.abandon(p); + return; } }; - Some(m.complete(p, kind)) + m.complete(p, kind); } const JOIN_FIRST: TokenSet = TokenSet::new(&[NATURAL_KW, CROSS_KW]).union(JOIN_TYPE_FIRST); @@ -3162,43 +3149,37 @@ fn join(p: &mut Parser<'_>) { assert!(p.at_ts(JOIN_FIRST)); let m = p.start(); p.eat(NATURAL_KW); - if join_type(p).is_none() { - p.error("expected join type"); - } + join_type(p); if !opt_from_item(p) { p.error("expected from_item"); } if p.at(ON_KW) { - let m = p.start(); - p.bump(ON_KW); - if expr(p).is_none() { - p.error("expected an expression"); - } - m.complete(p, ON_CLAUSE); + on_clause(p); } else if p.at(USING_KW) { - let m = p.start(); - // USING ( join_column [, ...] ) - p.expect(USING_KW); - if p.at(L_PAREN) { - column_list(p); - } else { - p.error("expected L_PAREN"); - } - { - let m = p.start(); - // [ AS join_using_alias ] - if p.eat(AS_KW) { - name(p); - m.complete(p, ALIAS); - } else { - m.abandon(p); - } - } - m.complete(p, USING_CLAUSE); + join_using_clause(p); } m.complete(p, JOIN); } +fn on_clause(p: &mut Parser<'_>) { + let m = p.start(); + p.bump(ON_KW); + if expr(p).is_none() { + p.error("expected an expression"); + } + m.complete(p, ON_CLAUSE); +} + +fn join_using_clause(p: &mut Parser<'_>) { + assert!(p.at(USING_KW)); + let m = p.start(); + // USING ( join_column [, ...] ) + p.expect(USING_KW); + column_list(p); + opt_alias(p); + m.complete(p, JOIN_USING_CLAUSE); +} + #[must_use] fn opt_numeric_literal(p: &mut Parser<'_>) -> Option { if p.at_ts(NUMERIC_FIRST) { @@ -3454,31 +3435,34 @@ fn opt_index_parameters(p: &mut Parser<'_>) { // referential_action in a FOREIGN KEY/REFERENCES constraint is: // { NO ACTION | RESTRICT | CASCADE | SET NULL [ ( column_name [, ... ] ) ] | SET DEFAULT [ ( column_name [, ... ] ) ] } -fn referential_action(p: &mut Parser<'_>) -> bool { - if p.eat(NO_KW) { - p.expect(ACTION_KW) - } else if opt_cascade_or_restrict(p) { - true - } else if p.at(SET_KW) && p.nth_at(1, NULL_KW) { - p.expect(SET_KW); - p.expect(NULL_KW); - opt_column_list(p); - true - } else if p.eat(SET_KW) { - p.expect(DEFAULT_KW); - if p.eat(L_PAREN) { - // column_name [, ... ] - while !p.at(EOF) && !p.at(COMMA) { - name_ref(p); - if !p.eat(COMMA) { - break; - } - } - return p.expect(R_PAREN); +fn referential_action(p: &mut Parser<'_>) { + match p.current() { + NO_KW => { + let m = p.start(); + p.bump(NO_KW); + p.expect(ACTION_KW); + m.complete(p, NO_ACTION); + } + CASCADE_KW | RESTRICT_KW => { + opt_cascade_or_restrict(p); + } + SET_KW if p.nth_at(1, NULL_KW) => { + let m = p.start(); + p.expect(SET_KW); + p.expect(NULL_KW); + opt_column_list(p); + m.complete(p, SET_NULL_COLUMNS); + } + SET_KW => { + let m = p.start(); + p.bump(SET_KW); + p.expect(DEFAULT_KW); + opt_column_list(p); + m.complete(p, SET_DEFAULT_COLUMNS); + } + _ => { + p.error("expected foreign key action"); } - true - } else { - false } } @@ -3531,6 +3515,15 @@ fn opt_column_constraint(p: &mut Parser<'_>) -> Option { } } +// [ column_constraint [ ... ] ] +fn opt_column_constraint_list(p: &mut Parser<'_>) { + while !p.at(EOF) { + if opt_column_constraint(p).is_none() { + break; + } + } +} + // { NOT NULL | // NULL | // CHECK ( expression ) [ NO INHERIT ] | @@ -3634,7 +3627,7 @@ fn opt_constraint_inner(p: &mut Parser<'_>) -> Option { p.expect(R_PAREN); } opt_match_type(p); - foreign_key_actions(p); + opt_foreign_key_actions(p); REFERENCES_CONSTRAINT } _ => { @@ -3689,27 +3682,39 @@ fn opt_no_inherit(p: &mut Parser<'_>) { // [ ON DELETE referential_action ] // [ ON UPDATE referential_action ] -fn foreign_key_actions(p: &mut Parser<'_>) { +fn opt_foreign_key_actions(p: &mut Parser<'_>) { // [ ON DELETE referential_action ] if p.at(ON_KW) && p.nth_at(1, DELETE_KW) { - p.expect(ON_KW); - p.expect(DELETE_KW); - referential_action(p); + on_delete_action(p); } // [ ON UPDATE referential_action ] if p.at(ON_KW) && p.nth_at(1, UPDATE_KW) { - p.expect(ON_KW); - p.expect(UPDATE_KW); - referential_action(p); + on_update_action(p); } // [ ON DELETE referential_action ] if p.at(ON_KW) && p.nth_at(1, DELETE_KW) { - p.expect(ON_KW); - p.expect(DELETE_KW); - referential_action(p); + on_delete_action(p); } } +fn on_update_action(p: &mut Parser<'_>) { + assert!(p.at(ON_KW)); + let m = p.start(); + p.expect(ON_KW); + p.expect(UPDATE_KW); + referential_action(p); + m.complete(p, ON_UPDATE_ACTION); +} + +fn on_delete_action(p: &mut Parser<'_>) { + assert!(p.at(ON_KW)); + let m = p.start(); + p.expect(ON_KW); + p.expect(DELETE_KW); + referential_action(p); + m.complete(p, ON_DELETE_ACTION); +} + const LIKE_OPTION: TokenSet = TokenSet::new(&[ COMMENTS_KW, COMPRESSION_KW, @@ -3877,38 +3882,10 @@ fn table_constraint(p: &mut Parser<'_>) -> CompletedMarker { // EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] | EXCLUDE_KW => { p.bump(EXCLUDE_KW); - if p.at(USING_KW) { - let m = p.start(); - p.bump(USING_KW); - name_ref(p); - m.complete(p, CONSTRAINT_INDEX_METHOD); - } - let m = p.start(); - p.expect(L_PAREN); - while !p.at(EOF) && !p.at(R_PAREN) { - index_elem(p); - p.expect(WITH_KW); - // support: - // with > - // with foo.bar.buzz.> - operator(p); - if !p.eat(COMMA) { - break; - } - } - p.expect(R_PAREN); - m.complete(p, CONSTRAINT_EXCLUSIONS); + opt_constraint_index_method(p); + constraint_exclusions(p); opt_index_parameters(p); - if p.at(WHERE_KW) { - let m = p.start(); - p.bump(WHERE_KW); - p.expect(L_PAREN); - if expr(p).is_none() { - p.error("expected expr"); - } - p.expect(R_PAREN); - m.complete(p, CONSTRAINT_WHERE_CLAUSE); - } + opt_constraint_where_clause(p); EXCLUDE_CONSTRAINT } NOT_KW => { @@ -3928,7 +3905,7 @@ fn table_constraint(p: &mut Parser<'_>) -> CompletedMarker { path_name_ref(p); opt_column_list(p); opt_match_type(p); - foreign_key_actions(p); + opt_foreign_key_actions(p); FOREIGN_KEY_CONSTRAINT } }; @@ -3952,6 +3929,46 @@ fn opt_nulls_not_distinct(p: &mut Parser<'_>) { } } +fn opt_constraint_where_clause(p: &mut Parser<'_>) { + if p.at(WHERE_KW) { + let m = p.start(); + p.bump(WHERE_KW); + p.expect(L_PAREN); + if expr(p).is_none() { + p.error("expected expr"); + } + p.expect(R_PAREN); + m.complete(p, CONSTRAINT_WHERE_CLAUSE); + } +} + +fn constraint_exclusions(p: &mut Parser<'_>) { + let m = p.start(); + p.expect(L_PAREN); + while !p.at(EOF) && !p.at(R_PAREN) { + index_elem(p); + p.expect(WITH_KW); + // support: + // with > + // with foo.bar.buzz.> + operator(p); + if !p.eat(COMMA) { + break; + } + } + p.expect(R_PAREN); + m.complete(p, CONSTRAINT_EXCLUSIONS); +} + +fn opt_constraint_index_method(p: &mut Parser<'_>) { + if p.at(USING_KW) { + let m = p.start(); + p.bump(USING_KW); + name_ref(p); + m.complete(p, CONSTRAINT_INDEX_METHOD); + } +} + fn opt_without_overlaps(p: &mut Parser<'_>) { if p.eat(WITHOUT_KW) { p.expect(OVERLAPS_KW); @@ -4114,12 +4131,7 @@ fn col_def(p: &mut Parser<'_>, t: ColDefType) -> Option { } } opt_collate(p); - // [ column_constraint [ ... ] ] - while !p.at(EOF) { - if opt_column_constraint(p).is_none() { - break; - } - } + opt_column_constraint_list(p); Some(m.complete(p, COLUMN)) } @@ -4220,48 +4232,52 @@ fn group_by_list(p: &mut Parser<'_>) { // ambiguity, a GROUP BY name will be interpreted as an input-column name // rather than an output column name. while !p.at(EOF) && !p.at(SEMICOLON) { - let m = p.start(); - let kind = match p.current() { - ROLLUP_KW => { - p.bump_any(); - p.expect(L_PAREN); - if !expr_list(p) { - p.error("expected expression list"); - }; - p.expect(R_PAREN); - GROUPING_ROLLUP - } - CUBE_KW => { - p.bump_any(); - p.expect(L_PAREN); - if !expr_list(p) { - p.error("expected expression list"); - }; - p.expect(R_PAREN); - GROUPING_CUBE - } - GROUPING_KW if p.nth_at(1, SETS_KW) => { - p.bump(GROUPING_KW); - p.bump(SETS_KW); - p.expect(L_PAREN); - group_by_list(p); - p.expect(R_PAREN); - GROUPING_SETS - } - _ => { - if expr(p).is_none() { - p.error("expected an expression"); - } - GROUPING_EXPR - } - }; - m.complete(p, kind); + group_by_item(p); if !p.eat(COMMA) { break; } } } +fn group_by_item(p: &mut Parser<'_>) { + let m = p.start(); + let kind = match p.current() { + ROLLUP_KW => { + p.bump_any(); + p.expect(L_PAREN); + if !expr_list(p) { + p.error("expected expression list"); + }; + p.expect(R_PAREN); + GROUPING_ROLLUP + } + CUBE_KW => { + p.bump_any(); + p.expect(L_PAREN); + if !expr_list(p) { + p.error("expected expression list"); + }; + p.expect(R_PAREN); + GROUPING_CUBE + } + GROUPING_KW if p.nth_at(1, SETS_KW) => { + p.bump(GROUPING_KW); + p.bump(SETS_KW); + p.expect(L_PAREN); + group_by_list(p); + p.expect(R_PAREN); + GROUPING_SETS + } + _ => { + if expr(p).is_none() { + p.error("expected an expression"); + } + GROUPING_EXPR + } + }; + m.complete(p, kind); +} + /// fn opt_having_clause(p: &mut Parser<'_>) -> Option { if !p.at(HAVING_KW) { @@ -4342,13 +4358,8 @@ fn window_definition(p: &mut Parser<'_>) -> Option { opt_ident(p); if p.eat(PARTITION_KW) { p.expect(BY_KW); - if expr(p).is_none() { - p.error("expected an expression"); - } - while p.eat(COMMA) && !p.at(EOF) { - if expr(p).is_none() { - p.error("expected an expression"); - } + if !expr_list(p) { + p.error("expected expression") } } opt_order_by_clause(p); @@ -4817,12 +4828,7 @@ fn partition_option(p: &mut Parser<'_>) { fn opt_inherits_tables(p: &mut Parser<'_>) { if p.eat(INHERITS_KW) { p.expect(L_PAREN); - while !p.at(EOF) && !p.at(COMMA) { - path_name_ref(p); - if !p.eat(COMMA) { - break; - } - } + path_name_ref_list(p); p.expect(R_PAREN); } } @@ -5949,10 +5955,7 @@ fn alter_policy(p: &mut Parser<'_>) -> CompletedMarker { name(p); } else { if p.eat(TO_KW) { - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); } if p.eat(USING_KW) { p.expect(L_PAREN); @@ -5973,6 +5976,13 @@ fn alter_policy(p: &mut Parser<'_>) -> CompletedMarker { m.complete(p, ALTER_POLICY) } +fn role_list(p: &mut Parser<'_>) { + role(p); + while !p.at(EOF) && p.eat(COMMA) { + role(p); + } +} + // ALTER OPERATOR FAMILY name USING index_method ADD // { OPERATOR strategy_number operator_name ( op_type, op_type ) // [ FOR SEARCH | FOR ORDER BY sort_family_name ] @@ -6155,10 +6165,7 @@ fn alter_materialized_view(p: &mut Parser<'_>) -> CompletedMarker { name_ref(p); if p.eat(OWNED_KW) { p.expect(BY_KW); - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); } p.expect(SET_KW); p.expect(TABLESPACE_KW); @@ -6284,10 +6291,7 @@ fn alter_index(p: &mut Parser<'_>) -> CompletedMarker { path_name_ref(p); if p.eat(OWNED_KW) { p.expect(BY_KW); - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); } p.expect(SET_KW); p.expect(TABLESPACE_KW); @@ -6369,10 +6373,7 @@ fn alter_group(p: &mut Parser<'_>) -> CompletedMarker { ADD_KW | DROP_KW => { p.bump_any(); p.expect(USER_KW); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); } RENAME_KW => { p.bump(RENAME_KW); @@ -7004,18 +7005,12 @@ fn alter_default_privileges(p: &mut Parser<'_>) -> CompletedMarker { if !p.eat(ROLE_KW) && !p.eat(USER_KW) { p.error("expected ROLE or USER"); } - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); } // [ IN SCHEMA schema_name [, ...] ] if p.eat(IN_KW) { - p.expect(SCHEMA_KW); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + p.expect(SCHEMA_KW); + name_ref_list(p); } match p.current() { GRANT_KW => { @@ -7024,10 +7019,7 @@ fn alter_default_privileges(p: &mut Parser<'_>) -> CompletedMarker { p.expect(ON_KW); privilege_target_type(p); p.expect(TO_KW); - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); if p.eat(WITH_KW) { p.expect(GRANT_KW); p.expect(OPTION_KW); @@ -7043,10 +7035,7 @@ fn alter_default_privileges(p: &mut Parser<'_>) -> CompletedMarker { p.expect(ON_KW); privilege_target_type(p); p.expect(FROM_KW); - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); opt_cascade_or_restrict(p); } _ => { @@ -7269,19 +7258,13 @@ fn alter_subscription(p: &mut Parser<'_>) -> CompletedMarker { SET_KW | ADD_KW => { p.bump_any(); p.expect(PUBLICATION_KW); - name(p); - while !p.at(EOF) && p.eat(COMMA) { - name(p); - } + name_list(p); opt_with_options_list(p); } DROP_KW => { p.bump(DROP_KW); p.expect(PUBLICATION_KW); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); opt_with_options_list(p); } REFRESH_KW => { @@ -7315,6 +7298,13 @@ fn alter_subscription(p: &mut Parser<'_>) -> CompletedMarker { m.complete(p, ALTER_SUBSCRIPTION) } +fn name_list(p: &mut Parser<'_>) { + name(p); + while !p.at(EOF) && p.eat(COMMA) { + name(p); + } +} + fn opt_with_options_list(p: &mut Parser<'_>) { if p.eat(WITH_KW) { attribute_list(p); @@ -8345,12 +8335,7 @@ fn create_domain(p: &mut Parser<'_>) -> CompletedMarker { p.eat(AS_KW); type_name(p); opt_collate(p); - while !p.at(EOF) { - // TODO: add validation to limit the types of constraints allowed - if opt_column_constraint(p).is_none() { - break; - } - } + opt_column_constraint_list(p); m.complete(p, CREATE_DOMAIN) } @@ -8447,9 +8432,7 @@ fn create_foreign_table(p: &mut Parser<'_>) -> CompletedMarker { } else { name_ref(p); opt_with_options(p); - while !p.at(EOF) && opt_column_constraint(p).is_some() { - // pass - } + opt_column_constraint_list(p); } while !p.at(EOF) && p.eat(COMMA) { if p.at_ts(TABLE_CONSTRAINT_FIRST) { @@ -8457,9 +8440,7 @@ fn create_foreign_table(p: &mut Parser<'_>) -> CompletedMarker { } else { name_ref(p); opt_with_options(p); - while !p.at(EOF) && opt_column_constraint(p).is_some() { - // pass - } + opt_column_constraint_list(p); } } p.expect(R_PAREN); @@ -8475,7 +8456,7 @@ fn create_foreign_table(p: &mut Parser<'_>) -> CompletedMarker { type_name(p); opt_options_list(p); opt_collate(p); - while !p.at(EOF) && opt_column_constraint(p).is_some() {} + opt_column_constraint_list(p); } if !p.eat(COMMA) { break; @@ -8802,10 +8783,7 @@ fn create_policy(p: &mut Parser<'_>) -> CompletedMarker { || p.eat(DELETE_KW); } if p.eat(TO_KW) { - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); } if p.eat(USING_KW) { p.expect(L_PAREN); @@ -9071,21 +9049,10 @@ fn create_statistics(p: &mut Parser<'_>) -> CompletedMarker { if !p.at(L_PAREN) && !p.at(ON_KW) { path_name(p); } - if p.eat(L_PAREN) { - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } - p.expect(R_PAREN); - } + opt_paren_name_ref_list(p); if p.eat(ON_KW) { - if expr(p).is_none() { - p.error("expected expression"); - } - while !p.at(EOF) && p.eat(COMMA) { - if expr(p).is_none() { - p.error("expected expression"); - } + if !expr_list(p) { + p.error("expected expression") } } p.expect(FROM_KW); @@ -9093,6 +9060,16 @@ fn create_statistics(p: &mut Parser<'_>) -> CompletedMarker { m.complete(p, CREATE_STATISTICS) } +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 + } +} + // CREATE SUBSCRIPTION subscription_name // CONNECTION 'conninfo' // PUBLICATION publication_name [, ...] @@ -9106,10 +9083,7 @@ fn create_subscription(p: &mut Parser<'_>) -> CompletedMarker { p.expect(CONNECTION_KW); string_literal(p); p.expect(PUBLICATION_KW); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); opt_with_params(p); m.complete(p, CREATE_SUBSCRIPTION) } @@ -9321,17 +9295,11 @@ fn opt_role_option(p: &mut Parser<'_>) -> bool { } else { p.error("expected GROUP or ROLE"); } - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); } ROLE_KW | ADMIN_KW | USER_KW => { p.bump_any(); - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); } SYSID_KW => { p.bump(SYSID_KW); @@ -9405,10 +9373,7 @@ fn drop_group(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(GROUP_KW); opt_if_exists(p); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); m.complete(p, DROP_GROUP) } @@ -9441,10 +9406,7 @@ fn drop_foreign_data(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DATA_KW); p.bump(WRAPPER_KW); opt_if_exists(p); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_FOREIGN_DATA_WRAPPER) } @@ -9457,10 +9419,7 @@ fn drop_foreign_table(p: &mut Parser<'_>) -> CompletedMarker { p.bump(FOREIGN_KW); p.bump(TABLE_KW); opt_if_exists(p); - path_name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - path_name_ref(p); - } + path_name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_FOREIGN_TABLE) } @@ -9531,10 +9490,7 @@ fn drop_collation(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(COLLATION_KW); opt_if_exists(p); - path_name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - path_name_ref(p); - } + path_name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_COLLATION) } @@ -9558,10 +9514,7 @@ fn drop_domain(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(DOMAIN_KW); opt_if_exists(p); - path_name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - path_name_ref(p); - } + path_name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_DOMAIN) } @@ -9586,10 +9539,7 @@ fn drop_extension(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(EXTENSION_KW); opt_if_exists(p); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_EXTENSION) } @@ -9602,10 +9552,7 @@ fn drop_materialized_view(p: &mut Parser<'_>) -> CompletedMarker { p.bump(MATERIALIZED_KW); p.bump(VIEW_KW); opt_if_exists(p); - path_name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - path_name_ref(p); - } + path_name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_MATERIALIZED_VIEW) } @@ -9674,10 +9621,7 @@ fn drop_owned(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(OWNED_KW); p.expect(BY_KW); - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_OWNED) } @@ -9721,10 +9665,7 @@ fn drop_publication(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(PUBLICATION_KW); opt_if_exists(p); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_PUBLICATION) } @@ -9736,10 +9677,7 @@ fn drop_role(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(ROLE_KW); opt_if_exists(p); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); m.complete(p, DROP_ROLE) } @@ -9782,10 +9720,7 @@ fn drop_sequence(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(SEQUENCE_KW); opt_if_exists(p); - path_name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - path_name_ref(p); - } + path_name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_SEQUENCE) } @@ -9797,10 +9732,7 @@ fn drop_server(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(SERVER_KW); opt_if_exists(p); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_SERVER) } @@ -9812,10 +9744,7 @@ fn drop_statistics(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(STATISTICS_KW); opt_if_exists(p); - path_name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - path_name_ref(p); - } + path_name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_STATISTICS) } @@ -9940,10 +9869,7 @@ fn drop_user(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(USER_KW); opt_if_exists(p); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); m.complete(p, DROP_USER) } @@ -10088,10 +10014,7 @@ fn import_foreign_schema(p: &mut Parser<'_>) -> CompletedMarker { } // ( table_name [, ...] ) p.expect(L_PAREN); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); p.expect(R_PAREN); } // FROM SERVER server_name @@ -10309,15 +10232,9 @@ fn reassign(p: &mut Parser<'_>) -> CompletedMarker { p.bump(REASSIGN_KW); p.expect(OWNED_KW); p.expect(BY_KW); - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); p.expect(TO_KW); - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); m.complete(p, REASSIGN) } @@ -10381,10 +10298,7 @@ fn grant(p: &mut Parser<'_>) -> CompletedMarker { } // TO role_specification [, ...] p.expect(TO_KW); - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); // TODO: need more validation here // [ WITH GRANT OPTION ] // [ WITH { ADMIN | INHERIT | SET } { OPTION | TRUE | FALSE } ] @@ -10423,10 +10337,7 @@ fn privilege_target(p: &mut Parser<'_>) { p.expect(IN_KW); p.expect(SCHEMA_KW); // schema_name [, ...] - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); } _ => p.error("expected TABLE"), } @@ -10434,10 +10345,7 @@ fn privilege_target(p: &mut Parser<'_>) { match p.current() { PARAMETER_KW => { p.bump(PARAMETER_KW); - path_name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - path_name_ref(p); - } + path_name_ref_list(p); } FUNCTION_KW | PROCEDURE_KW | ROUTINE_KW => { p.bump_any(); @@ -10461,18 +10369,12 @@ fn privilege_target(p: &mut Parser<'_>) { // no schema allowed for the name DATABASE_KW | TABLESPACE_KW | SCHEMA_KW | LANGUAGE_KW => { p.bump_any(); - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); } // these allow schema TABLE_KW | SEQUENCE_KW | DOMAIN_KW => { p.bump_any(); - path_name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - path_name_ref(p); - } + path_name_ref_list(p); } FOREIGN_KW => { p.bump(FOREIGN_KW); @@ -10481,10 +10383,7 @@ fn privilege_target(p: &mut Parser<'_>) { } else { p.expect(SERVER_KW); } - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); } LARGE_KW => { p.bump(LARGE_KW); @@ -10500,10 +10399,7 @@ fn privilege_target(p: &mut Parser<'_>) { } // table_name [, ...] _ if p.at_ts(COL_LABEL_FIRST) => { - path_name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - path_name_ref(p); - } + path_name_ref_list(p); } _ => (), } @@ -10563,10 +10459,7 @@ fn revoke(p: &mut Parser<'_>) -> CompletedMarker { } // FROM role_specification [, ...] p.expect(FROM_KW); - role(p); - while !p.at(EOF) && p.eat(COMMA) { - role(p); - } + role_list(p); // [ GRANTED BY role_specification ] opt_granted_by(p); opt_cascade_or_restrict(p); @@ -10821,15 +10714,7 @@ fn set_constraints(p: &mut Parser<'_>) -> CompletedMarker { p.bump(SET_KW); p.bump(CONSTRAINTS_KW); if !p.eat(ALL_KW) { - // TODO: generalize - path_name(p); - while !p.at(EOF) { - if p.eat(COMMA) { - path_name(p); - } else { - break; - } - } + path_name_list(p); } if !p.eat(DEFERRED_KW) && !p.eat(IMMEDIATE_KW) { p.error("expected DEFERRED or IMMEDIATE"); @@ -10837,6 +10722,17 @@ fn set_constraints(p: &mut Parser<'_>) -> CompletedMarker { m.complete(p, SET_CONSTRAINTS) } +fn path_name_list(p: &mut Parser<'_>) { + path_name(p); + while !p.at(EOF) { + if p.eat(COMMA) { + path_name(p); + } else { + break; + } + } +} + // SET [ SESSION | LOCAL ] ROLE role_name // SET [ SESSION | LOCAL ] ROLE NONE // RESET ROLE @@ -11034,16 +10930,7 @@ fn drop_view(p: &mut Parser<'_>) -> CompletedMarker { p.bump(VIEW_KW); opt_if_exists(p); // name [, ...] - // TODO: we need to generalize this pattern - path_name_ref(p); - if p.eat(COMMA) { - while !p.at(EOF) { - path_name_ref(p); - if !p.eat(COMMA) { - break; - } - } - } + path_name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_VIEW) } @@ -11580,19 +11467,13 @@ fn opt_copy_option_item(p: &mut Parser<'_>) -> bool { p.bump_any(); p.expect(NULL_KW); if !p.eat(STAR) { - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); } } QUOTE_KW | NULL_KW => { p.bump_any(); if !p.eat(STAR) { - name_ref(p); - while !p.at(EOF) && p.eat(COMMA) { - name_ref(p); - } + name_ref_list(p); } } _ => return false, @@ -11781,12 +11662,7 @@ fn create_trigger(p: &mut Parser<'_>) -> CompletedMarker { if p.eat(UPDATE_KW) { // [ OF column_name [, ... ] ] if p.eat(OF_KW) { - while !p.at(EOF) { - name_ref(p); - if !p.eat(COMMA) { - break; - } - } + name_ref_list(p); } } else if !(p.eat(INSERT_KW) || p.eat(DELETE_KW) || p.eat(TRUNCATE_KW)) { p.error("expected INSERT, UPDATE, DELETE, or TRUNCATE"); @@ -11867,17 +11743,7 @@ fn drop_schema(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(SCHEMA_KW); opt_if_exists(p); - let mut found_name = false; - while !p.at(EOF) { - name(p); - found_name = true; - if !p.eat(COMMA) { - break; - } - } - if !found_name { - p.error("expected name"); - } + name_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_SCHEMA) } @@ -12302,13 +12168,7 @@ fn drop_type(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(TYPE_KW); opt_if_exists(p); - // name [, ...] - while !p.at(EOF) { - path_name_ref(p); - if !p.eat(COMMA) { - break; - } - } + path_name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_TYPE) } @@ -12343,12 +12203,7 @@ fn drop_index(p: &mut Parser<'_>) -> CompletedMarker { p.eat(CONCURRENTLY_KW); opt_if_exists(p); // name [, ...] - while !p.at(EOF) { - path_name_ref(p); - if !p.eat(COMMA) { - break; - } - } + path_name_ref_list(p); opt_cascade_or_restrict(p); m.complete(p, DROP_INDEX) } @@ -13270,13 +13125,7 @@ fn alter_table(p: &mut Parser<'_>) -> CompletedMarker { // SET TABLESPACE new_tablespace [ NOWAIT ] if all_in_tablespace && p.eat(OWNED_KW) { p.expect(BY_KW); - while !p.at(EOF) { - // name - name_ref(p); - if !p.eat(COMMA) { - break; - } - } + name_ref_list(p); } opt_alter_table_action_list(p); m.complete(p, ALTER_TABLE) @@ -13539,12 +13388,7 @@ fn opt_alter_table_action(p: &mut Parser<'_>) -> Option { type_name(p); opt_options_list(p); opt_collate(p); - // [ column_constraint [ ... ] ] - while !p.at(EOF) { - if opt_column_constraint(p).is_none() { - break; - } - } + opt_column_constraint_list(p); ADD_COLUMN } } @@ -13662,7 +13506,6 @@ fn opt_alter_table_action(p: &mut Parser<'_>) -> Option { p.bump(ALTER_KW); // ALTER CONSTRAINT constraint_name [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] if p.eat(CONSTRAINT_KW) { - // name name_ref(p); opt_constraint_option_list(p); ALTER_CONSTRAINT @@ -13729,7 +13572,7 @@ fn col_label(p: &mut Parser<'_>) { // | ColLabel // | ColLabel '.' ColLabel '=' def_arg // | ColLabel '.' ColLabel -fn attribute_option(p: &mut Parser<'_>) -> bool { +fn opt_attribute_option(p: &mut Parser<'_>) -> bool { let m = p.start(); if !opt_col_label(p) { m.abandon(p); @@ -13991,16 +13834,15 @@ fn alter_column_option(p: &mut Parser<'_>) -> Option { fn attribute_list(p: &mut Parser<'_>) { let m = p.start(); - p.expect(L_PAREN); - while !p.at(EOF) && !p.at(R_PAREN) { - if !attribute_option(p) { - break; - } - if !p.eat(COMMA) { - break; - } - } - p.expect(R_PAREN); + delimited( + p, + L_PAREN, + R_PAREN, + COMMA, + || "unexpected comma".to_string(), + COL_LABEL_FIRST, + |p| opt_attribute_option(p), + ); m.complete(p, ATTRIBUTE_LIST); } diff --git a/crates/squawk_parser/tests/data/err/alter_table.sql b/crates/squawk_parser/tests/data/err/alter_table.sql index 9872c3be..dfdce101 100644 --- a/crates/squawk_parser/tests/data/err/alter_table.sql +++ b/crates/squawk_parser/tests/data/err/alter_table.sql @@ -10,3 +10,11 @@ validate constraint foo validate constraint b ; -- pg 18 only, via: https://www.depesz.com/2025/05/01/waiting-for-postgresql-18-allow-not-null-constraints-to-be-added-as-not-valid/ alter table public.copy_2 add constraint id_not_null not null id not valid; + +-- missing comma +alter foreign table t + alter c set (a b = 1); + +-- extra comma +alter foreign table t + alter c set (a, , b = 1); diff --git a/crates/squawk_parser/tests/data/err/select.sql b/crates/squawk_parser/tests/data/err/select.sql index 59736be4..e1b04a15 100644 --- a/crates/squawk_parser/tests/data/err/select.sql +++ b/crates/squawk_parser/tests/data/err/select.sql @@ -25,6 +25,13 @@ select 1 in c; -- type cast must use a string literal select numeric 1234; +-- missing comma +select array[1 2,3]; +-- extra comma +select array[1, ,3]; +-- trailing comma +select array[1,2,3,]; + -- regression test: this would cause the parser to get stuck & panic, now it -- warns about a missing semicolon select select; diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_table_err.snap b/crates/squawk_parser/tests/snapshots/tests__alter_table_err.snap index 121b9aaf..838cf0b6 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_table_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_table_err.snap @@ -120,6 +120,90 @@ SOURCE_FILE WHITESPACE " " VALID_KW "valid" SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- missing comma" + WHITESPACE "\n" + ALTER_FOREIGN_TABLE + ALTER_KW "alter" + WHITESPACE " " + FOREIGN_KW "foreign" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + RELATION_NAME + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE "\n " + ALTER_COLUMN + ALTER_KW "alter" + WHITESPACE " " + IDENT "c" + WHITESPACE " " + SET_OPTIONS + SET_KW "set" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + ATTRIBUTE_OPTION + NAME + IDENT "a" + WHITESPACE " " + ATTRIBUTE_OPTION + NAME + IDENT "b" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- extra comma" + WHITESPACE "\n" + ALTER_FOREIGN_TABLE + ALTER_KW "alter" + WHITESPACE " " + FOREIGN_KW "foreign" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + RELATION_NAME + PATH + PATH_SEGMENT + NAME_REF + IDENT "t" + WHITESPACE "\n " + ALTER_COLUMN + ALTER_KW "alter" + WHITESPACE " " + IDENT "c" + WHITESPACE " " + SET_OPTIONS + SET_KW "set" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + ATTRIBUTE_OPTION + NAME + IDENT "a" + COMMA "," + WHITESPACE " " + ERROR + COMMA "," + WHITESPACE " " + ATTRIBUTE_OPTION + NAME + IDENT "b" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + R_PAREN ")" + SEMICOLON ";" WHITESPACE "\n" --- ERROR@23: expected command, found ADD_KW @@ -127,3 +211,5 @@ ERROR@27: expected command, found COLUMN_KW ERROR@34: expected command, found IDENT ERROR@38: expected command, found BOOLEAN_KW ERROR@175: missing comma +ERROR@505: expected COMMA +ERROR@570: unexpected comma diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap index 990f0f75..c878f572 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap @@ -2685,21 +2685,25 @@ SOURCE_FILE WHITESPACE " " FULL_KW "full" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - NO_KW "no" - WHITESPACE " " - ACTION_KW "action" - WHITESPACE " " - ON_KW "on" - WHITESPACE " " - UPDATE_KW "update" - WHITESPACE " " - SET_KW "set" + ON_DELETE_ACTION + ON_KW "on" + WHITESPACE " " + DELETE_KW "delete" + WHITESPACE " " + NO_ACTION + NO_KW "no" + WHITESPACE " " + ACTION_KW "action" WHITESPACE " " - NULL_KW "null" + ON_UPDATE_ACTION + ON_KW "on" + WHITESPACE " " + UPDATE_KW "update" + WHITESPACE " " + SET_NULL_COLUMNS + SET_KW "set" + WHITESPACE " " + NULL_KW "null" SEMICOLON ";" WHITESPACE "\n\n" ALTER_TABLE @@ -2735,13 +2739,15 @@ SOURCE_FILE NAME_REF IDENT "t1" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - NO_KW "no" - WHITESPACE " " - ACTION_KW "action" + ON_DELETE_ACTION + ON_KW "on" + WHITESPACE " " + DELETE_KW "delete" + WHITESPACE " " + NO_ACTION + NO_KW "no" + WHITESPACE " " + ACTION_KW "action" SEMICOLON ";" WHITESPACE "\n" ALTER_TABLE @@ -2777,11 +2783,12 @@ SOURCE_FILE NAME_REF IDENT "t1" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - RESTRICT_KW "restrict" + ON_DELETE_ACTION + ON_KW "on" + WHITESPACE " " + DELETE_KW "delete" + WHITESPACE " " + RESTRICT_KW "restrict" SEMICOLON ";" WHITESPACE "\n" ALTER_TABLE @@ -2817,11 +2824,12 @@ SOURCE_FILE NAME_REF IDENT "t1" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - CASCADE_KW "cascade" + ON_DELETE_ACTION + ON_KW "on" + WHITESPACE " " + DELETE_KW "delete" + WHITESPACE " " + CASCADE_KW "cascade" SEMICOLON ";" WHITESPACE "\n" ALTER_TABLE @@ -2857,13 +2865,15 @@ SOURCE_FILE NAME_REF IDENT "t1" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - SET_KW "set" - WHITESPACE " " - NULL_KW "null" + ON_DELETE_ACTION + ON_KW "on" + WHITESPACE " " + DELETE_KW "delete" + WHITESPACE " " + SET_NULL_COLUMNS + SET_KW "set" + WHITESPACE " " + NULL_KW "null" SEMICOLON ";" WHITESPACE "\n" ALTER_TABLE @@ -2899,25 +2909,27 @@ SOURCE_FILE NAME_REF IDENT "t1" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - SET_KW "set" - WHITESPACE " " - NULL_KW "null" - WHITESPACE " " - COLUMN_LIST - L_PAREN "(" - COLUMN - NAME_REF - IDENT "a" - COMMA "," + ON_DELETE_ACTION + ON_KW "on" WHITESPACE " " - COLUMN - NAME_REF - IDENT "b" - R_PAREN ")" + DELETE_KW "delete" + WHITESPACE " " + SET_NULL_COLUMNS + SET_KW "set" + WHITESPACE " " + NULL_KW "null" + WHITESPACE " " + COLUMN_LIST + L_PAREN "(" + COLUMN + NAME_REF + IDENT "a" + COMMA "," + WHITESPACE " " + COLUMN + NAME_REF + IDENT "b" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n" ALTER_TABLE @@ -2953,13 +2965,15 @@ SOURCE_FILE NAME_REF IDENT "t1" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - SET_KW "set" - WHITESPACE " " - DEFAULT_KW "default" + ON_DELETE_ACTION + ON_KW "on" + WHITESPACE " " + DELETE_KW "delete" + WHITESPACE " " + SET_DEFAULT_COLUMNS + SET_KW "set" + WHITESPACE " " + DEFAULT_KW "default" SEMICOLON ";" WHITESPACE "\n" ALTER_TABLE @@ -2995,22 +3009,27 @@ SOURCE_FILE NAME_REF IDENT "t1" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - SET_KW "set" - WHITESPACE " " - DEFAULT_KW "default" - WHITESPACE " " - L_PAREN "(" - NAME_REF - IDENT "a" - COMMA "," - WHITESPACE " " - NAME_REF - IDENT "b" - R_PAREN ")" + ON_DELETE_ACTION + ON_KW "on" + WHITESPACE " " + DELETE_KW "delete" + WHITESPACE " " + SET_DEFAULT_COLUMNS + SET_KW "set" + WHITESPACE " " + DEFAULT_KW "default" + WHITESPACE " " + COLUMN_LIST + L_PAREN "(" + COLUMN + NAME_REF + IDENT "a" + COMMA "," + WHITESPACE " " + COLUMN + NAME_REF + IDENT "b" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" ALTER_TABLE @@ -3819,16 +3838,18 @@ SOURCE_FILE IDENT "id" R_PAREN ")" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - UPDATE_KW "update" - WHITESPACE " " - CASCADE_KW "cascade" - WHITESPACE " " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" + ON_UPDATE_ACTION + ON_KW "on" + WHITESPACE " " + UPDATE_KW "update" + WHITESPACE " " + CASCADE_KW "cascade" WHITESPACE " " - CASCADE_KW "cascade" + ON_DELETE_ACTION + ON_KW "on" + WHITESPACE " " + DELETE_KW "delete" + WHITESPACE " " + CASCADE_KW "cascade" SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__comment_ok.snap b/crates/squawk_parser/tests/snapshots/tests__comment_ok.snap index 3fa66f7c..6442d668 100644 --- a/crates/squawk_parser/tests/snapshots/tests__comment_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__comment_ok.snap @@ -160,8 +160,7 @@ SOURCE_FILE WHITESPACE " " PARAM TIME_TYPE - NAME_REF - TIMESTAMP_KW "timestamp" + TIMESTAMP_KW "timestamp" R_PAREN ")" WHITESPACE " " IS_KW "is" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_aggregate_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_aggregate_ok.snap index 4334607d..5f8559c2 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_aggregate_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_aggregate_ok.snap @@ -760,8 +760,7 @@ SOURCE_FILE IDENT "value2" WHITESPACE " " TIME_TYPE - NAME_REF - TIMESTAMP_KW "timestamp" + TIMESTAMP_KW "timestamp" WHITESPACE " " ORDER_KW "ORDER" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__create_table_err.snap b/crates/squawk_parser/tests/snapshots/tests__create_table_err.snap index f8980bd6..74578180 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_table_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_table_err.snap @@ -141,11 +141,12 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - UPDATE_KW "update" - WHITESPACE " " - CASCADE_KW "cascade" + ON_UPDATE_ACTION + ON_KW "on" + WHITESPACE " " + UPDATE_KW "update" + WHITESPACE " " + CASCADE_KW "cascade" COMMA "," WHITESPACE "\n " COLUMN @@ -166,13 +167,15 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - UPDATE_KW "update" - WHITESPACE " " - SET_KW "set" - WHITESPACE " " - NULL_KW "null" + ON_UPDATE_ACTION + ON_KW "on" + WHITESPACE " " + UPDATE_KW "update" + WHITESPACE " " + SET_NULL_COLUMNS + SET_KW "set" + WHITESPACE " " + NULL_KW "null" COMMA "," WHITESPACE "\n " COLUMN @@ -193,13 +196,15 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - UPDATE_KW "update" - WHITESPACE " " - SET_KW "set" - WHITESPACE " " - DEFAULT_KW "default" + ON_UPDATE_ACTION + ON_KW "on" + WHITESPACE " " + UPDATE_KW "update" + WHITESPACE " " + SET_DEFAULT_COLUMNS + SET_KW "set" + WHITESPACE " " + DEFAULT_KW "default" COMMA "," WHITESPACE "\n " COLUMN @@ -220,26 +225,32 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE " " - ON_KW "on" - WHITESPACE " " - UPDATE_KW "update" - WHITESPACE " " - SET_KW "set" - WHITESPACE " " - DEFAULT_KW "default" - WHITESPACE " " - L_PAREN "(" - NAME_REF - IDENT "a" - COMMA "," - WHITESPACE " " - NAME_REF - IDENT "b" - COMMA "," - WHITESPACE " " - NAME_REF - IDENT "c" - R_PAREN ")" + ON_UPDATE_ACTION + ON_KW "on" + WHITESPACE " " + UPDATE_KW "update" + WHITESPACE " " + SET_DEFAULT_COLUMNS + SET_KW "set" + WHITESPACE " " + DEFAULT_KW "default" + WHITESPACE " " + COLUMN_LIST + L_PAREN "(" + COLUMN + NAME_REF + IDENT "a" + COMMA "," + WHITESPACE " " + COLUMN + NAME_REF + IDENT "b" + COMMA "," + WHITESPACE " " + COLUMN + NAME_REF + IDENT "c" + R_PAREN ")" WHITESPACE "\n" R_PAREN ")" SEMICOLON ";" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap index 5392ea71..6daeaa13 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap @@ -1168,21 +1168,25 @@ SOURCE_FILE NAME_REF IDENT "foo" WHITESPACE "\n " - ON_KW "on" - WHITESPACE " " - UPDATE_KW "update" - WHITESPACE " " - NO_KW "no" - WHITESPACE " " - ACTION_KW "action" + ON_UPDATE_ACTION + ON_KW "on" + WHITESPACE " " + UPDATE_KW "update" + WHITESPACE " " + NO_ACTION + NO_KW "no" + WHITESPACE " " + ACTION_KW "action" WHITESPACE "\n " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - NO_KW "no" - WHITESPACE " " - ACTION_KW "action" + ON_DELETE_ACTION + ON_KW "on" + WHITESPACE " " + DELETE_KW "delete" + WHITESPACE " " + NO_ACTION + NO_KW "no" + WHITESPACE " " + ACTION_KW "action" WHITESPACE "\n" R_PAREN ")" SEMICOLON ";" @@ -1220,21 +1224,25 @@ SOURCE_FILE NAME_REF IDENT "foo" WHITESPACE "\n " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - NO_KW "no" - WHITESPACE " " - ACTION_KW "action" + ON_DELETE_ACTION + ON_KW "on" + WHITESPACE " " + DELETE_KW "delete" + WHITESPACE " " + NO_ACTION + NO_KW "no" + WHITESPACE " " + ACTION_KW "action" WHITESPACE "\n " - ON_KW "on" - WHITESPACE " " - UPDATE_KW "update" - WHITESPACE " " - NO_KW "no" - WHITESPACE " " - ACTION_KW "action" + ON_UPDATE_ACTION + ON_KW "on" + WHITESPACE " " + UPDATE_KW "update" + WHITESPACE " " + NO_ACTION + NO_KW "no" + WHITESPACE " " + ACTION_KW "action" WHITESPACE "\n" R_PAREN ")" SEMICOLON ";" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_table_pg17_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_table_pg17_ok.snap index b5637de7..1c7d6ee4 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_table_pg17_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_table_pg17_ok.snap @@ -64,35 +64,39 @@ SOURCE_FILE WHITESPACE " " PARTIAL_KW "partial" WHITESPACE "\n " - ON_KW "on" - WHITESPACE " " - DELETE_KW "delete" - WHITESPACE " " - NO_KW "no" - WHITESPACE " " - ACTION_KW "action" - WHITESPACE "\n " - ON_KW "on" - WHITESPACE " " - UPDATE_KW "update" - WHITESPACE " " - SET_KW "set" - WHITESPACE " " - NULL_KW "null" - WHITESPACE " " - COLUMN_LIST - L_PAREN "(" + ON_DELETE_ACTION + ON_KW "on" WHITESPACE " " - COLUMN - NAME_REF - IDENT "a" - COMMA "," + DELETE_KW "delete" WHITESPACE " " - COLUMN - NAME_REF - IDENT "b" + NO_ACTION + NO_KW "no" + WHITESPACE " " + ACTION_KW "action" + WHITESPACE "\n " + ON_UPDATE_ACTION + ON_KW "on" WHITESPACE " " - R_PAREN ")" + UPDATE_KW "update" + WHITESPACE " " + SET_NULL_COLUMNS + SET_KW "set" + WHITESPACE " " + NULL_KW "null" + WHITESPACE " " + COLUMN_LIST + L_PAREN "(" + WHITESPACE " " + COLUMN + NAME_REF + IDENT "a" + COMMA "," + WHITESPACE " " + COLUMN + NAME_REF + IDENT "b" + WHITESPACE " " + R_PAREN ")" WHITESPACE "\n" R_PAREN ")" SEMICOLON ";" diff --git a/crates/squawk_parser/tests/snapshots/tests__drop_aggregate_ok.snap b/crates/squawk_parser/tests/snapshots/tests__drop_aggregate_ok.snap index b33f4ad9..559e48cd 100644 --- a/crates/squawk_parser/tests/snapshots/tests__drop_aggregate_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__drop_aggregate_ok.snap @@ -171,8 +171,7 @@ SOURCE_FILE IDENT "a" WHITESPACE " " TIME_TYPE - NAME_REF - TIMESTAMP_KW "timestamp" + TIMESTAMP_KW "timestamp" COMMA "," WHITESPACE "\n " PARAM @@ -228,8 +227,7 @@ SOURCE_FILE IDENT "a" WHITESPACE " " TIME_TYPE - NAME_REF - TIMESTAMP_KW "timestamp" + TIMESTAMP_KW "timestamp" COMMA "," WHITESPACE "\n " PARAM diff --git a/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap b/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap index 873c2c7d..e1fbf4de 100644 --- a/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap @@ -3070,7 +3070,7 @@ SOURCE_FILE NAME_REF IDENT "job" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "using" WHITESPACE " " COLUMN_LIST @@ -6441,7 +6441,7 @@ SOURCE_FILE NAME IDENT "b" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "USING" WHITESPACE " " COLUMN_LIST diff --git a/crates/squawk_parser/tests/snapshots/tests__security_label_ok.snap b/crates/squawk_parser/tests/snapshots/tests__security_label_ok.snap index 277bb2db..2cd50a36 100644 --- a/crates/squawk_parser/tests/snapshots/tests__security_label_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__security_label_ok.snap @@ -233,8 +233,7 @@ SOURCE_FILE IDENT "a" WHITESPACE " " TIME_TYPE - NAME_REF - TIMESTAMP_KW "timestamp" + TIMESTAMP_KW "timestamp" COMMA "," WHITESPACE "\n " PARAM @@ -297,8 +296,7 @@ SOURCE_FILE IDENT "a" WHITESPACE " " TIME_TYPE - NAME_REF - TIMESTAMP_KW "timestamp" + TIMESTAMP_KW "timestamp" COMMA "," WHITESPACE "\n " PARAM diff --git a/crates/squawk_parser/tests/snapshots/tests__select_err.snap b/crates/squawk_parser/tests/snapshots/tests__select_err.snap index e104effd..b924e6c7 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_err.snap @@ -210,6 +210,74 @@ SOURCE_FILE INT_NUMBER "1234" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- missing comma" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + ARRAY_EXPR + ARRAY_KW "array" + L_BRACK "[" + LITERAL + INT_NUMBER "1" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + COMMA "," + LITERAL + INT_NUMBER "3" + R_BRACK "]" + SEMICOLON ";" + WHITESPACE "\n" + COMMENT "-- extra comma" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + ARRAY_EXPR + ARRAY_KW "array" + L_BRACK "[" + LITERAL + INT_NUMBER "1" + COMMA "," + WHITESPACE " " + ERROR + COMMA "," + LITERAL + INT_NUMBER "3" + R_BRACK "]" + SEMICOLON ";" + WHITESPACE "\n" + COMMENT "-- trailing comma" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + ARRAY_EXPR + ARRAY_KW "array" + L_BRACK "[" + LITERAL + INT_NUMBER "1" + COMMA "," + LITERAL + INT_NUMBER "2" + COMMA "," + LITERAL + INT_NUMBER "3" + ERROR + COMMA "," + R_BRACK "]" + SEMICOLON ";" + WHITESPACE "\n\n" COMMENT "-- regression test: this would cause the parser to get stuck & panic, now it" WHITESPACE "\n" COMMENT "-- warns about a missing semicolon" @@ -268,6 +336,9 @@ ERROR@395: expected expression ERROR@396: expected expression ERROR@397: expected expression ERROR@520: missing comma -ERROR@646: expected SEMICOLON -ERROR@679: unexpected trailing comma -ERROR@723: unexpected trailing comma +ERROR@559: expected COMMA +ERROR@597: unexpected comma +ERROR@638: unexpected trailing comma +ERROR@761: expected SEMICOLON +ERROR@794: unexpected trailing comma +ERROR@838: unexpected trailing comma diff --git a/crates/squawk_parser/tests/snapshots/tests__select_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_ok.snap index fe24df34..c29f96ee 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_ok.snap @@ -4478,7 +4478,7 @@ SOURCE_FILE NAME_REF IDENT "t2" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "using" WHITESPACE " " COLUMN_LIST @@ -4515,7 +4515,7 @@ SOURCE_FILE NAME_REF IDENT "t2" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "using" WHITESPACE " " COLUMN_LIST @@ -4559,7 +4559,7 @@ SOURCE_FILE NAME_REF IDENT "t2" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "using" WHITESPACE " " COLUMN_LIST @@ -4598,7 +4598,7 @@ SOURCE_FILE NAME_REF IDENT "t2" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "using" WHITESPACE " " COLUMN_LIST @@ -4743,7 +4743,7 @@ SOURCE_FILE NAME_REF IDENT "t2" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "using" COLUMN_LIST L_PAREN "(" @@ -4804,7 +4804,7 @@ SOURCE_FILE NAME_REF IDENT "t2" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "using" WHITESPACE " " COLUMN_LIST @@ -4962,7 +4962,7 @@ SOURCE_FILE NAME_REF IDENT "u" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "using" WHITESPACE " " COLUMN_LIST @@ -4982,7 +4982,7 @@ SOURCE_FILE NAME_REF IDENT "k" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "using" WHITESPACE " " COLUMN_LIST @@ -5099,7 +5099,7 @@ SOURCE_FILE NAME IDENT "f" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "USING" WHITESPACE " " COLUMN_LIST @@ -5134,7 +5134,7 @@ SOURCE_FILE NAME_REF IDENT "t2" WHITESPACE " " - USING_CLAUSE + JOIN_USING_CLAUSE USING_KW "using" WHITESPACE " " COLUMN_LIST diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index d2ee3a64..0bfb0e52 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -5558,7 +5558,7 @@ impl Join { support::child(&self.syntax) } #[inline] - pub fn using_clause(&self) -> Option { + pub fn using_clause(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -5673,6 +5673,25 @@ impl JoinRight { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct JoinUsingClause { + pub(crate) syntax: SyntaxNode, +} +impl JoinUsingClause { + #[inline] + pub fn alias(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn column_list(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn using_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::USING_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct JsonBehaviorClause { pub(crate) syntax: SyntaxNode, @@ -8769,10 +8788,6 @@ impl TimeType { support::child(&self.syntax) } #[inline] - pub fn name_ref(&self) -> Option { - support::child(&self.syntax) - } - #[inline] pub fn with_timezone(&self) -> Option { support::child(&self.syntax) } @@ -14556,6 +14571,24 @@ impl AstNode for JoinRight { &self.syntax } } +impl AstNode for JoinUsingClause { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::JOIN_USING_CLAUSE + } + #[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 JsonBehaviorClause { #[inline] fn can_cast(kind: SyntaxKind) -> bool { diff --git a/crates/squawk_syntax/src/lib.rs b/crates/squawk_syntax/src/lib.rs index 6a559ac3..543c8998 100644 --- a/crates/squawk_syntax/src/lib.rs +++ b/crates/squawk_syntax/src/lib.rs @@ -382,3 +382,98 @@ fn api_walkthrough() { } assert_eq!(exprs_cast, exprs_visit); } + +#[test] +fn create_table() { + use insta::assert_debug_snapshot; + + let source_code = " + create table users ( + id int8 primary key, + name varchar(255) not null, + email text, + created_at timestamp default now() + ); + + create table posts ( + id serial primary key, + title varchar(500), + content text, + user_id int8 references users(id) + ); + "; + + let parse = SourceFile::parse(source_code); + assert!(parse.errors().is_empty()); + let file: SourceFile = parse.tree(); + + let mut tables: Vec<(String, Vec<(String, String)>)> = vec![]; + + for stmt in file.stmts() { + if let ast::Stmt::CreateTable(create_table) = stmt { + let table_name = create_table.path().unwrap().syntax().to_string(); + let mut columns = vec![]; + for arg in create_table.table_arg_list().unwrap().args() { + match arg { + ast::TableArg::Column(column) => { + let column_name = column.name().unwrap(); + let column_type = column.ty().unwrap(); + columns.push(( + column_name.syntax().to_string(), + column_type.syntax().to_string(), + )); + } + ast::TableArg::TableConstraint(_) | ast::TableArg::LikeClause(_) => (), + } + } + tables.push((table_name, columns)); + } + } + + assert_debug_snapshot!(tables, @r#" + [ + ( + "users", + [ + ( + "id", + "int8", + ), + ( + "name", + "varchar(255)", + ), + ( + "email", + "text", + ), + ( + "created_at", + "timestamp", + ), + ], + ), + ( + "posts", + [ + ( + "id", + "serial", + ), + ( + "title", + "varchar(500)", + ), + ( + "content", + "text", + ), + ( + "user_id", + "int8", + ), + ], + ), + ] + "#) +} diff --git a/crates/squawk_syntax/src/postgresql.ungram b/crates/squawk_syntax/src/postgresql.ungram index 8b0c7258..45e4d0d9 100644 --- a/crates/squawk_syntax/src/postgresql.ungram +++ b/crates/squawk_syntax/src/postgresql.ungram @@ -245,7 +245,6 @@ DoubleType = TimeType = ('time' | 'timestamp') - NameRef ('(' Literal ')')? (WithTimezone | WithoutTimezone)? @@ -603,8 +602,11 @@ JoinType = OnClause = 'on' Expr +JoinUsingClause = + 'using' ColumnList Alias? + Join = - 'natural'? JoinType (UsingClause | OnClause)? + 'natural'? JoinType (using_clause:JoinUsingClause | OnClause)? JoinExpr = (FromItem | JoinExpr) Join diff --git a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__drop_aggregate_params_validation.snap b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__drop_aggregate_params_validation.snap index 8368aaf3..b01bf008 100644 --- a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__drop_aggregate_params_validation.snap +++ b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__drop_aggregate_params_validation.snap @@ -157,8 +157,7 @@ SOURCE_FILE@0..389 IDENT@248..249 "a" WHITESPACE@249..250 " " TIME_TYPE@250..259 - NAME_REF@250..259 - TIMESTAMP_KW@250..259 "timestamp" + TIMESTAMP_KW@250..259 "timestamp" COMMA@259..260 "," WHITESPACE@260..265 "\n " PARAM@265..278 @@ -217,8 +216,7 @@ SOURCE_FILE@0..389 IDENT@344..345 "a" WHITESPACE@345..346 " " TIME_TYPE@346..355 - NAME_REF@346..355 - TIMESTAMP_KW@346..355 "timestamp" + TIMESTAMP_KW@346..355 "timestamp" COMMA@355..356 "," WHITESPACE@356..361 "\n " PARAM@361..374 diff --git a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__join_clauses_validation.snap b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__join_clauses_validation.snap index 0a32a84a..2c8b8fbb 100644 --- a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__join_clauses_validation.snap +++ b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__join_clauses_validation.snap @@ -139,7 +139,7 @@ SOURCE_FILE@0..653 NAME_REF@201..202 IDENT@201..202 "u" WHITESPACE@202..203 " " - USING_CLAUSE@203..213 + JOIN_USING_CLAUSE@203..213 USING_KW@203..208 "using" WHITESPACE@208..209 " " COLUMN_LIST@209..213 @@ -225,7 +225,7 @@ SOURCE_FILE@0..653 NAME_REF@336..337 IDENT@336..337 "u" WHITESPACE@337..338 " " - USING_CLAUSE@338..348 + JOIN_USING_CLAUSE@338..348 USING_KW@338..343 "using" WHITESPACE@343..344 " " COLUMN_LIST@344..348 @@ -385,7 +385,7 @@ SOURCE_FILE@0..653 NAME_REF@510..511 IDENT@510..511 "c" WHITESPACE@511..512 " " - USING_CLAUSE@512..521 + JOIN_USING_CLAUSE@512..521 USING_KW@512..517 "using" COLUMN_LIST@517..521 L_PAREN@517..518 "(" @@ -496,7 +496,7 @@ SOURCE_FILE@0..653 NAME_REF@619..620 IDENT@619..620 "u" WHITESPACE@620..621 " " - USING_CLAUSE@621..631 + JOIN_USING_CLAUSE@621..631 USING_KW@621..626 "using" WHITESPACE@626..627 " " COLUMN_LIST@627..631 @@ -514,7 +514,7 @@ SOURCE_FILE@0..653 NAME_REF@639..640 IDENT@639..640 "c" WHITESPACE@640..641 " " - USING_CLAUSE@641..651 + JOIN_USING_CLAUSE@641..651 USING_KW@641..646 "using" WHITESPACE@646..647 " " COLUMN_LIST@647..651 From d717bc1d28798805c7e7cf944141993bad4b6029 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Mon, 23 Jun 2025 14:49:03 -0400 Subject: [PATCH 2/4] lint --- crates/squawk_parser/src/grammar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index 42731705..a567656b 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -13841,7 +13841,7 @@ fn attribute_list(p: &mut Parser<'_>) { COMMA, || "unexpected comma".to_string(), COL_LABEL_FIRST, - |p| opt_attribute_option(p), + opt_attribute_option, ); m.complete(p, ATTRIBUTE_LIST); } From 2a650d0fc3918fe9ef8a883fa181a5cf4708ab58 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Tue, 24 Jun 2025 19:18:52 -0400 Subject: [PATCH 3/4] parser: add more nodes for transactions, partitions, materialized --- .../src/rules/prefer_timestamptz.rs | 4 +- .../src/generated/syntax_kind.rs | 11 +- crates/squawk_parser/src/grammar.rs | 252 +++---- .../snapshots/tests__alter_table_ok.snap | 32 +- .../tests__create_foreign_table_ok.snap | 131 ++-- .../snapshots/tests__create_table_ok.snap | 138 ++-- .../tests/snapshots/tests__misc_ok.snap | 106 +-- .../tests/snapshots/tests__select_cte_ok.snap | 12 +- .../snapshots/tests__select_funcs_ok.snap | 64 +- .../tests/snapshots/tests__select_ok.snap | 254 +++---- .../snapshots/tests__set_transaction_ok.snap | 76 ++- .../snapshots/tests__transaction_ok.snap | 238 ++++--- .../squawk_syntax/src/ast/generated/nodes.rs | 630 ++++++++++++++++-- crates/squawk_syntax/src/postgresql.ungram | 61 +- 14 files changed, 1312 insertions(+), 697 deletions(-) diff --git a/crates/squawk_linter/src/rules/prefer_timestamptz.rs b/crates/squawk_linter/src/rules/prefer_timestamptz.rs index 6f35a0f4..3756b1cd 100644 --- a/crates/squawk_linter/src/rules/prefer_timestamptz.rs +++ b/crates/squawk_linter/src/rules/prefer_timestamptz.rs @@ -32,7 +32,9 @@ pub fn is_not_allowed_timestamp(ty: &ast::Type) -> bool { ast::Type::BitType(_) => false, ast::Type::DoubleType(_) => false, ast::Type::TimeType(time_type) => { - if time_type.timestamp_token().is_some() && time_type.with_timezone().is_none() { + if time_type.timestamp_token().is_some() + && !matches!(time_type.timezone(), Some(ast::Timezone::WithTimezone(_))) + { return true; } false diff --git a/crates/squawk_parser/src/generated/syntax_kind.rs b/crates/squawk_parser/src/generated/syntax_kind.rs index c6b6855f..797ed233 100644 --- a/crates/squawk_parser/src/generated/syntax_kind.rs +++ b/crates/squawk_parser/src/generated/syntax_kind.rs @@ -763,6 +763,7 @@ pub enum SyntaxKind { FILTER_CLAUSE, FORCE_RLS, FOREIGN_KEY_CONSTRAINT, + FRAME_CLAUSE, FROM_CLAUSE, FROM_ITEM, FUNC_OPTION, @@ -838,6 +839,7 @@ pub enum SyntaxKind { MATCH_PARTIAL, MATCH_SIMPLE, MATCH_TYPE, + MATERIALIZED, MERGE, MOVE, NAME, @@ -852,6 +854,7 @@ pub enum SyntaxKind { NOT_ILIKE, NOT_IN, NOT_LIKE, + NOT_MATERIALIZED, NOT_NULL_CONSTRAINT, NOT_OF, NOT_SIMILAR_TO, @@ -890,8 +893,13 @@ pub enum SyntaxKind { PAREN_EXPR, PAREN_SELECT, PARTITION_BY, + PARTITION_DEFAULT, + PARTITION_FOR_VALUES_FROM, + PARTITION_FOR_VALUES_IN, + PARTITION_FOR_VALUES_WITH, PARTITION_ITEM, PARTITION_OF, + PARTITION_TYPE, PATH, PATH_SEGMENT, PATH_TYPE, @@ -988,9 +996,9 @@ pub enum SyntaxKind { TABLE_LIST, TARGET, TARGET_LIST, + TIMEZONE, TIME_TYPE, TRANSACTION_MODE, - TRANSACTION_MODE_ISOLATION_LEVEL, TRANSACTION_MODE_LIST, TRANSFORM_FUNC_OPTION, TRUNCATE, @@ -1012,6 +1020,7 @@ pub enum SyntaxKind { WINDOW_CLAUSE, WINDOW_DEF, WINDOW_FUNC_OPTION, + WINDOW_SPEC, WITHIN_CLAUSE, WITHOUT_OIDS, WITHOUT_TIMEZONE, diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index a567656b..c220709a 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -1959,17 +1959,12 @@ fn call_expr_args(p: &mut Parser<'_>, lhs: CompletedMarker) -> CompletedMarker { } fn opt_agg_clauses(p: &mut Parser<'_>) { - // postgres has: - // func_expr: func_application within_group_clause filter_clause over_clause - if p.at(WITHIN_KW) { - let m = p.start(); - p.expect(WITHIN_KW); - p.expect(GROUP_KW); - p.expect(L_PAREN); - opt_order_by_clause(p); - p.expect(R_PAREN); - m.complete(p, WITHIN_CLAUSE); - } + opt_within_clause(p); + opt_filter_clause(p); + opt_over_clause(p); +} + +fn opt_filter_clause(p: &mut Parser<'_>) { if p.at(FILTER_KW) { let m = p.start(); p.expect(FILTER_KW); @@ -1981,13 +1976,16 @@ fn opt_agg_clauses(p: &mut Parser<'_>) { p.expect(R_PAREN); m.complete(p, FILTER_CLAUSE); } +} + +fn opt_over_clause(p: &mut Parser<'_>) { if p.at(OVER_KW) { // OVER window_name // OVER ( window_definition ) let m = p.start(); p.expect(OVER_KW); if p.eat(L_PAREN) { - window_definition(p); + window_spec(p); p.expect(R_PAREN); } else { name_ref(p); @@ -1996,6 +1994,18 @@ fn opt_agg_clauses(p: &mut Parser<'_>) { } } +fn opt_within_clause(p: &mut Parser<'_>) { + if p.at(WITHIN_KW) { + let m = p.start(); + p.expect(WITHIN_KW); + p.expect(GROUP_KW); + p.expect(L_PAREN); + opt_order_by_clause(p); + p.expect(R_PAREN); + m.complete(p, WITHIN_CLAUSE); + } +} + // foo[] // foo[:b] // foo[a:] @@ -2360,12 +2370,7 @@ fn with_query(p: &mut Parser<'_>) -> CompletedMarker { name(p); opt_column_list_with(p, ColumnDefKind::Name); p.expect(AS_KW); - // [ [ NOT ] MATERIALIZED ] - if p.eat(NOT_KW) { - p.expect(MATERIALIZED_KW); - } else { - p.eat(MATERIALIZED_KW); - } + opt_materialized(p); p.expect(L_PAREN); preparable_stmt(p); p.expect(R_PAREN); @@ -2387,31 +2392,50 @@ fn with_query(p: &mut Parser<'_>) -> CompletedMarker { p.expect(SET_KW); name_ref(p); } - // [ CYCLE column_name [, ...] SET cycle_mark_col_name [ TO cycle_mark_value DEFAULT cycle_mark_default ] USING cycle_path_col_name ] - if p.eat(CYCLE_KW) { - separated( - p, - COMMA, - || "unexpected comma, expected a column name".to_string(), - NAME_REF_FIRST, - TokenSet::new(&[SET_KW]), - |p| opt_name_ref(p).is_some(), - ); - p.expect(SET_KW); - name_ref(p); - if p.eat(TO_KW) { - if expr(p).is_none() { - p.error("expected an expression"); - } - p.expect(DEFAULT_KW); - if expr(p).is_none() { - p.error("expected an expression"); - } + opt_cycle_clause(p); + m.complete(p, WITH_TABLE) +} + +// [ CYCLE column_name [, ...] SET cycle_mark_col_name [ TO cycle_mark_value DEFAULT cycle_mark_default ] USING cycle_path_col_name ] +fn opt_cycle_clause(p: &mut Parser<'_>) { + if !p.at(CYCLE_KW) { + return; + } + p.expect(CYCLE_KW); + separated( + p, + COMMA, + || "unexpected comma, expected a column name".to_string(), + NAME_REF_FIRST, + TokenSet::new(&[SET_KW]), + |p| opt_name_ref(p).is_some(), + ); + p.expect(SET_KW); + name_ref(p); + if p.eat(TO_KW) { + if expr(p).is_none() { + p.error("expected an expression"); + } + p.expect(DEFAULT_KW); + if expr(p).is_none() { + p.error("expected an expression"); } - p.expect(USING_KW); - name_ref(p); } - m.complete(p, WITH_TABLE) + p.expect(USING_KW); + name_ref(p); +} + +// [ [ NOT ] MATERIALIZED ] +fn opt_materialized(p: &mut Parser<'_>) { + let m = p.start(); + if p.eat(NOT_KW) { + p.expect(MATERIALIZED_KW); + m.complete(p, NOT_MATERIALIZED); + } else if p.eat(MATERIALIZED_KW) { + m.complete(p, MATERIALIZED); + } else { + m.abandon(p); + } } const WITH_FOLLOW: TokenSet = TokenSet::new(&[ @@ -4350,7 +4374,7 @@ const WINDOW_DEF_START: TokenSet = // The frame_clause can be one of // { RANGE | ROWS | GROUPS } frame_start [ frame_exclusion ] // { RANGE | ROWS | GROUPS } BETWEEN frame_start AND frame_end [ frame_exclusion ] -fn window_definition(p: &mut Parser<'_>) -> Option { +fn window_spec(p: &mut Parser<'_>) -> Option { if !p.at_ts(WINDOW_DEF_START) { return None; } @@ -4363,7 +4387,13 @@ fn window_definition(p: &mut Parser<'_>) -> Option { } } opt_order_by_clause(p); + opt_frame_clause(p); + Some(m.complete(p, WINDOW_SPEC)) +} + +fn opt_frame_clause(p: &mut Parser<'_>) { if p.at(RANGE_KW) || p.at(ROWS_KW) || p.at(GROUPS_KW) { + let m = p.start(); p.bump_any(); if p.eat(BETWEEN_KW) { frame_start_end(p); @@ -4374,8 +4404,8 @@ fn window_definition(p: &mut Parser<'_>) -> Option { frame_start_end(p); opt_frame_exclusion(p); } + m.complete(p, FRAME_CLAUSE); } - Some(m.complete(p, WINDOW_DEF)) } /// @@ -4393,11 +4423,13 @@ fn opt_window_clause(p: &mut Parser<'_>) -> Option { } fn window_def(p: &mut Parser<'_>) { + let m = p.start(); name(p); p.expect(AS_KW); p.expect(L_PAREN); - window_definition(p); + window_spec(p); p.expect(R_PAREN); + m.complete(p, WINDOW_DEF); } // [ LIMIT { count | ALL } ] @@ -4787,7 +4819,8 @@ fn table_arg_list(p: &mut Parser<'_>, t: ColDefType) -> Option // { FOR VALUES partition_bound_spec | DEFAULT } fn partition_option(p: &mut Parser<'_>) { - if p.eat(FOR_KW) { + let m = p.start(); + let kind = if p.eat(FOR_KW) { p.expect(VALUES_KW); // FOR VALUES WITH (modulus 5, remainder 0) if p.eat(WITH_KW) { @@ -4798,6 +4831,7 @@ fn partition_option(p: &mut Parser<'_>) { ident(p); p.expect(INT_NUMBER); p.expect(R_PAREN); + PARTITION_FOR_VALUES_WITH // FOR VALUES IN '(' expr_list ')' } else if p.eat(IN_KW) { p.expect(L_PAREN); @@ -4805,6 +4839,7 @@ fn partition_option(p: &mut Parser<'_>) { p.error("expected expr list"); } p.expect(R_PAREN); + PARTITION_FOR_VALUES_IN // FOR VALUES FROM '(' expr_list ')' TO '(' expr_list ')' } else if p.eat(FROM_KW) { p.expect(L_PAREN); @@ -4818,11 +4853,17 @@ fn partition_option(p: &mut Parser<'_>) { p.error("expected expr list"); } p.expect(R_PAREN); + PARTITION_FOR_VALUES_FROM + } else { + p.error("expected partition option"); + PARTITION_DEFAULT } // DEFAULT } else { p.expect(DEFAULT_KW); - } + PARTITION_DEFAULT + }; + m.complete(p, kind); } fn opt_inherits_tables(p: &mut Parser<'_>) { @@ -4909,14 +4950,7 @@ fn create_table(p: &mut Parser<'_>) -> CompletedMarker { // AS query // [ WITH [ NO ] DATA ] if p.eat(AS_KW) { - match stmt( - p, - &StmtRestrictions { - begin_end_allowed: false, - }, - ) - .map(|x| x.kind()) - { + match stmt(p, &StmtRestrictions::default()).map(|x| x.kind()) { Some( SELECT | COMPOUND_SELECT | SELECT_INTO | PAREN_SELECT | TABLE | VALUES | EXECUTE, ) => (), @@ -5104,38 +5138,46 @@ fn opt_transaction_mode(p: &mut Parser<'_>) -> bool { if !p.at_ts(TRANSACTION_MODE_FIRST) { return false; } + let m = p.start(); // ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED } - if p.eat(ISOLATION_KW) { + let kind = if p.eat(ISOLATION_KW) { p.expect(LEVEL_KW); if p.eat(SERIALIZABLE_KW) { - true + SERIALIZABLE } else if p.eat(REPEATABLE_KW) { - p.expect(READ_KW) + p.expect(READ_KW); + REPEATABLE_READ } else if p.eat(READ_KW) { - p.eat(UNCOMMITTED_KW) || p.expect(COMMITTED_KW) + if p.eat(UNCOMMITTED_KW) { + READ_UNCOMMITTED + } else { + p.expect(COMMITTED_KW); + READ_COMMITTED + } } else { - false + p.error("expected isolation level"); + READ_COMMITTED } // READ WRITE | READ ONLY } else if p.eat(READ_KW) { - p.eat(WRITE_KW) || p.expect(ONLY_KW) + if p.eat(WRITE_KW) { + READ_WRITE + } else { + p.expect(ONLY_KW); + READ_ONLY + } // [ NOT ] DEFERRABLE } else { - p.eat(NOT_KW); - p.expect(DEFERRABLE_KW) - } -} - -// [ transaction_mode [, ...] ] -fn opt_transaction_mode_list(p: &mut Parser<'_>) { - while !p.at(EOF) { - if !opt_transaction_mode(p) { - break; - } - if !p.eat(COMMA) && !p.at_ts(TRANSACTION_MODE_FIRST) { - break; - } - } + let kind = if p.eat(NOT_KW) { + NOT_DEFERRABLE + } else { + DEFERRABLE + }; + p.expect(DEFERRABLE_KW); + kind + }; + m.complete(p, kind); + true } // BEGIN [ WORK | TRANSACTION ] [ transaction_mode [, ...] ] @@ -5156,12 +5198,12 @@ fn begin(p: &mut Parser<'_>) -> CompletedMarker { if p.eat(BEGIN_KW) { // [ WORK | TRANSACTION ] let _ = p.eat(WORK_KW) || p.eat(TRANSACTION_KW); - opt_transaction_mode_list(p); + transaction_mode_list(p); } else { // START TRANSACTION [ transaction_mode [, ...] ] p.bump(START_KW); p.expect(TRANSACTION_KW); - opt_transaction_mode_list(p); + transaction_mode_list(p); } m.complete(p, BEGIN) } @@ -5261,6 +5303,13 @@ fn rollback(p: &mut Parser<'_>) -> CompletedMarker { struct StmtRestrictions { begin_end_allowed: bool, } +impl Default for StmtRestrictions { + fn default() -> Self { + Self { + begin_end_allowed: false, + } + } +} fn stmt(p: &mut Parser, r: &StmtRestrictions) -> Option { match (p.current(), p.nth(1)) { @@ -8601,12 +8650,7 @@ fn create_materialized_view(p: &mut Parser<'_>) -> CompletedMarker { opt_tablespace(p); p.expect(AS_KW); // A SELECT, TABLE, or VALUES command. - let statement = stmt( - p, - &StmtRestrictions { - begin_end_allowed: false, - }, - ); + let statement = stmt(p, &StmtRestrictions::default()); match statement.map(|x| x.kind()) { Some(SELECT | SELECT_INTO | COMPOUND_SELECT | TABLE | VALUES) => (), Some(kind) => { @@ -8922,12 +8966,7 @@ fn create_role(p: &mut Parser<'_>) -> CompletedMarker { fn select_insert_delete_update_or_notify(p: &mut Parser<'_>) { // statement // Any SELECT, INSERT, UPDATE, DELETE, MERGE, or VALUES statement. - let statement = stmt( - p, - &StmtRestrictions { - begin_end_allowed: false, - }, - ); + let statement = stmt(p, &StmtRestrictions::default()); if let Some(statement) = statement { match statement.kind() { SELECT | VALUES | INSERT | UPDATE | DELETE | NOTIFY => (), @@ -9907,12 +9946,7 @@ fn explain(p: &mut Parser<'_>) -> CompletedMarker { p.expect(R_PAREN); } // statement is SELECT, INSERT, UPDATE, DELETE, MERGE, VALUES, EXECUTE, DECLARE, CREATE TABLE AS, or CREATE MATERIALIZED VIEW AS - let statement = stmt( - p, - &StmtRestrictions { - begin_end_allowed: false, - }, - ); + let statement = stmt(p, &StmtRestrictions::default()); if let Some(statement) = statement { match statement.kind() { SELECT @@ -10954,12 +10988,7 @@ fn create_view(p: &mut Parser<'_>) -> CompletedMarker { // TODO: this can be more specific opt_with_params(p); p.expect(AS_KW); - match stmt( - p, - &StmtRestrictions { - begin_end_allowed: false, - }, - ) { + match stmt(p, &StmtRestrictions::default()) { Some(statement) => match statement.kind() { SELECT | COMPOUND_SELECT | SELECT_INTO | VALUES | TABLE => (), kind => p.error(format!("expected SELECT, got {:?}", kind)), @@ -11156,12 +11185,7 @@ fn declare(p: &mut Parser<'_>) -> CompletedMarker { } p.expect(FOR_KW); // select stmt - let statement = stmt( - p, - &StmtRestrictions { - begin_end_allowed: false, - }, - ); + let statement = stmt(p, &StmtRestrictions::default()); match statement.map(|x| x.kind()) { Some(SELECT | SELECT_INTO | COMPOUND_SELECT | TABLE | VALUES) => (), Some(kind) => { @@ -11588,12 +11612,7 @@ fn copy(p: &mut Parser<'_>) -> CompletedMarker { } fn preparable_stmt(p: &mut Parser<'_>) { - let statement = stmt( - p, - &StmtRestrictions { - begin_end_allowed: false, - }, - ); + let statement = stmt(p, &StmtRestrictions::default()); match statement.map(|x| x.kind()) { // select | insert | update | delete | merge Some( @@ -12624,12 +12643,7 @@ fn opt_function_option(p: &mut Parser<'_>) -> bool { p.error("expected expr") } } else { - stmt( - p, - &StmtRestrictions { - begin_end_allowed: false, - }, - ); + stmt(p, &StmtRestrictions::default()); } if p.at(END_KW) { p.expect(SEMICOLON); diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap index c878f572..72782947 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap @@ -3583,7 +3583,8 @@ SOURCE_FILE NAME_REF IDENT "f" WHITESPACE " " - DEFAULT_KW "default" + PARTITION_DEFAULT + DEFAULT_KW "default" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- partition spec" @@ -3609,20 +3610,21 @@ SOURCE_FILE NAME_REF IDENT "f" WHITESPACE " " - FOR_KW "for" - WHITESPACE " " - VALUES_KW "values" - WHITESPACE " " - IN_KW "in" - WHITESPACE " " - L_PAREN "(" - LITERAL - STRING "'bar'" - COMMA "," - WHITESPACE " " - LITERAL - STRING "'buzz'" - R_PAREN ")" + PARTITION_FOR_VALUES_IN + FOR_KW "for" + WHITESPACE " " + VALUES_KW "values" + WHITESPACE " " + IN_KW "in" + WHITESPACE " " + L_PAREN "(" + LITERAL + STRING "'bar'" + COMMA "," + WHITESPACE " " + LITERAL + STRING "'buzz'" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- multiple_actions" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_foreign_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_foreign_table_ok.snap index 3122da0f..32dd90e8 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_foreign_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_foreign_table_ok.snap @@ -248,7 +248,8 @@ SOURCE_FILE NAME_REF IDENT "u" WHITESPACE "\n " - DEFAULT_KW "default" + PARTITION_DEFAULT + DEFAULT_KW "default" WHITESPACE "\n " SERVER_KW "server" WHITESPACE " " @@ -349,7 +350,8 @@ SOURCE_FILE WHITESPACE "\n " R_PAREN ")" WHITESPACE "\n " - DEFAULT_KW "default" + PARTITION_DEFAULT + DEFAULT_KW "default" WHITESPACE "\n " SERVER_KW "server" WHITESPACE " " @@ -410,22 +412,23 @@ SOURCE_FILE WHITESPACE "\n " R_PAREN ")" WHITESPACE "\n " - FOR_KW "for" - WHITESPACE " " - VALUES_KW "values" - WHITESPACE " " - IN_KW "in" - WHITESPACE " " - L_PAREN "(" - BIN_EXPR - NAME_REF - IDENT "a" + PARTITION_FOR_VALUES_IN + FOR_KW "for" WHITESPACE " " - R_ANGLE ">" + VALUES_KW "values" WHITESPACE " " - NAME_REF - IDENT "b" - R_PAREN ")" + IN_KW "in" + WHITESPACE " " + L_PAREN "(" + BIN_EXPR + NAME_REF + IDENT "a" + WHITESPACE " " + R_ANGLE ">" + WHITESPACE " " + NAME_REF + IDENT "b" + R_PAREN ")" WHITESPACE "\n " SERVER_KW "server" WHITESPACE " " @@ -469,41 +472,42 @@ SOURCE_FILE WHITESPACE "\n " R_PAREN ")" WHITESPACE "\n " - FOR_KW "for" - WHITESPACE " " - VALUES_KW "values" - WHITESPACE " " - FROM_KW "from" - WHITESPACE " " - L_PAREN "(" - BIN_EXPR + PARTITION_FOR_VALUES_FROM + FOR_KW "for" + WHITESPACE " " + VALUES_KW "values" + WHITESPACE " " + FROM_KW "from" + WHITESPACE " " + L_PAREN "(" + BIN_EXPR + NAME_REF + IDENT "a" + WHITESPACE " " + R_ANGLE ">" + WHITESPACE " " + NAME_REF + IDENT "b" + COMMA "," + WHITESPACE " " NAME_REF - IDENT "a" + MINVALUE_KW "minvalue" + COMMA "," WHITESPACE " " - R_ANGLE ">" + NAME_REF + MAXVALUE_KW "maxvalue" + R_PAREN ")" + WHITESPACE " \n " + TO_KW "to" WHITESPACE " " + L_PAREN "(" NAME_REF - IDENT "b" - COMMA "," - WHITESPACE " " - NAME_REF - MINVALUE_KW "minvalue" - COMMA "," - WHITESPACE " " - NAME_REF - MAXVALUE_KW "maxvalue" - R_PAREN ")" - WHITESPACE " \n " - TO_KW "to" - WHITESPACE " " - L_PAREN "(" - NAME_REF - MAXVALUE_KW "maxvalue" - COMMA "," - WHITESPACE " " - NAME_REF - MINVALUE_KW "minvalue" - R_PAREN ")" + MAXVALUE_KW "maxvalue" + COMMA "," + WHITESPACE " " + NAME_REF + MINVALUE_KW "minvalue" + R_PAREN ")" WHITESPACE "\n " SERVER_KW "server" WHITESPACE " " @@ -547,22 +551,23 @@ SOURCE_FILE WHITESPACE "\n " R_PAREN ")" WHITESPACE "\n " - FOR_KW "for" - WHITESPACE " " - VALUES_KW "values" - WHITESPACE " " - WITH_KW "with" - WHITESPACE " " - L_PAREN "(" - IDENT "modulus" - WHITESPACE " " - INT_NUMBER "10" - COMMA "," - WHITESPACE " " - IDENT "remainder" - WHITESPACE " " - INT_NUMBER "2" - R_PAREN ")" + PARTITION_FOR_VALUES_WITH + FOR_KW "for" + WHITESPACE " " + VALUES_KW "values" + WHITESPACE " " + WITH_KW "with" + WHITESPACE " " + L_PAREN "(" + IDENT "modulus" + WHITESPACE " " + INT_NUMBER "10" + COMMA "," + WHITESPACE " " + IDENT "remainder" + WHITESPACE " " + INT_NUMBER "2" + R_PAREN ")" WHITESPACE "\n " SERVER_KW "server" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap index 6daeaa13..0493adfd 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap @@ -2892,7 +2892,8 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE " " - DEFAULT_KW "default" + PARTITION_DEFAULT + DEFAULT_KW "default" SEMICOLON ";" WHITESPACE "\n\n" CREATE_TABLE @@ -2951,7 +2952,8 @@ SOURCE_FILE WHITESPACE "\n" R_PAREN ")" WHITESPACE " " - DEFAULT_KW "default" + PARTITION_DEFAULT + DEFAULT_KW "default" SEMICOLON ";" WHITESPACE "\n\n" CREATE_TABLE @@ -2981,22 +2983,23 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE " \n" - FOR_KW "for" - WHITESPACE " " - VALUES_KW "values" - WHITESPACE " " - WITH_KW "with" - WHITESPACE " " - L_PAREN "(" - IDENT "modulus" - WHITESPACE " " - INT_NUMBER "1" - COMMA "," - WHITESPACE " " - IDENT "remainder" - WHITESPACE " " - INT_NUMBER "1" - R_PAREN ")" + PARTITION_FOR_VALUES_WITH + FOR_KW "for" + WHITESPACE " " + VALUES_KW "values" + WHITESPACE " " + WITH_KW "with" + WHITESPACE " " + L_PAREN "(" + IDENT "modulus" + WHITESPACE " " + INT_NUMBER "1" + COMMA "," + WHITESPACE " " + IDENT "remainder" + WHITESPACE " " + INT_NUMBER "1" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" CREATE_TABLE @@ -3026,20 +3029,21 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE " \n" - FOR_KW "for" - WHITESPACE " " - VALUES_KW "values" - WHITESPACE " " - IN_KW "in" - WHITESPACE " " - L_PAREN "(" - LITERAL - STRING "'bar'" - COMMA "," - WHITESPACE " " - LITERAL - STRING "'buzz'" - R_PAREN ")" + PARTITION_FOR_VALUES_IN + FOR_KW "for" + WHITESPACE " " + VALUES_KW "values" + WHITESPACE " " + IN_KW "in" + WHITESPACE " " + L_PAREN "(" + LITERAL + STRING "'bar'" + COMMA "," + WHITESPACE " " + LITERAL + STRING "'buzz'" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" CREATE_TABLE @@ -3069,23 +3073,24 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE "\n" - FOR_KW "for" - WHITESPACE " " - VALUES_KW "values" - WHITESPACE " " - FROM_KW "from" - WHITESPACE " " - L_PAREN "(" - LITERAL - STRING "'bar'" - R_PAREN ")" - WHITESPACE " " - TO_KW "to" - WHITESPACE " " - L_PAREN "(" - LITERAL - STRING "'buzz'" - R_PAREN ")" + PARTITION_FOR_VALUES_FROM + FOR_KW "for" + WHITESPACE " " + VALUES_KW "values" + WHITESPACE " " + FROM_KW "from" + WHITESPACE " " + L_PAREN "(" + LITERAL + STRING "'bar'" + R_PAREN ")" + WHITESPACE " " + TO_KW "to" + WHITESPACE " " + L_PAREN "(" + LITERAL + STRING "'buzz'" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" CREATE_TABLE @@ -3137,23 +3142,24 @@ SOURCE_FILE WHITESPACE "\n" R_PAREN ")" WHITESPACE " " - FOR_KW "for" - WHITESPACE " " - VALUES_KW "values" - WHITESPACE " " - FROM_KW "from" - WHITESPACE " " - L_PAREN "(" - LITERAL - STRING "'2016-09-01'" - R_PAREN ")" - WHITESPACE " " - TO_KW "to" - WHITESPACE " " - L_PAREN "(" - LITERAL - STRING "'2016-10-01'" - R_PAREN ")" + PARTITION_FOR_VALUES_FROM + FOR_KW "for" + WHITESPACE " " + VALUES_KW "values" + WHITESPACE " " + FROM_KW "from" + WHITESPACE " " + L_PAREN "(" + LITERAL + STRING "'2016-09-01'" + R_PAREN ")" + WHITESPACE " " + TO_KW "to" + WHITESPACE " " + L_PAREN "(" + LITERAL + STRING "'2016-10-01'" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" CREATE_TABLE diff --git a/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap b/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap index e1fbf4de..8c58d1b5 100644 --- a/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap @@ -1273,7 +1273,8 @@ SOURCE_FILE NAME_REF IDENT "time_taptest_table" WHITESPACE " " - DEFAULT_KW "DEFAULT" + PARTITION_DEFAULT + DEFAULT_KW "DEFAULT" SEMICOLON ";" WHITESPACE "\n\n" INSERT @@ -1383,23 +1384,24 @@ SOURCE_FILE NAME_REF IDENT "time_taptest_table" WHITESPACE " " - FOR_KW "FOR" - WHITESPACE " " - VALUES_KW "VALUES" - WHITESPACE " " - FROM_KW "FROM" - WHITESPACE " " - L_PAREN "(" - LITERAL - STRING "'2024-12-25'" - R_PAREN ")" - WHITESPACE " " - TO_KW "TO" - WHITESPACE " " - L_PAREN "(" - LITERAL - STRING "'2024-12-26'" - R_PAREN ")" + PARTITION_FOR_VALUES_FROM + FOR_KW "FOR" + WHITESPACE " " + VALUES_KW "VALUES" + WHITESPACE " " + FROM_KW "FROM" + WHITESPACE " " + L_PAREN "(" + LITERAL + STRING "'2024-12-25'" + R_PAREN ")" + WHITESPACE " " + TO_KW "TO" + WHITESPACE " " + L_PAREN "(" + LITERAL + STRING "'2024-12-26'" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" BEGIN @@ -1555,23 +1557,24 @@ SOURCE_FILE NAME_REF IDENT "time_taptest_table" WHITESPACE " " - FOR_KW "FOR" - WHITESPACE " " - VALUES_KW "VALUES" - WHITESPACE " " - FROM_KW "FROM" - WHITESPACE " " - L_PAREN "(" - LITERAL - STRING "'2024-12-25'" - R_PAREN ")" - WHITESPACE " " - TO_KW "TO" - WHITESPACE " " - L_PAREN "(" - LITERAL - STRING "'2024-12-26'" - R_PAREN ")" + PARTITION_FOR_VALUES_FROM + FOR_KW "FOR" + WHITESPACE " " + VALUES_KW "VALUES" + WHITESPACE " " + FROM_KW "FROM" + WHITESPACE " " + L_PAREN "(" + LITERAL + STRING "'2024-12-25'" + R_PAREN ")" + WHITESPACE " " + TO_KW "TO" + WHITESPACE " " + L_PAREN "(" + LITERAL + STRING "'2024-12-26'" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" INSERT @@ -4674,7 +4677,7 @@ SOURCE_FILE WHITESPACE " " L_PAREN "(" WHITESPACE "\n " - WINDOW_DEF + WINDOW_SPEC ORDER_BY_CLAUSE ORDER_KW "ORDER" WHITESPACE " " @@ -4684,20 +4687,21 @@ SOURCE_FILE NAME_REF MONTH_KW "month" WHITESPACE " \n " - ROWS_KW "ROWS" - WHITESPACE " " - BETWEEN_KW "BETWEEN" - WHITESPACE " " - LITERAL - INT_NUMBER "2" - WHITESPACE " " - PRECEDING_KW "PRECEDING" - WHITESPACE " " - AND_KW "AND" - WHITESPACE " " - CURRENT_KW "CURRENT" - WHITESPACE " " - ROW_KW "ROW" + FRAME_CLAUSE + ROWS_KW "ROWS" + WHITESPACE " " + BETWEEN_KW "BETWEEN" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + WHITESPACE " " + PRECEDING_KW "PRECEDING" + WHITESPACE " " + AND_KW "AND" + WHITESPACE " " + CURRENT_KW "CURRENT" + WHITESPACE " " + ROW_KW "ROW" WHITESPACE "\n " R_PAREN ")" WHITESPACE " " @@ -4781,7 +4785,7 @@ SOURCE_FILE WHITESPACE " " L_PAREN "(" WHITESPACE "\n " - WINDOW_DEF + WINDOW_SPEC PARTITION_KW "PARTITION" WHITESPACE " " BY_KW "BY" diff --git a/crates/squawk_parser/tests/snapshots/tests__select_cte_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_cte_ok.snap index 69600a9b..4bc08d9e 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_cte_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_cte_ok.snap @@ -216,7 +216,8 @@ SOURCE_FILE WHITESPACE " " AS_KW "as" WHITESPACE " " - MATERIALIZED_KW "materialized" + MATERIALIZED + MATERIALIZED_KW "materialized" WHITESPACE " " L_PAREN "(" WHITESPACE "\n " @@ -258,9 +259,10 @@ SOURCE_FILE WHITESPACE " " AS_KW "as" WHITESPACE " " - NOT_KW "not" - WHITESPACE " " - MATERIALIZED_KW "materialized" + NOT_MATERIALIZED + NOT_KW "not" + WHITESPACE " " + MATERIALIZED_KW "materialized" WHITESPACE " " L_PAREN "(" WHITESPACE "\n " @@ -1344,7 +1346,7 @@ SOURCE_FILE WHITESPACE " " L_PAREN "(" WHITESPACE "\n " - WINDOW_DEF + WINDOW_SPEC PARTITION_KW "PARTITION" WHITESPACE " " BY_KW "BY" diff --git a/crates/squawk_parser/tests/snapshots/tests__select_funcs_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_funcs_ok.snap index 711962ee..0dc4d173 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_funcs_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_funcs_ok.snap @@ -3765,7 +3765,7 @@ SOURCE_FILE OVER_KW "over" WHITESPACE " " L_PAREN "(" - WINDOW_DEF + WINDOW_SPEC PARTITION_KW "partition" WHITESPACE " " BY_KW "by" @@ -3838,40 +3838,42 @@ SOURCE_FILE WINDOW_CLAUSE WINDOW_KW "window" WHITESPACE " " - NAME - OWNER_KW "owner" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" WINDOW_DEF - ORDER_BY_CLAUSE - ORDER_KW "order" - WHITESPACE " " - BY_KW "by" - WHITESPACE " " - SORT_BY - NAME_REF - IDENT "a" - R_PAREN ")" + NAME + OWNER_KW "owner" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + L_PAREN "(" + WINDOW_SPEC + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY + NAME_REF + IDENT "a" + R_PAREN ")" COMMA "," WHITESPACE " " - NAME - IDENT "w2" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" WINDOW_DEF - ORDER_BY_CLAUSE - ORDER_KW "order" - WHITESPACE " " - BY_KW "by" - WHITESPACE " " - SORT_BY - NAME_REF - IDENT "b" - R_PAREN ")" + NAME + IDENT "w2" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + L_PAREN "(" + WINDOW_SPEC + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY + NAME_REF + IDENT "b" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n" COMMENT "-- ^^^^^ make sure we allow using keywords" diff --git a/crates/squawk_parser/tests/snapshots/tests__select_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_ok.snap index c29f96ee..ff45cfe7 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_ok.snap @@ -3494,13 +3494,14 @@ SOURCE_FILE WINDOW_CLAUSE WINDOW_KW "window" WHITESPACE " " - NAME - IDENT "w" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" - R_PAREN ")" + WINDOW_DEF + NAME + IDENT "w" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + L_PAREN "(" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- with window def order by" @@ -3517,22 +3518,23 @@ SOURCE_FILE WINDOW_CLAUSE WINDOW_KW "window" WHITESPACE " " - NAME - IDENT "w" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" WINDOW_DEF - ORDER_BY_CLAUSE - ORDER_KW "order" - WHITESPACE " " - BY_KW "by" - WHITESPACE " " - SORT_BY - LITERAL - INT_NUMBER "1" - R_PAREN ")" + NAME + IDENT "w" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + L_PAREN "(" + WINDOW_SPEC + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY + LITERAL + INT_NUMBER "1" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- with window def frame_start, frame_end" @@ -3549,20 +3551,22 @@ SOURCE_FILE WINDOW_CLAUSE WINDOW_KW "window" WHITESPACE " " - NAME - IDENT "w" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" WINDOW_DEF - RANGE_KW "range" + NAME + IDENT "w" WHITESPACE " " - LITERAL - INT_NUMBER "1" + AS_KW "as" WHITESPACE " " - PRECEDING_KW "preceding" - R_PAREN ")" + L_PAREN "(" + WINDOW_SPEC + FRAME_CLAUSE + RANGE_KW "range" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + PRECEDING_KW "preceding" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n" SELECT @@ -3577,20 +3581,22 @@ SOURCE_FILE WINDOW_CLAUSE WINDOW_KW "window" WHITESPACE " " - NAME - IDENT "w" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" WINDOW_DEF - ROWS_KW "rows" + NAME + IDENT "w" WHITESPACE " " - LITERAL - INT_NUMBER "1" + AS_KW "as" WHITESPACE " " - PRECEDING_KW "preceding" - R_PAREN ")" + L_PAREN "(" + WINDOW_SPEC + FRAME_CLAUSE + ROWS_KW "rows" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + PRECEDING_KW "preceding" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n" SELECT @@ -3605,20 +3611,22 @@ SOURCE_FILE WINDOW_CLAUSE WINDOW_KW "window" WHITESPACE " " - NAME - IDENT "w" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" WINDOW_DEF - GROUPS_KW "groups" + NAME + IDENT "w" WHITESPACE " " - LITERAL - INT_NUMBER "1" + AS_KW "as" WHITESPACE " " - PRECEDING_KW "preceding" - R_PAREN ")" + L_PAREN "(" + WINDOW_SPEC + FRAME_CLAUSE + GROUPS_KW "groups" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + PRECEDING_KW "preceding" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- with window def frame_exclusion" @@ -3635,26 +3643,28 @@ SOURCE_FILE WINDOW_CLAUSE WINDOW_KW "window" WHITESPACE " " - NAME - IDENT "w" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" WINDOW_DEF - ROWS_KW "rows" - WHITESPACE " " - LITERAL - INT_NUMBER "1" - WHITESPACE " " - PRECEDING_KW "preceding" + NAME + IDENT "w" WHITESPACE " " - EXCLUDE_KW "exclude" + AS_KW "as" WHITESPACE " " - CURRENT_KW "current" - WHITESPACE " " - ROW_KW "row" - R_PAREN ")" + L_PAREN "(" + WINDOW_SPEC + FRAME_CLAUSE + ROWS_KW "rows" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + PRECEDING_KW "preceding" + WHITESPACE " " + EXCLUDE_KW "exclude" + WHITESPACE " " + CURRENT_KW "current" + WHITESPACE " " + ROW_KW "row" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n" SELECT @@ -3669,24 +3679,26 @@ SOURCE_FILE WINDOW_CLAUSE WINDOW_KW "window" WHITESPACE " " - NAME - IDENT "w" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" WINDOW_DEF - ROWS_KW "rows" - WHITESPACE " " - LITERAL - INT_NUMBER "1" - WHITESPACE " " - PRECEDING_KW "preceding" + NAME + IDENT "w" WHITESPACE " " - EXCLUDE_KW "exclude" + AS_KW "as" WHITESPACE " " - GROUP_KW "group" - R_PAREN ")" + L_PAREN "(" + WINDOW_SPEC + FRAME_CLAUSE + ROWS_KW "rows" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + PRECEDING_KW "preceding" + WHITESPACE " " + EXCLUDE_KW "exclude" + WHITESPACE " " + GROUP_KW "group" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n" SELECT @@ -3701,24 +3713,26 @@ SOURCE_FILE WINDOW_CLAUSE WINDOW_KW "window" WHITESPACE " " - NAME - IDENT "w" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" WINDOW_DEF - ROWS_KW "rows" - WHITESPACE " " - LITERAL - INT_NUMBER "1" - WHITESPACE " " - PRECEDING_KW "preceding" + NAME + IDENT "w" WHITESPACE " " - EXCLUDE_KW "exclude" + AS_KW "as" WHITESPACE " " - TIES_KW "ties" - R_PAREN ")" + L_PAREN "(" + WINDOW_SPEC + FRAME_CLAUSE + ROWS_KW "rows" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + PRECEDING_KW "preceding" + WHITESPACE " " + EXCLUDE_KW "exclude" + WHITESPACE " " + TIES_KW "ties" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n" SELECT @@ -3733,26 +3747,28 @@ SOURCE_FILE WINDOW_CLAUSE WINDOW_KW "window" WHITESPACE " " - NAME - IDENT "w" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - L_PAREN "(" WINDOW_DEF - ROWS_KW "rows" - WHITESPACE " " - LITERAL - INT_NUMBER "1" + NAME + IDENT "w" WHITESPACE " " - PRECEDING_KW "preceding" + AS_KW "as" WHITESPACE " " - EXCLUDE_KW "exclude" - WHITESPACE " " - NO_KW "no" - WHITESPACE " " - OTHERS_KW "others" - R_PAREN ")" + L_PAREN "(" + WINDOW_SPEC + FRAME_CLAUSE + ROWS_KW "rows" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + PRECEDING_KW "preceding" + WHITESPACE " " + EXCLUDE_KW "exclude" + WHITESPACE " " + NO_KW "no" + WHITESPACE " " + OTHERS_KW "others" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n\n" COMMENT "-- select_having_clause" diff --git a/crates/squawk_parser/tests/snapshots/tests__set_transaction_ok.snap b/crates/squawk_parser/tests/snapshots/tests__set_transaction_ok.snap index c09d765c..7b8cb192 100644 --- a/crates/squawk_parser/tests/snapshots/tests__set_transaction_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__set_transaction_ok.snap @@ -27,18 +27,20 @@ SOURCE_FILE WHITESPACE " " TRANSACTION_KW "TRANSACTION" WHITESPACE " " - ISOLATION_KW "ISOLATION" - WHITESPACE " " - LEVEL_KW "LEVEL" - WHITESPACE " " - READ_KW "READ" - WHITESPACE " " - COMMITTED_KW "COMMITTED" + READ_COMMITTED + ISOLATION_KW "ISOLATION" + WHITESPACE " " + LEVEL_KW "LEVEL" + WHITESPACE " " + READ_KW "READ" + WHITESPACE " " + COMMITTED_KW "COMMITTED" COMMA "," WHITESPACE " " - READ_KW "read" - WHITESPACE " " - WRITE_KW "write" + READ_WRITE + READ_KW "read" + WHITESPACE " " + WRITE_KW "write" SEMICOLON ";" WHITESPACE "\n\n" SET_TRANSACTION @@ -46,21 +48,24 @@ SOURCE_FILE WHITESPACE " " TRANSACTION_KW "TRANSACTION" WHITESPACE " " - ISOLATION_KW "ISOLATION" - WHITESPACE " " - LEVEL_KW "LEVEL" - WHITESPACE " " - SERIALIZABLE_KW "SERIALIZABLE" + SERIALIZABLE + ISOLATION_KW "ISOLATION" + WHITESPACE " " + LEVEL_KW "LEVEL" + WHITESPACE " " + SERIALIZABLE_KW "SERIALIZABLE" COMMA "," WHITESPACE " " - READ_KW "READ" - WHITESPACE " " - WRITE_KW "WRITE" + READ_WRITE + READ_KW "READ" + WHITESPACE " " + WRITE_KW "WRITE" COMMA "," WHITESPACE " " - NOT_KW "NOT" - WHITESPACE " " - DEFERRABLE_KW "DEFERRABLE" + NOT_DEFERRABLE + NOT_KW "NOT" + WHITESPACE " " + DEFERRABLE_KW "DEFERRABLE" SEMICOLON ";" WHITESPACE "\n\n\n" COMMENT "-- no commas is postgres historical according to gram.y" @@ -70,18 +75,21 @@ SOURCE_FILE WHITESPACE " " TRANSACTION_KW "TRANSACTION" WHITESPACE " " - ISOLATION_KW "ISOLATION" - WHITESPACE " " - LEVEL_KW "LEVEL" - WHITESPACE " " - SERIALIZABLE_KW "SERIALIZABLE" - WHITESPACE " " - READ_KW "READ" - WHITESPACE " " - WRITE_KW "WRITE" - WHITESPACE " " - NOT_KW "NOT" - WHITESPACE " " - DEFERRABLE_KW "DEFERRABLE" + SERIALIZABLE + ISOLATION_KW "ISOLATION" + WHITESPACE " " + LEVEL_KW "LEVEL" + WHITESPACE " " + SERIALIZABLE_KW "SERIALIZABLE" + WHITESPACE " " + READ_WRITE + READ_KW "READ" + WHITESPACE " " + WRITE_KW "WRITE" + WHITESPACE " " + NOT_DEFERRABLE + NOT_KW "NOT" + WHITESPACE " " + DEFERRABLE_KW "DEFERRABLE" SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__transaction_ok.snap b/crates/squawk_parser/tests/snapshots/tests__transaction_ok.snap index 39f7627a..38f91af6 100644 --- a/crates/squawk_parser/tests/snapshots/tests__transaction_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__transaction_ok.snap @@ -71,82 +71,95 @@ SOURCE_FILE BEGIN BEGIN_KW "begin" WHITESPACE " \n " - ISOLATION_KW "isolation" - WHITESPACE " " - LEVEL_KW "level" - WHITESPACE " " - READ_KW "read" - WHITESPACE " " - COMMITTED_KW "committed" + READ_COMMITTED + ISOLATION_KW "isolation" + WHITESPACE " " + LEVEL_KW "level" + WHITESPACE " " + READ_KW "read" + WHITESPACE " " + COMMITTED_KW "committed" WHITESPACE "\n " - READ_KW "read" - WHITESPACE " " - ONLY_KW "only" + READ_ONLY + READ_KW "read" + WHITESPACE " " + ONLY_KW "only" WHITESPACE "\n " - READ_KW "read" - WHITESPACE " " - WRITE_KW "write" + READ_WRITE + READ_KW "read" + WHITESPACE " " + WRITE_KW "write" WHITESPACE "\n " - DEFERRABLE_KW "deferrable" + DEFERRABLE + DEFERRABLE_KW "deferrable" WHITESPACE "\n " - NOT_KW "not" - WHITESPACE " " - DEFERRABLE_KW "deferrable" + NOT_DEFERRABLE + NOT_KW "not" + WHITESPACE " " + DEFERRABLE_KW "deferrable" SEMICOLON ";" WHITESPACE "\n\n" BEGIN BEGIN_KW "begin" WHITESPACE "\n " - ISOLATION_KW "isolation" - WHITESPACE " " - LEVEL_KW "level" - WHITESPACE " " - READ_KW "read" - WHITESPACE " " - COMMITTED_KW "committed" + READ_COMMITTED + ISOLATION_KW "isolation" + WHITESPACE " " + LEVEL_KW "level" + WHITESPACE " " + READ_KW "read" + WHITESPACE " " + COMMITTED_KW "committed" COMMA "," WHITESPACE "\n " - ISOLATION_KW "isolation" - WHITESPACE " " - LEVEL_KW "level" - WHITESPACE " " - READ_KW "read" - WHITESPACE " " - UNCOMMITTED_KW "uncommitted" + READ_UNCOMMITTED + ISOLATION_KW "isolation" + WHITESPACE " " + LEVEL_KW "level" + WHITESPACE " " + READ_KW "read" + WHITESPACE " " + UNCOMMITTED_KW "uncommitted" COMMA "," WHITESPACE "\n " - ISOLATION_KW "isolation" - WHITESPACE " " - LEVEL_KW "level" - WHITESPACE " " - REPEATABLE_KW "repeatable" - WHITESPACE " " - READ_KW "read" + REPEATABLE_READ + ISOLATION_KW "isolation" + WHITESPACE " " + LEVEL_KW "level" + WHITESPACE " " + REPEATABLE_KW "repeatable" + WHITESPACE " " + READ_KW "read" COMMA "," WHITESPACE "\n " - ISOLATION_KW "isolation" - WHITESPACE " " - LEVEL_KW "level" - WHITESPACE " " - SERIALIZABLE_KW "serializable" + SERIALIZABLE + ISOLATION_KW "isolation" + WHITESPACE " " + LEVEL_KW "level" + WHITESPACE " " + SERIALIZABLE_KW "serializable" COMMA "," WHITESPACE "\n " - READ_KW "read" - WHITESPACE " " - ONLY_KW "only" + READ_ONLY + READ_KW "read" + WHITESPACE " " + ONLY_KW "only" COMMA "," WHITESPACE "\n " - READ_KW "read" - WHITESPACE " " - WRITE_KW "write" + READ_WRITE + READ_KW "read" + WHITESPACE " " + WRITE_KW "write" COMMA "," WHITESPACE "\n " - DEFERRABLE_KW "deferrable" + DEFERRABLE + DEFERRABLE_KW "deferrable" COMMA "," WHITESPACE "\n " - NOT_KW "not" - WHITESPACE " " - DEFERRABLE_KW "deferrable" + NOT_DEFERRABLE + NOT_KW "not" + WHITESPACE " " + DEFERRABLE_KW "deferrable" SEMICOLON ";" WHITESPACE "\n\n" BEGIN @@ -154,27 +167,32 @@ SOURCE_FILE WHITESPACE " " TRANSACTION_KW "transaction" WHITESPACE "\n " - ISOLATION_KW "isolation" - WHITESPACE " " - LEVEL_KW "level" - WHITESPACE " " - READ_KW "read" - WHITESPACE " " - COMMITTED_KW "committed" + READ_COMMITTED + ISOLATION_KW "isolation" + WHITESPACE " " + LEVEL_KW "level" + WHITESPACE " " + READ_KW "read" + WHITESPACE " " + COMMITTED_KW "committed" WHITESPACE "\n " - READ_KW "read" - WHITESPACE " " - ONLY_KW "only" + READ_ONLY + READ_KW "read" + WHITESPACE " " + ONLY_KW "only" WHITESPACE "\n " - READ_KW "read" - WHITESPACE " " - WRITE_KW "write" + READ_WRITE + READ_KW "read" + WHITESPACE " " + WRITE_KW "write" WHITESPACE "\n " - DEFERRABLE_KW "deferrable" + DEFERRABLE + DEFERRABLE_KW "deferrable" WHITESPACE "\n " - NOT_KW "not" - WHITESPACE " " - DEFERRABLE_KW "deferrable" + NOT_DEFERRABLE + NOT_KW "not" + WHITESPACE " " + DEFERRABLE_KW "deferrable" SEMICOLON ";" WHITESPACE "\n\n" BEGIN @@ -182,56 +200,64 @@ SOURCE_FILE WHITESPACE " " TRANSACTION_KW "transaction" WHITESPACE "\n " - ISOLATION_KW "isolation" - WHITESPACE " " - LEVEL_KW "level" - WHITESPACE " " - READ_KW "read" - WHITESPACE " " - COMMITTED_KW "committed" + READ_COMMITTED + ISOLATION_KW "isolation" + WHITESPACE " " + LEVEL_KW "level" + WHITESPACE " " + READ_KW "read" + WHITESPACE " " + COMMITTED_KW "committed" COMMA "," WHITESPACE "\n " - ISOLATION_KW "isolation" - WHITESPACE " " - LEVEL_KW "level" - WHITESPACE " " - READ_KW "read" - WHITESPACE " " - UNCOMMITTED_KW "uncommitted" + READ_UNCOMMITTED + ISOLATION_KW "isolation" + WHITESPACE " " + LEVEL_KW "level" + WHITESPACE " " + READ_KW "read" + WHITESPACE " " + UNCOMMITTED_KW "uncommitted" COMMA "," WHITESPACE "\n " - ISOLATION_KW "isolation" - WHITESPACE " " - LEVEL_KW "level" - WHITESPACE " " - REPEATABLE_KW "repeatable" - WHITESPACE " " - READ_KW "read" + REPEATABLE_READ + ISOLATION_KW "isolation" + WHITESPACE " " + LEVEL_KW "level" + WHITESPACE " " + REPEATABLE_KW "repeatable" + WHITESPACE " " + READ_KW "read" COMMA "," WHITESPACE "\n " - ISOLATION_KW "isolation" - WHITESPACE " " - LEVEL_KW "level" - WHITESPACE " " - SERIALIZABLE_KW "serializable" + SERIALIZABLE + ISOLATION_KW "isolation" + WHITESPACE " " + LEVEL_KW "level" + WHITESPACE " " + SERIALIZABLE_KW "serializable" COMMA "," WHITESPACE "\n " - READ_KW "read" - WHITESPACE " " - ONLY_KW "only" + READ_ONLY + READ_KW "read" + WHITESPACE " " + ONLY_KW "only" COMMA "," WHITESPACE "\n " - READ_KW "read" - WHITESPACE " " - WRITE_KW "write" + READ_WRITE + READ_KW "read" + WHITESPACE " " + WRITE_KW "write" COMMA "," WHITESPACE "\n " - DEFERRABLE_KW "deferrable" + DEFERRABLE + DEFERRABLE_KW "deferrable" COMMA "," WHITESPACE "\n " - NOT_KW "not" - WHITESPACE " " - DEFERRABLE_KW "deferrable" + NOT_DEFERRABLE + NOT_KW "not" + WHITESPACE " " + DEFERRABLE_KW "deferrable" SEMICOLON ";" WHITESPACE "\n\n" PREPARE_TRANSACTION diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index 0bfb0e52..d23f0af2 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -1221,6 +1221,14 @@ pub struct AttachPartition { pub(crate) syntax: SyntaxNode, } impl AttachPartition { + #[inline] + pub fn partition_type(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn path(&self) -> Option { + support::child(&self.syntax) + } #[inline] pub fn attach_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::ATTACH_KW) @@ -4719,6 +4727,25 @@ impl ForeignKeyConstraint { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FrameClause { + pub(crate) syntax: SyntaxNode, +} +impl FrameClause { + #[inline] + pub fn groups_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::GROUPS_KW) + } + #[inline] + pub fn range_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::RANGE_KW) + } + #[inline] + pub fn rows_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ROWS_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FromClause { pub(crate) syntax: SyntaxNode, @@ -6122,6 +6149,17 @@ impl MatchSimple { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Materialized { + pub(crate) syntax: SyntaxNode, +} +impl Materialized { + #[inline] + pub fn materialized_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::MATERIALIZED_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Merge { pub(crate) syntax: SyntaxNode, @@ -6362,6 +6400,21 @@ impl NotLike { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NotMaterialized { + pub(crate) syntax: SyntaxNode, +} +impl NotMaterialized { + #[inline] + pub fn materialized_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::MATERIALIZED_KW) + } + #[inline] + pub fn not_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NOT_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NotNullConstraint { pub(crate) syntax: SyntaxNode, @@ -7108,6 +7161,122 @@ impl PartitionBy { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PartitionDefault { + pub(crate) syntax: SyntaxNode, +} +impl PartitionDefault { + #[inline] + pub fn default_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::DEFAULT_KW) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PartitionForValuesFrom { + pub(crate) syntax: SyntaxNode, +} +impl PartitionForValuesFrom { + #[inline] + pub fn exprs(&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) + } + #[inline] + pub fn for_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::FOR_KW) + } + #[inline] + pub fn from_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::FROM_KW) + } + #[inline] + pub fn to_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::TO_KW) + } + #[inline] + pub fn values_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::VALUES_KW) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PartitionForValuesIn { + pub(crate) syntax: SyntaxNode, +} +impl PartitionForValuesIn { + #[inline] + pub fn exprs(&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) + } + #[inline] + pub fn for_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::FOR_KW) + } + #[inline] + pub fn in_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::IN_KW) + } + #[inline] + pub fn values_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::VALUES_KW) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PartitionForValuesWith { + pub(crate) syntax: SyntaxNode, +} +impl PartitionForValuesWith { + #[inline] + pub fn literal(&self) -> Option { + support::child(&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) + } + #[inline] + pub fn comma_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::COMMA) + } + #[inline] + pub fn for_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::FOR_KW) + } + #[inline] + pub fn ident_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::IDENT) + } + #[inline] + pub fn values_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::VALUES_KW) + } + #[inline] + pub fn with_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::WITH_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct PartitionItem { pub(crate) syntax: SyntaxNode, @@ -7337,6 +7506,14 @@ impl ReadCommitted { support::token(&self.syntax, SyntaxKind::COMMITTED_KW) } #[inline] + pub fn isolation_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ISOLATION_KW) + } + #[inline] + pub fn level_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::LEVEL_KW) + } + #[inline] pub fn read_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::READ_KW) } @@ -7362,6 +7539,14 @@ pub struct ReadUncommitted { pub(crate) syntax: SyntaxNode, } impl ReadUncommitted { + #[inline] + pub fn isolation_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ISOLATION_KW) + } + #[inline] + pub fn level_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::LEVEL_KW) + } #[inline] pub fn read_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::READ_KW) @@ -7618,6 +7803,14 @@ pub struct RepeatableRead { pub(crate) syntax: SyntaxNode, } impl RepeatableRead { + #[inline] + pub fn isolation_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ISOLATION_KW) + } + #[inline] + pub fn level_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::LEVEL_KW) + } #[inline] pub fn read_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::READ_KW) @@ -8048,6 +8241,14 @@ pub struct Serializable { pub(crate) syntax: SyntaxNode, } impl Serializable { + #[inline] + pub fn isolation_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ISOLATION_KW) + } + #[inline] + pub fn level_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::LEVEL_KW) + } #[inline] pub fn serializable_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::SERIALIZABLE_KW) @@ -8788,11 +8989,7 @@ impl TimeType { support::child(&self.syntax) } #[inline] - pub fn with_timezone(&self) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn without_timezone(&self) -> Option { + pub fn timezone(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -8813,37 +9010,6 @@ impl TimeType { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct TransactionModeIsolationLevel { - pub(crate) syntax: SyntaxNode, -} -impl TransactionModeIsolationLevel { - #[inline] - pub fn read_committed(&self) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn read_uncommitted(&self) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn repeatable_read(&self) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn serializable(&self) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn isolation_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::ISOLATION_KW) - } - #[inline] - pub fn level_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::LEVEL_KW) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TransactionModeList { pub(crate) syntax: SyntaxNode, @@ -9145,8 +9311,8 @@ pub struct WindowClause { } impl WindowClause { #[inline] - pub fn ident_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IDENT) + pub fn window_defs(&self) -> AstChildren { + support::children(&self.syntax) } #[inline] pub fn window_token(&self) -> Option { @@ -9160,20 +9326,24 @@ pub struct WindowDef { } impl WindowDef { #[inline] - pub fn expr(&self) -> Option { + pub fn name(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn by_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::BY_KW) + pub fn window_spec(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn ident_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::IDENT) + pub fn l_paren_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::L_PAREN) } #[inline] - pub fn partition_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::PARTITION_KW) + pub fn r_paren_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::R_PAREN) + } + #[inline] + pub fn as_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::AS_KW) } } @@ -9188,6 +9358,37 @@ impl WindowFuncOption { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct WindowSpec { + pub(crate) syntax: SyntaxNode, +} +impl WindowSpec { + #[inline] + pub fn exprs(&self) -> AstChildren { + support::children(&self.syntax) + } + #[inline] + pub fn frame_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn order_by_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn by_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::BY_KW) + } + #[inline] + pub fn ident_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::IDENT) + } + #[inline] + pub fn partition_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::PARTITION_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct WithClause { pub(crate) syntax: SyntaxNode, @@ -9280,6 +9481,14 @@ pub struct WithTable { pub(crate) syntax: SyntaxNode, } impl WithTable { + #[inline] + pub fn materialized(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn not_materialized(&self) -> Option { + support::child(&self.syntax) + } #[inline] pub fn with_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::WITH_KW) @@ -9610,6 +9819,14 @@ pub enum ParamMode { ParamVariadic(ParamVariadic), } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum PartitionType { + PartitionDefault(PartitionDefault), + PartitionForValuesFrom(PartitionForValuesFrom), + PartitionForValuesIn(PartitionForValuesIn), + PartitionForValuesWith(PartitionForValuesWith), +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum RefAction { Cascade(Cascade), @@ -9817,13 +10034,22 @@ pub enum TableConstraint { UniqueConstraint(UniqueConstraint), } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Timezone { + WithTimezone(WithTimezone), + WithoutTimezone(WithoutTimezone), +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum TransactionMode { Deferrable(Deferrable), NotDeferrable(NotDeferrable), + ReadCommitted(ReadCommitted), ReadOnly(ReadOnly), + ReadUncommitted(ReadUncommitted), ReadWrite(ReadWrite), - TransactionModeIsolationLevel(TransactionModeIsolationLevel), + RepeatableRead(RepeatableRead), + Serializable(Serializable), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -13743,6 +13969,24 @@ impl AstNode for ForeignKeyConstraint { &self.syntax } } +impl AstNode for FrameClause { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::FRAME_CLAUSE + } + #[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 FromClause { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -15039,6 +15283,24 @@ impl AstNode for MatchSimple { &self.syntax } } +impl AstNode for Materialized { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::MATERIALIZED + } + #[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 Merge { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -15327,6 +15589,24 @@ impl AstNode for NotLike { &self.syntax } } +impl AstNode for NotMaterialized { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::NOT_MATERIALIZED + } + #[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 NotNullConstraint { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -15939,6 +16219,78 @@ impl AstNode for PartitionBy { &self.syntax } } +impl AstNode for PartitionDefault { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::PARTITION_DEFAULT + } + #[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 PartitionForValuesFrom { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::PARTITION_FOR_VALUES_FROM + } + #[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 PartitionForValuesIn { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::PARTITION_FOR_VALUES_IN + } + #[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 PartitionForValuesWith { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::PARTITION_FOR_VALUES_WITH + } + #[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 PartitionItem { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -17649,24 +18001,6 @@ impl AstNode for TimeType { &self.syntax } } -impl AstNode for TransactionModeIsolationLevel { - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { - kind == SyntaxKind::TRANSACTION_MODE_ISOLATION_LEVEL - } - #[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 TransactionModeList { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -18027,6 +18361,24 @@ impl AstNode for WindowFuncOption { &self.syntax } } +impl AstNode for WindowSpec { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::WINDOW_SPEC + } + #[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 WithClause { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -19667,6 +20019,72 @@ impl From for ParamMode { ParamMode::ParamVariadic(node) } } +impl AstNode for PartitionType { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + matches!( + kind, + SyntaxKind::PARTITION_DEFAULT + | SyntaxKind::PARTITION_FOR_VALUES_FROM + | SyntaxKind::PARTITION_FOR_VALUES_IN + | SyntaxKind::PARTITION_FOR_VALUES_WITH + ) + } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + SyntaxKind::PARTITION_DEFAULT => { + PartitionType::PartitionDefault(PartitionDefault { syntax }) + } + SyntaxKind::PARTITION_FOR_VALUES_FROM => { + PartitionType::PartitionForValuesFrom(PartitionForValuesFrom { syntax }) + } + SyntaxKind::PARTITION_FOR_VALUES_IN => { + PartitionType::PartitionForValuesIn(PartitionForValuesIn { syntax }) + } + SyntaxKind::PARTITION_FOR_VALUES_WITH => { + PartitionType::PartitionForValuesWith(PartitionForValuesWith { syntax }) + } + _ => { + return None; + } + }; + Some(res) + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + match self { + PartitionType::PartitionDefault(it) => &it.syntax, + PartitionType::PartitionForValuesFrom(it) => &it.syntax, + PartitionType::PartitionForValuesIn(it) => &it.syntax, + PartitionType::PartitionForValuesWith(it) => &it.syntax, + } + } +} +impl From for PartitionType { + #[inline] + fn from(node: PartitionDefault) -> PartitionType { + PartitionType::PartitionDefault(node) + } +} +impl From for PartitionType { + #[inline] + fn from(node: PartitionForValuesFrom) -> PartitionType { + PartitionType::PartitionForValuesFrom(node) + } +} +impl From for PartitionType { + #[inline] + fn from(node: PartitionForValuesIn) -> PartitionType { + PartitionType::PartitionForValuesIn(node) + } +} +impl From for PartitionType { + #[inline] + fn from(node: PartitionForValuesWith) -> PartitionType { + PartitionType::PartitionForValuesWith(node) + } +} impl AstNode for RefAction { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -21545,6 +21963,45 @@ impl From for TableConstraint { TableConstraint::UniqueConstraint(node) } } +impl AstNode for Timezone { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + matches!( + kind, + SyntaxKind::WITH_TIMEZONE | SyntaxKind::WITHOUT_TIMEZONE + ) + } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + SyntaxKind::WITH_TIMEZONE => Timezone::WithTimezone(WithTimezone { syntax }), + SyntaxKind::WITHOUT_TIMEZONE => Timezone::WithoutTimezone(WithoutTimezone { syntax }), + _ => { + return None; + } + }; + Some(res) + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + match self { + Timezone::WithTimezone(it) => &it.syntax, + Timezone::WithoutTimezone(it) => &it.syntax, + } + } +} +impl From for Timezone { + #[inline] + fn from(node: WithTimezone) -> Timezone { + Timezone::WithTimezone(node) + } +} +impl From for Timezone { + #[inline] + fn from(node: WithoutTimezone) -> Timezone { + Timezone::WithoutTimezone(node) + } +} impl AstNode for TransactionMode { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -21552,9 +22009,12 @@ impl AstNode for TransactionMode { kind, SyntaxKind::DEFERRABLE | SyntaxKind::NOT_DEFERRABLE + | SyntaxKind::READ_COMMITTED | SyntaxKind::READ_ONLY + | SyntaxKind::READ_UNCOMMITTED | SyntaxKind::READ_WRITE - | SyntaxKind::TRANSACTION_MODE_ISOLATION_LEVEL + | SyntaxKind::REPEATABLE_READ + | SyntaxKind::SERIALIZABLE ) } #[inline] @@ -21562,13 +22022,16 @@ impl AstNode for TransactionMode { let res = match syntax.kind() { SyntaxKind::DEFERRABLE => TransactionMode::Deferrable(Deferrable { syntax }), SyntaxKind::NOT_DEFERRABLE => TransactionMode::NotDeferrable(NotDeferrable { syntax }), + SyntaxKind::READ_COMMITTED => TransactionMode::ReadCommitted(ReadCommitted { syntax }), SyntaxKind::READ_ONLY => TransactionMode::ReadOnly(ReadOnly { syntax }), + SyntaxKind::READ_UNCOMMITTED => { + TransactionMode::ReadUncommitted(ReadUncommitted { syntax }) + } SyntaxKind::READ_WRITE => TransactionMode::ReadWrite(ReadWrite { syntax }), - SyntaxKind::TRANSACTION_MODE_ISOLATION_LEVEL => { - TransactionMode::TransactionModeIsolationLevel(TransactionModeIsolationLevel { - syntax, - }) + SyntaxKind::REPEATABLE_READ => { + TransactionMode::RepeatableRead(RepeatableRead { syntax }) } + SyntaxKind::SERIALIZABLE => TransactionMode::Serializable(Serializable { syntax }), _ => { return None; } @@ -21580,9 +22043,12 @@ impl AstNode for TransactionMode { match self { TransactionMode::Deferrable(it) => &it.syntax, TransactionMode::NotDeferrable(it) => &it.syntax, + TransactionMode::ReadCommitted(it) => &it.syntax, TransactionMode::ReadOnly(it) => &it.syntax, + TransactionMode::ReadUncommitted(it) => &it.syntax, TransactionMode::ReadWrite(it) => &it.syntax, - TransactionMode::TransactionModeIsolationLevel(it) => &it.syntax, + TransactionMode::RepeatableRead(it) => &it.syntax, + TransactionMode::Serializable(it) => &it.syntax, } } } @@ -21598,22 +22064,40 @@ impl From for TransactionMode { TransactionMode::NotDeferrable(node) } } +impl From for TransactionMode { + #[inline] + fn from(node: ReadCommitted) -> TransactionMode { + TransactionMode::ReadCommitted(node) + } +} impl From for TransactionMode { #[inline] fn from(node: ReadOnly) -> TransactionMode { TransactionMode::ReadOnly(node) } } +impl From for TransactionMode { + #[inline] + fn from(node: ReadUncommitted) -> TransactionMode { + TransactionMode::ReadUncommitted(node) + } +} impl From for TransactionMode { #[inline] fn from(node: ReadWrite) -> TransactionMode { TransactionMode::ReadWrite(node) } } -impl From for TransactionMode { +impl From for TransactionMode { + #[inline] + fn from(node: RepeatableRead) -> TransactionMode { + TransactionMode::RepeatableRead(node) + } +} +impl From for TransactionMode { #[inline] - fn from(node: TransactionModeIsolationLevel) -> TransactionMode { - TransactionMode::TransactionModeIsolationLevel(node) + fn from(node: Serializable) -> TransactionMode { + TransactionMode::Serializable(node) } } impl AstNode for Type { diff --git a/crates/squawk_syntax/src/postgresql.ungram b/crates/squawk_syntax/src/postgresql.ungram index 45e4d0d9..6036c437 100644 --- a/crates/squawk_syntax/src/postgresql.ungram +++ b/crates/squawk_syntax/src/postgresql.ungram @@ -243,10 +243,14 @@ BitType = DoubleType = 'double' 'precision' +Timezone = + WithTimezone +| WithoutTimezone + TimeType = ('time' | 'timestamp') ('(' Literal ')')? - (WithTimezone | WithoutTimezone)? + Timezone? IntervalType = 'interval' @@ -515,8 +519,11 @@ GroupingExpr = HavingClause = 'having' +WindowDef = + Name 'as' '(' WindowSpec ')' + WindowClause = - 'window' '#ident' + 'window' (WindowDef (',' WindowDef)*) LimitClause = 'limit' @@ -632,19 +639,17 @@ Select = FilterClause? Serializable = - 'serializable' + 'isolation' 'level' 'serializable' RepeatableRead = - 'repeatable' 'read' + 'isolation' 'level' 'repeatable' 'read' ReadCommitted = - 'read' 'committed' + 'isolation' 'level' 'read' 'committed' ReadUncommitted = - 'read' 'uncommitted' + 'isolation' 'level' 'read' 'uncommitted' -TransactionModeIsolationLevel = - 'isolation' 'level' ( Serializable | RepeatableRead | ReadCommitted | ReadUncommitted ) ReadWrite = 'read' 'write' @@ -659,7 +664,10 @@ NotDeferrable = 'not' 'deferrable' TransactionMode = - TransactionModeIsolationLevel + Serializable +| RepeatableRead +| ReadCommitted +| ReadUncommitted | ReadWrite | ReadOnly | Deferrable @@ -979,8 +987,14 @@ OverClause = WithinClause = 'within' 'group' '(' OrderByClause ')' +Materialized = + 'materialized' + +NotMaterialized = + 'not' 'materialized' + WithTable = - 'with' + 'with' (Materialized | NotMaterialized)? WithClause = 'with' 'recursive'? (WithTable (',' WithTable)*) @@ -1071,8 +1085,11 @@ ConstraintWhereClause = ExcludeConstraint = 'exclude' ConstraintIndexMethod? ConstraintExclusions -WindowDef = - '#ident'? ('partition' 'by' Expr)? +FrameClause = + ('range' | 'rows' | 'groups') + +WindowSpec = + '#ident'? ('partition' 'by' (Expr (',' Expr)*))? OrderByClause? FrameClause? AlterStatistics = 'alter' 'statistics' NameRef @@ -1630,8 +1647,26 @@ DropColumn = AddColumn = 'add' 'column'? IfNotExists? NameRef Type Collate? (Constraint (',' Constraint)*)? +PartitionForValuesWith = + 'for' 'values' 'with' '(' '#ident' Literal ',' '#ident' Literal ')' + +PartitionForValuesIn = + 'for' 'values' 'in' '(' (Expr (',' Expr)*) ')' + +PartitionForValuesFrom = + 'for' 'values' 'from' '(' (Expr (',' Expr)*) ')' 'to' '(' (Expr (',' Expr)*) ')' + +PartitionDefault = + 'default' + +PartitionType = + PartitionForValuesWith +| PartitionForValuesIn +| PartitionForValuesFrom +| PartitionDefault + AttachPartition = - 'attach' 'partition' + 'attach' 'partition' Path PartitionType SetTablespace = 'set' 'tablespace' NameRef From 667d9f74aee22507dcd22a2d0f7380725e4b4dc4 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Tue, 24 Jun 2025 19:21:46 -0400 Subject: [PATCH 4/4] fix --- crates/squawk_parser/src/grammar.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index c220709a..0a8bed6f 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -5300,16 +5300,10 @@ fn rollback(p: &mut Parser<'_>) -> CompletedMarker { m.complete(p, ROLLBACK) } +#[derive(Default)] struct StmtRestrictions { begin_end_allowed: bool, } -impl Default for StmtRestrictions { - fn default() -> Self { - Self { - begin_end_allowed: false, - } - } -} fn stmt(p: &mut Parser, r: &StmtRestrictions) -> Option { match (p.current(), p.nth(1)) {