diff --git a/crates/squawk_parser/src/generated/syntax_kind.rs b/crates/squawk_parser/src/generated/syntax_kind.rs index d83a6c4e..30eef395 100644 --- a/crates/squawk_parser/src/generated/syntax_kind.rs +++ b/crates/squawk_parser/src/generated/syntax_kind.rs @@ -858,6 +858,10 @@ pub enum SyntaxKind { NO_ACTION, NO_FORCE_RLS, NO_INHERIT, + NULLS_DISTINCT, + NULLS_FIRST, + NULLS_LAST, + NULLS_NOT_DISTINCT, NULL_CONSTRAINT, OFFSET_CLAUSE, OF_TYPE, @@ -966,6 +970,10 @@ pub enum SyntaxKind { SET_WITHOUT_OIDS, SHOW, SIMILAR_TO, + SORT_ASC, + SORT_BY, + SORT_DESC, + SORT_USING, SOURCE_FILE, STMT, STORAGE, diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index 04085626..2d106347 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -1463,6 +1463,43 @@ fn delimited( p.expect(ket); } +/// This is essentially the same as [delimited] but without the wrapping +/// tokens, i.e., `(` `)` +fn separated( + p: &mut Parser<'_>, + delim: SyntaxKind, + unexpected_delim_message: impl Fn() -> String, + first_set: TokenSet, + follow_set: TokenSet, + mut parser: impl FnMut(&mut Parser<'_>) -> bool, +) { + while !p.at(EOF) { + if p.at(delim) { + // Recover if an argument is missing and only got a delimiter, + // e.g. `(a, , b)`. + // Wrap the erroneous delimiter in an error node so that fixup logic gets rid of it. + // FIXME: Ideally this should be handled in fixup in a structured way, but our list + // nodes currently have no concept of a missing node between two delimiters. + // So doing it this way is easier. + let m = p.start(); + p.error(unexpected_delim_message()); + p.bump(delim); + m.complete(p, ERROR); + continue; + } + if !parser(p) { + break; + } + if !p.eat(delim) { + if p.at_ts(first_set) && !p.at_ts(follow_set) { + p.error(format!("expected {delim:?}")); + } else { + break; + } + } + } +} + fn name_ref(p: &mut Parser<'_>) -> Option { opt_name_ref(p).or_else(|| { p.error("expected name"); @@ -1516,8 +1553,10 @@ fn path_segment(p: &mut Parser<'_>, kind: SyntaxKind) { m.complete(p, PATH_SEGMENT); } +const PATH_FIRST: TokenSet = COL_LABEL_FIRST; + fn opt_path(p: &mut Parser<'_>, kind: SyntaxKind) -> Option { - if !p.at_ts(COL_LABEL_FIRST) { + if !p.at_ts(PATH_FIRST) { return None; } let m = p.start(); @@ -2330,7 +2369,7 @@ const COMPOUND_SELECT_FIRST: TokenSet = TokenSet::new(&[UNION_KW, INTERSECT_KW, // with_query_name [ ( column_name [, ...] ) ] AS [ [ NOT ] MATERIALIZED ] ( select | values | insert | update | delete | merge ) // [ SEARCH { BREADTH | DEPTH } FIRST BY column_name [, ...] SET search_seq_col_name ] // [ CYCLE column_name [, ...] SET cycle_mark_col_name [ TO cycle_mark_value DEFAULT cycle_mark_default ] USING cycle_path_col_name ] -fn with_query(p: &mut Parser<'_>) -> Option { +fn with_query(p: &mut Parser<'_>) -> CompletedMarker { let m = p.start(); name(p); opt_column_list_with(p, ColumnDefKind::Name); @@ -2351,23 +2390,27 @@ fn with_query(p: &mut Parser<'_>) -> Option { } p.expect(FIRST_KW); p.expect(BY_KW); - while !p.at(EOF) && !p.at(COMMA) { - name_ref(p); - if !p.eat(COMMA) { - break; - } - } + 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); } // [ 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) { - while !p.at(EOF) && !p.at(COMMA) { - name_ref(p); - if !p.eat(COMMA) { - break; - } - } + 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) { @@ -2382,7 +2425,7 @@ fn with_query(p: &mut Parser<'_>) -> Option { p.expect(USING_KW); name_ref(p); } - Some(m.complete(p, WITH_TABLE)) + m.complete(p, WITH_TABLE) } const WITH_FOLLOW: TokenSet = TokenSet::new(&[ @@ -2395,9 +2438,7 @@ fn with_query_clause(p: &mut Parser<'_>) -> Option { p.expect(WITH_KW); p.eat(RECURSIVE_KW); while !p.at(EOF) { - if with_query(p).is_none() { - p.error("expected with_query"); - } + with_query(p); if p.at(COMMA) && p.nth_at_ts(1, WITH_FOLLOW) { p.err_and_bump("unexpected comma"); break; @@ -2553,13 +2594,8 @@ fn opt_locking_clause(p: &mut Parser<'_>) -> Option { } lock_strength(p); if p.eat(OF_KW) { - while !p.at(EOF) && !p.at(SEMICOLON) { - if expr(p).is_none() { - p.error("expected an expression"); - } - if !p.eat(COMMA) { - break; - } + if !expr_list(p) { + p.error("expected an expression"); } } if p.eat(SKIP_KW) { @@ -2606,23 +2642,7 @@ fn opt_order_by_clause(p: &mut Parser<'_>) -> bool { } p.expect(BY_KW); while !p.at(EOF) { - if expr(p).is_none() { - p.error("expected an expression"); - } - // ASC | DESC | USING operator - if p.at(ASC_KW) || p.at(DESC_KW) { - p.bump_any(); - } else if p.eat(USING_KW) { - operator(p); - } - // NULLS {FIRST | LAST} - if p.eat(NULLS_KW) { - if p.at(FIRST_KW) || p.at(LAST_KW) { - p.bump_any(); - } else { - p.error("expected FIRST or LAST following NULLS"); - } - } + sort_by(p); if !p.eat(COMMA) { break; } @@ -2631,6 +2651,40 @@ fn opt_order_by_clause(p: &mut Parser<'_>) -> bool { true } +fn sort_by(p: &mut Parser<'_>) { + let m = p.start(); + if expr(p).is_none() { + p.error("expected an expression"); + } + opt_sort_order(p); + opt_nulls_order(p); + m.complete(p, SORT_BY); +} + +fn opt_sort_order(p: &mut Parser<'_>) { + let m = p.start(); + let kind = match p.current() { + ASC_KW => { + p.bump(ASC_KW); + SORT_ASC + } + DESC_KW => { + p.bump(DESC_KW); + SORT_DESC + } + USING_KW => { + p.bump(USING_KW); + operator(p); + SORT_USING + } + _ => { + m.abandon(p); + return; + } + }; + m.complete(p, kind); +} + const JOIN_TYPE_FIRST: TokenSet = TokenSet::new(&[INNER_KW, JOIN_KW, LEFT_KW, RIGHT_KW, FULL_KW, CROSS_KW]); @@ -3559,10 +3613,7 @@ fn opt_constraint_inner(p: &mut Parser<'_>) -> Option { // UNIQUE [ NULLS [ NOT ] DISTINCT ] index_parameters UNIQUE_KW => { p.bump(UNIQUE_KW); - if p.eat(NULLS_KW) { - p.eat(NOT_KW); - p.expect(DISTINCT_KW); - } + opt_nulls_not_distinct(p); opt_index_parameters(p); UNIQUE_CONSTRAINT } @@ -3582,13 +3633,7 @@ fn opt_constraint_inner(p: &mut Parser<'_>) -> Option { name_ref(p); p.expect(R_PAREN); } - if p.eat(MATCH_KW) { - if p.at(FULL_KW) || p.at(PARTIAL_KW) || p.at(SIMPLE_KW) { - p.bump_any(); - } else { - p.error("expected FULL, PARTIAL, or SIMPLE"); - } - } + opt_match_type(p); foreign_key_actions(p); REFERENCES_CONSTRAINT } @@ -3599,6 +3644,34 @@ fn opt_constraint_inner(p: &mut Parser<'_>) -> Option { Some(kind) } +fn opt_match_type(p: &mut Parser<'_>) { + let m = p.start(); + if p.eat(MATCH_KW) { + let kind = match p.current() { + FULL_KW => { + p.bump(FULL_KW); + MATCH_FULL + } + + PARTIAL_KW => { + p.bump(PARTIAL_KW); + MATCH_PARTIAL + } + SIMPLE_KW => { + p.bump(SIMPLE_KW); + MATCH_SIMPLE + } + _ => { + p.error("expected FULL, PARTIAL, or SIMPLE"); + MATCH_SIMPLE + } + }; + m.complete(p, kind); + } else { + m.abandon(p); + } +} + fn opt_virtual_or_stored(p: &mut Parser<'_>) { let _ = p.eat(STORED_KW) || p.eat(VIRTUAL_KW); } @@ -3780,10 +3853,7 @@ fn table_constraint(p: &mut Parser<'_>) -> CompletedMarker { using_index(p); // [ NULLS [ NOT ] DISTINCT ] ( column_name [, ... ] ) index_parameters } else { - if p.eat(NULLS_KW) { - p.eat(NOT_KW); - p.eat(DISTINCT_KW); - } + opt_nulls_not_distinct(p); column_list(p); opt_index_parameters(p); } @@ -3856,26 +3926,8 @@ fn table_constraint(p: &mut Parser<'_>) -> CompletedMarker { column_list(p); p.expect(REFERENCES_KW); path_name_ref(p); - if p.eat(L_PAREN) { - while !p.at(EOF) && !p.at(COMMA) { - let found_period = p.eat(PERIOD_KW); - name_ref(p); - if found_period { - break; - } - if !p.eat(COMMA) { - break; - } - } - p.expect(R_PAREN); - } - if p.eat(MATCH_KW) { - if p.at(FULL_KW) || p.at(PARTIAL_KW) || p.at(SIMPLE_KW) { - p.bump_any(); - } else { - p.error("expected FULL, PARTIAL, or SIMPLE"); - } - } + opt_column_list(p); + opt_match_type(p); foreign_key_actions(p); FOREIGN_KEY_CONSTRAINT } @@ -3885,6 +3937,21 @@ fn table_constraint(p: &mut Parser<'_>) -> CompletedMarker { cm } +fn opt_nulls_not_distinct(p: &mut Parser<'_>) { + let m = p.start(); + if p.eat(NULLS_KW) { + let kind = if p.eat(NOT_KW) { + NULLS_NOT_DISTINCT + } else { + NULLS_DISTINCT + }; + p.eat(DISTINCT_KW); + m.complete(p, kind); + } else { + m.abandon(p); + } +} + fn opt_without_overlaps(p: &mut Parser<'_>) { if p.eat(WITHOUT_KW) { p.expect(OVERLAPS_KW); @@ -4598,6 +4665,8 @@ fn opt_if_exists(p: &mut Parser<'_>) -> Option { } } +const DROP_TABLE_FOLLOW: TokenSet = TokenSet::new(&[CASCADE_KW, RESTRICT_KW]); + // DROP TABLE [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ] /// fn drop_table(p: &mut Parser<'_>) -> CompletedMarker { @@ -4606,12 +4675,14 @@ fn drop_table(p: &mut Parser<'_>) -> CompletedMarker { p.bump(DROP_KW); p.bump(TABLE_KW); opt_if_exists(p); - while !p.at(EOF) { - path_name_ref(p); - if !p.eat(COMMA) { - break; - } - } + separated( + p, + COMMA, + || "unexpected comma, expected a name".to_string(), + PATH_FIRST, + DROP_TABLE_FOLLOW, + |p| opt_path_name_ref(p).is_some(), + ); opt_cascade_or_restrict(p); m.complete(p, DROP_TABLE) } @@ -4645,16 +4716,36 @@ fn partition_item(p: &mut Parser<'_>, allow_extra_params: bool) { if p.at(L_PAREN) { attribute_list(p); } - // [ ASC | DESC ] - let _ = p.eat(ASC_KW) || p.eat(DESC_KW); - // [ NULLS { FIRST | LAST } ] - if p.eat(NULLS_KW) { - let _ = p.eat(FIRST_KW) || p.expect(LAST_KW); - } + opt_sort_order(p); + opt_nulls_order(p); } m.complete(p, PARTITION_ITEM); } +// [ NULLS { FIRST | LAST } ] +fn opt_nulls_order(p: &mut Parser<'_>) { + let m = p.start(); + if p.eat(NULLS_KW) { + let kind = match p.current() { + FIRST_KW => { + p.bump(FIRST_KW); + NULLS_FIRST + } + LAST_KW => { + p.bump(LAST_KW); + NULLS_LAST + } + _ => { + p.error("expected FIRST or LAST"); + NULLS_LAST + } + }; + m.complete(p, kind); + } else { + m.abandon(p); + } +} + fn table_arg_list(p: &mut Parser<'_>, t: ColDefType) -> Option { assert!(p.at(L_PAREN)); let m = p.start(); @@ -12338,13 +12429,8 @@ fn create_index(p: &mut Parser<'_>) -> CompletedMarker { // [, ...] // ) index_params(p); - // [ INCLUDE ( column_name [, ...] ) ] opt_include_columns(p); - // [ NULLS [ NOT ] DISTINCT ] - if p.eat(NULLS_KW) { - p.eat(NOT_KW); - p.expect(DISTINCT_KW); - } + opt_nulls_not_distinct(p); opt_with_params(p); opt_tablespace(p); opt_where_clause(p); diff --git a/crates/squawk_parser/tests/data/err/drop_table.sql b/crates/squawk_parser/tests/data/err/drop_table.sql new file mode 100644 index 00000000..3d624e7d --- /dev/null +++ b/crates/squawk_parser/tests/data/err/drop_table.sql @@ -0,0 +1,5 @@ +-- missing comma +drop table foo, bar buzz cascade; + +-- missing name +drop table foo, , buzz cascade; diff --git a/crates/squawk_parser/tests/data/err/select_cte.sql b/crates/squawk_parser/tests/data/err/select_cte.sql index dcb7c7e4..1def4524 100644 --- a/crates/squawk_parser/tests/data/err/select_cte.sql +++ b/crates/squawk_parser/tests/data/err/select_cte.sql @@ -2,3 +2,13 @@ with t as ( select 1 ), -- <--- extra comma! select * from t; + +-- search depth missing item +with t as (select 1) +search depth first by a, , c set ordercol +select * from t order by ordercol; + +-- search depth missing comma +with t as (select 1) +search depth first by a, b c set ordercol +select * from t order by ordercol; diff --git a/crates/squawk_parser/tests/data/ok/drop_table.sql b/crates/squawk_parser/tests/data/ok/drop_table.sql index 052105b1..605095f0 100644 --- a/crates/squawk_parser/tests/data/ok/drop_table.sql +++ b/crates/squawk_parser/tests/data/ok/drop_table.sql @@ -18,5 +18,11 @@ drop table if exists t; -- cascade drop table foo, bar cascade; +-- cascade is the table name +drop table cascade; + +-- restrict is the table name +drop table restrict; + -- restrict drop table t restrict; 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 733b3005..990f0f75 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap @@ -2235,11 +2235,12 @@ SOURCE_FILE UNIQUE_CONSTRAINT UNIQUE_KW "unique" WHITESPACE " " - NULLS_KW "nulls" - WHITESPACE " " - NOT_KW "not" - WHITESPACE " " - DISTINCT_KW "distinct" + NULLS_NOT_DISTINCT + NULLS_KW "nulls" + WHITESPACE " " + NOT_KW "not" + WHITESPACE " " + DISTINCT_KW "distinct" WHITESPACE " " COLUMN_LIST L_PAREN "(" @@ -2580,9 +2581,10 @@ SOURCE_FILE NAME_REF IDENT "t1" WHITESPACE " " - MATCH_KW "match" - WHITESPACE " " - FULL_KW "full" + MATCH_FULL + MATCH_KW "match" + WHITESPACE " " + FULL_KW "full" SEMICOLON ";" WHITESPACE "\n" ALTER_TABLE @@ -2623,9 +2625,10 @@ SOURCE_FILE NAME_REF IDENT "t1" WHITESPACE " " - MATCH_KW "match" - WHITESPACE " " - SIMPLE_KW "simple" + MATCH_SIMPLE + MATCH_KW "match" + WHITESPACE " " + SIMPLE_KW "simple" SEMICOLON ";" WHITESPACE "\n" ALTER_TABLE @@ -2665,18 +2668,22 @@ SOURCE_FILE PATH_SEGMENT NAME_REF IDENT "t1" - L_PAREN "(" - NAME_REF - IDENT "e" - COMMA "," - WHITESPACE " " - NAME_REF - IDENT "f" - R_PAREN ")" - WHITESPACE " " - MATCH_KW "match" + COLUMN_LIST + L_PAREN "(" + COLUMN + NAME_REF + IDENT "e" + COMMA "," + WHITESPACE " " + COLUMN + NAME_REF + IDENT "f" + R_PAREN ")" WHITESPACE " " - FULL_KW "full" + MATCH_FULL + MATCH_KW "match" + WHITESPACE " " + FULL_KW "full" WHITESPACE " " ON_KW "on" WHITESPACE " " @@ -3805,10 +3812,12 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE " " - L_PAREN "(" - NAME_REF - IDENT "id" - R_PAREN ")" + COLUMN_LIST + L_PAREN "(" + COLUMN + NAME_REF + IDENT "id" + R_PAREN ")" WHITESPACE " " ON_KW "on" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_table_pg17_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_table_pg17_ok.snap index 282bf825..086c0352 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_table_pg17_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_table_pg17_ok.snap @@ -100,8 +100,9 @@ SOURCE_FILE NAME_REF IDENT "t1" WHITESPACE " " - MATCH_KW "match" - WHITESPACE " " - PARTIAL_KW "partial" + MATCH_PARTIAL + MATCH_KW "match" + WHITESPACE " " + PARTIAL_KW "partial" SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_index_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_index_ok.snap index cf4f992c..f6c86b7a 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_index_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_index_ok.snap @@ -99,11 +99,13 @@ SOURCE_FILE NAME_REF IDENT "\"fr_FR\"" WHITESPACE " " - ASC_KW "asc" + SORT_ASC + ASC_KW "asc" WHITESPACE " " - NULLS_KW "nulls" - WHITESPACE " " - LAST_KW "last" + NULLS_LAST + NULLS_KW "nulls" + WHITESPACE " " + LAST_KW "last" R_PAREN ")" SEMICOLON ";" WHITESPACE "\n" @@ -126,11 +128,13 @@ SOURCE_FILE NAME_REF IDENT "c" WHITESPACE " " - DESC_KW "desc" + SORT_DESC + DESC_KW "desc" WHITESPACE " " - NULLS_KW "nulls" - WHITESPACE " " - FIRST_KW "first" + NULLS_FIRST + NULLS_KW "nulls" + WHITESPACE " " + FIRST_KW "first" R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" @@ -279,11 +283,12 @@ SOURCE_FILE IDENT "b" R_PAREN ")" WHITESPACE "\n " - NULLS_KW "nulls" - WHITESPACE " " - NOT_KW "not" - WHITESPACE " " - DISTINCT_KW "distinct" + NULLS_NOT_DISTINCT + NULLS_KW "nulls" + WHITESPACE " " + NOT_KW "not" + WHITESPACE " " + DISTINCT_KW "distinct" WHITESPACE "\n " WITH_PARAMS WITH_KW "with" @@ -347,9 +352,10 @@ SOURCE_FILE IDENT "c" R_PAREN ")" WHITESPACE "\n " - NULLS_KW "nulls" - WHITESPACE " " - DISTINCT_KW "distinct" + NULLS_DISTINCT + NULLS_KW "nulls" + WHITESPACE " " + DISTINCT_KW "distinct" WHITESPACE "\n " WHERE_CLAUSE WHERE_KW "where" @@ -585,9 +591,10 @@ SOURCE_FILE NAME_REF IDENT "title" WHITESPACE " " - NULLS_KW "NULLS" - WHITESPACE " " - FIRST_KW "FIRST" + NULLS_FIRST + NULLS_KW "NULLS" + WHITESPACE " " + FIRST_KW "FIRST" R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" 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 a2d9b400..5392ea71 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_table_ok.snap @@ -1033,11 +1033,12 @@ SOURCE_FILE UNIQUE_CONSTRAINT UNIQUE_KW "unique" WHITESPACE " " - NULLS_KW "nulls" - WHITESPACE " " - NOT_KW "not" - WHITESPACE " " - DISTINCT_KW "distinct" + NULLS_NOT_DISTINCT + NULLS_KW "nulls" + WHITESPACE " " + NOT_KW "not" + WHITESPACE " " + DISTINCT_KW "distinct" COMMA "," WHITESPACE "\n " COLUMN @@ -1738,11 +1739,12 @@ SOURCE_FILE UNIQUE_CONSTRAINT UNIQUE_KW "unique" WHITESPACE " " - NULLS_KW "nulls" - WHITESPACE " " - NOT_KW "not" - WHITESPACE " " - DISTINCT_KW "distinct" + NULLS_NOT_DISTINCT + NULLS_KW "nulls" + WHITESPACE " " + NOT_KW "not" + WHITESPACE " " + DISTINCT_KW "distinct" WHITESPACE " " COLUMN_LIST L_PAREN "(" @@ -2299,16 +2301,19 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE " " - L_PAREN "(" - WHITESPACE " " - NAME_REF - IDENT "a" - COMMA "," - WHITESPACE " " - NAME_REF - IDENT "b" - WHITESPACE " " - R_PAREN ")" + 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 ";" @@ -2393,11 +2398,12 @@ SOURCE_FILE UNIQUE_CONSTRAINT UNIQUE_KW "unique" WHITESPACE " " - NULLS_KW "nulls" - WHITESPACE " " - NOT_KW "not" - WHITESPACE " " - DISTINCT_KW "distinct" + NULLS_NOT_DISTINCT + NULLS_KW "nulls" + WHITESPACE " " + NOT_KW "not" + WHITESPACE " " + DISTINCT_KW "distinct" WHITESPACE " " COLUMN_LIST L_PAREN "(" @@ -2533,16 +2539,19 @@ SOURCE_FILE NAME_REF IDENT "bar" WHITESPACE " " - L_PAREN "(" - WHITESPACE " " - NAME_REF - IDENT "a" - COMMA "," - WHITESPACE " " - NAME_REF - IDENT "b" - WHITESPACE " " - R_PAREN ")" + COLUMN_LIST + L_PAREN "(" + WHITESPACE " " + COLUMN + NAME_REF + IDENT "a" + COMMA "," + WHITESPACE " " + COLUMN + NAME_REF + IDENT "b" + WHITESPACE " " + R_PAREN ")" COMMA "," WHITESPACE "\n " UNIQUE_CONSTRAINT 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 407e562f..b5637de7 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 @@ -59,9 +59,10 @@ SOURCE_FILE WHITESPACE " " R_PAREN ")" WHITESPACE " " - MATCH_KW "match" - WHITESPACE " " - PARTIAL_KW "partial" + MATCH_PARTIAL + MATCH_KW "match" + WHITESPACE " " + PARTIAL_KW "partial" WHITESPACE "\n " ON_KW "on" WHITESPACE " " @@ -209,18 +210,21 @@ SOURCE_FILE NAME_REF IDENT "addresses" WHITESPACE " " - L_PAREN "(" - WHITESPACE " " - NAME_REF - IDENT "id" - COMMA "," - WHITESPACE " " - PERIOD_KW "PERIOD" - WHITESPACE " " - NAME_REF - IDENT "valid_range" - WHITESPACE " " - R_PAREN ")" + COLUMN_LIST + L_PAREN "(" + WHITESPACE " " + COLUMN + NAME_REF + IDENT "id" + COMMA "," + WHITESPACE " " + COLUMN + PERIOD_KW "PERIOD" + WHITESPACE " " + NAME_REF + IDENT "valid_range" + WHITESPACE " " + R_PAREN ")" WHITESPACE "\n" R_PAREN ")" SEMICOLON ";" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_view_extra_parens_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_view_extra_parens_ok.snap index d13ed00e..c19938ed 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_view_extra_parens_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_view_extra_parens_ok.snap @@ -68,9 +68,11 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - NAME_REF - IDENT "y" - WHITESPACE " " - DESC_KW "desc" + SORT_BY + NAME_REF + IDENT "y" + WHITESPACE " " + SORT_DESC + DESC_KW "desc" SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__delete_ok.snap b/crates/squawk_parser/tests/snapshots/tests__delete_ok.snap index 12185b66..affaa102 100644 --- a/crates/squawk_parser/tests/snapshots/tests__delete_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__delete_ok.snap @@ -996,12 +996,13 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - FIELD_EXPR - NAME_REF - IDENT "l" - DOT "." - NAME_REF - IDENT "creation_date" + SORT_BY + FIELD_EXPR + NAME_REF + IDENT "l" + DOT "." + NAME_REF + IDENT "creation_date" WHITESPACE "\n " LOCKING_CLAUSE FOR_KW "FOR" diff --git a/crates/squawk_parser/tests/snapshots/tests__drop_table_err.snap b/crates/squawk_parser/tests/snapshots/tests__drop_table_err.snap new file mode 100644 index 00000000..3c8f5618 --- /dev/null +++ b/crates/squawk_parser/tests/snapshots/tests__drop_table_err.snap @@ -0,0 +1,58 @@ +--- +source: crates/squawk_parser/tests/tests.rs +input_file: crates/squawk_parser/tests/data/err/drop_table.sql +--- +SOURCE_FILE + COMMENT "-- missing comma" + WHITESPACE "\n" + DROP_TABLE + DROP_KW "drop" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "foo" + COMMA "," + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "bar" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "buzz" + WHITESPACE " " + CASCADE_KW "cascade" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- missing name" + WHITESPACE "\n" + DROP_TABLE + DROP_KW "drop" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "foo" + COMMA "," + WHITESPACE " " + ERROR + COMMA "," + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + IDENT "buzz" + WHITESPACE " " + CASCADE_KW "cascade" + SEMICOLON ";" + WHITESPACE "\n" +--- +ERROR@36: expected COMMA +ERROR@86: unexpected comma, expected a name diff --git a/crates/squawk_parser/tests/snapshots/tests__drop_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__drop_table_ok.snap index 017916c5..76f885bc 100644 --- a/crates/squawk_parser/tests/snapshots/tests__drop_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__drop_table_ok.snap @@ -131,6 +131,32 @@ SOURCE_FILE CASCADE_KW "cascade" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- cascade is the table name" + WHITESPACE "\n" + DROP_TABLE + DROP_KW "drop" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + CASCADE_KW "cascade" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- restrict is the table name" + WHITESPACE "\n" + DROP_TABLE + DROP_KW "drop" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + PATH + PATH_SEGMENT + NAME_REF + RESTRICT_KW "restrict" + SEMICOLON ";" + WHITESPACE "\n\n" COMMENT "-- restrict" WHITESPACE "\n" DROP_TABLE diff --git a/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap b/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap index ec0e55f1..873c2c7d 100644 --- a/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__misc_ok.snap @@ -404,56 +404,58 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - CALL_EXPR - NAME_REF - IDENT "ts_rank_cd" - ARG_LIST - L_PAREN "(" - CALL_EXPR - NAME_REF - IDENT "to_tsvector" - ARG_LIST - L_PAREN "(" - LITERAL - STRING "'english'" - COMMA "," - WHITESPACE " " - BIN_EXPR - NAME_REF - IDENT "title" - WHITESPACE " " - CUSTOM_OP - PIPE "|" - PIPE "|" + SORT_BY + CALL_EXPR + NAME_REF + IDENT "ts_rank_cd" + ARG_LIST + L_PAREN "(" + CALL_EXPR + NAME_REF + IDENT "to_tsvector" + ARG_LIST + L_PAREN "(" + LITERAL + STRING "'english'" + COMMA "," WHITESPACE " " BIN_EXPR - LITERAL - STRING "' '" + NAME_REF + IDENT "title" WHITESPACE " " CUSTOM_OP PIPE "|" PIPE "|" WHITESPACE " " - CALL_EXPR - NAME_REF - COALESCE_KW "COALESCE" - ARG_LIST - L_PAREN "(" + BIN_EXPR + LITERAL + STRING "' '" + WHITESPACE " " + CUSTOM_OP + PIPE "|" + PIPE "|" + WHITESPACE " " + CALL_EXPR NAME_REF - IDENT "overview" - COMMA "," - WHITESPACE " " - LITERAL - STRING "''" - R_PAREN ")" - R_PAREN ")" - COMMA "," - WHITESPACE " " - NAME_REF - IDENT "query" - R_PAREN ")" - WHITESPACE " " - DESC_KW "DESC" + COALESCE_KW "COALESCE" + ARG_LIST + L_PAREN "(" + NAME_REF + IDENT "overview" + COMMA "," + WHITESPACE " " + LITERAL + STRING "''" + R_PAREN ")" + R_PAREN ")" + COMMA "," + WHITESPACE " " + NAME_REF + IDENT "query" + R_PAREN ")" + WHITESPACE " " + SORT_DESC + DESC_KW "DESC" WHITESPACE "\n" LIMIT_CLAUSE LIMIT_KW "LIMIT" @@ -553,25 +555,26 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - BIN_EXPR - FIELD_EXPR - NAME_REF - IDENT "movies" - DOT "." - NAME_REF - IDENT "embedding" - WHITESPACE " " - CUSTOM_OP - L_ANGLE "<" - EQ "=" - R_ANGLE ">" - WHITESPACE " " - FIELD_EXPR - NAME_REF - IDENT "query_embedding" - DOT "." - NAME_REF - IDENT "embedding" + SORT_BY + BIN_EXPR + FIELD_EXPR + NAME_REF + IDENT "movies" + DOT "." + NAME_REF + IDENT "embedding" + WHITESPACE " " + CUSTOM_OP + L_ANGLE "<" + EQ "=" + R_ANGLE ">" + WHITESPACE " " + FIELD_EXPR + NAME_REF + IDENT "query_embedding" + DOT "." + NAME_REF + IDENT "embedding" WHITESPACE "\n" LIMIT_CLAUSE LIMIT_KW "LIMIT" @@ -739,10 +742,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "relevance_score" - WHITESPACE " " - DESC_KW "DESC" + SORT_BY + NAME_REF + IDENT "relevance_score" + WHITESPACE " " + SORT_DESC + DESC_KW "DESC" WHITESPACE "\n" LIMIT_CLAUSE LIMIT_KW "LIMIT" @@ -1112,16 +1117,19 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - LITERAL - INT_NUMBER "1" + SORT_BY + LITERAL + INT_NUMBER "1" COMMA "," WHITESPACE " " - LITERAL - INT_NUMBER "2" + SORT_BY + LITERAL + INT_NUMBER "2" COMMA "," WHITESPACE " " - LITERAL - INT_NUMBER "3" + SORT_BY + LITERAL + INT_NUMBER "3" SEMICOLON ";" WHITESPACE "\n\n" CREATE_FUNCTION @@ -2090,42 +2098,43 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - BIN_EXPR - NAME_REF - IDENT "vec" - WHITESPACE " " - CUSTOM_OP - L_ANGLE "<" - MINUS "-" - R_ANGLE ">" - WHITESPACE " " - PAREN_EXPR - L_PAREN "(" - SELECT - SELECT_CLAUSE - SELECT_KW "SELECT" - WHITESPACE " " - TARGET_LIST - TARGET - CAST_EXPR - CALL_EXPR - FIELD_EXPR - NAME_REF - IDENT "openai" - DOT "." - NAME_REF - IDENT "vector" - ARG_LIST - L_PAREN "(" - LITERAL - STRING "'What is the Star Trek episode where Deanna and her mother are kidnapped?'" - R_PAREN ")" - COLON_COLON - COLON ":" - COLON ":" - NAME_REF - IDENT "vector" - R_PAREN ")" + SORT_BY + BIN_EXPR + NAME_REF + IDENT "vec" + WHITESPACE " " + CUSTOM_OP + L_ANGLE "<" + MINUS "-" + R_ANGLE ">" + WHITESPACE " " + PAREN_EXPR + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "SELECT" + WHITESPACE " " + TARGET_LIST + TARGET + CAST_EXPR + CALL_EXPR + FIELD_EXPR + NAME_REF + IDENT "openai" + DOT "." + NAME_REF + IDENT "vector" + ARG_LIST + L_PAREN "(" + LITERAL + STRING "'What is the Star Trek episode where Deanna and her mother are kidnapped?'" + R_PAREN ")" + COLON_COLON + COLON ":" + COLON ":" + NAME_REF + IDENT "vector" + R_PAREN ")" WHITESPACE "\n" LIMIT_CLAUSE LIMIT_KW "LIMIT" @@ -2535,10 +2544,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - LITERAL - INT_NUMBER "2" - WHITESPACE " " - DESC_KW "desc" + SORT_BY + LITERAL + INT_NUMBER "2" + WHITESPACE " " + SORT_DESC + DESC_KW "desc" WHITESPACE " " LIMIT_CLAUSE LIMIT_KW "limit" @@ -2793,8 +2804,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - LITERAL - INT_NUMBER "1" + SORT_BY + LITERAL + INT_NUMBER "1" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "/* define a function that wraps a COPY TO command to export data */" @@ -3085,10 +3097,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - LITERAL - INT_NUMBER "1" - WHITESPACE " " - DESC_KW "desc" + SORT_BY + LITERAL + INT_NUMBER "1" + WHITESPACE " " + SORT_DESC + DESC_KW "desc" WHITESPACE " " LIMIT_CLAUSE LIMIT_KW "limit" @@ -4666,8 +4680,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - MONTH_KW "month" + SORT_BY + NAME_REF + MONTH_KW "month" WHITESPACE " \n " ROWS_KW "ROWS" WHITESPACE " " @@ -4779,18 +4794,19 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - CALL_EXPR - NAME_REF - IDENT "date_trunc" - ARG_LIST - L_PAREN "(" - LITERAL - STRING "'month'" - COMMA "," - WHITESPACE " " + SORT_BY + CALL_EXPR NAME_REF - TIMESTAMP_KW "timestamp" - R_PAREN ")" + IDENT "date_trunc" + ARG_LIST + L_PAREN "(" + LITERAL + STRING "'month'" + COMMA "," + WHITESPACE " " + NAME_REF + TIMESTAMP_KW "timestamp" + R_PAREN ")" WHITESPACE "\n " R_PAREN ")" WHITESPACE " " @@ -5351,8 +5367,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - TIMESTAMP_KW "timestamp" + SORT_BY + NAME_REF + TIMESTAMP_KW "timestamp" WHITESPACE "\n" R_PAREN ")" WHITESPACE " " @@ -5552,14 +5569,17 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "user_id" + SORT_BY + NAME_REF + IDENT "user_id" COMMA "," WHITESPACE " " - NAME_REF - TIMESTAMP_KW "timestamp" - WHITESPACE " " - DESC_KW "DESC" + SORT_BY + NAME_REF + TIMESTAMP_KW "timestamp" + WHITESPACE " " + SORT_DESC + DESC_KW "DESC" SEMICOLON ";" WHITESPACE "\n\n" CREATE_INDEX @@ -5852,10 +5872,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE "\n " - LITERAL - INT_NUMBER "2" - WHITESPACE " " - DESC_KW "DESC" + SORT_BY + LITERAL + INT_NUMBER "2" + WHITESPACE " " + SORT_DESC + DESC_KW "DESC" SEMICOLON ";" WHITESPACE "\n\n" CREATE_INDEX @@ -6604,10 +6626,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - INTERVAL_KW "interval" - WHITESPACE " " - DESC_KW "DESC" + SORT_BY + NAME_REF + INTERVAL_KW "interval" + WHITESPACE " " + SORT_DESC + DESC_KW "DESC" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- Identify optimal trading windows" @@ -6686,8 +6710,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "price" + SORT_BY + NAME_REF + IDENT "price" R_PAREN ")" WHITESPACE " " AS_KW "AS" @@ -6820,7 +6845,8 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - HOUR_KW "hour" + SORT_BY + NAME_REF + HOUR_KW "hour" SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__precedence_ok.snap b/crates/squawk_parser/tests/snapshots/tests__precedence_ok.snap index 9c5a6a97..0219f484 100644 --- a/crates/squawk_parser/tests/snapshots/tests__precedence_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__precedence_ok.snap @@ -99,8 +99,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "baz" + SORT_BY + NAME_REF + IDENT "baz" SEMICOLON ";" WHITESPACE "\n" COMMENT "-- equal to:" @@ -134,7 +135,8 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "baz" + SORT_BY + NAME_REF + IDENT "baz" SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__select_compound_union_select_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_compound_union_select_ok.snap index f2fa7199..45b5c712 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_compound_union_select_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_compound_union_select_ok.snap @@ -63,10 +63,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "\"id\"" - WHITESPACE " " - ASC_KW "ASC" + SORT_BY + NAME_REF + IDENT "\"id\"" + WHITESPACE " " + SORT_ASC + ASC_KW "ASC" WHITESPACE " " LIMIT_CLAUSE LIMIT_KW "LIMIT" @@ -112,10 +114,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "\"id\"" - WHITESPACE " " - ASC_KW "ASC" + SORT_BY + NAME_REF + IDENT "\"id\"" + WHITESPACE " " + SORT_ASC + ASC_KW "ASC" WHITESPACE " " LIMIT_CLAUSE LIMIT_KW "LIMIT" @@ -195,10 +199,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "\"id\"" - WHITESPACE " " - ASC_KW "ASC" + SORT_BY + NAME_REF + IDENT "\"id\"" + WHITESPACE " " + SORT_ASC + ASC_KW "ASC" WHITESPACE " " LIMIT_CLAUSE LIMIT_KW "LIMIT" @@ -242,10 +248,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "\"id\"" - WHITESPACE " " - ASC_KW "ASC" + SORT_BY + NAME_REF + IDENT "\"id\"" + WHITESPACE " " + SORT_ASC + ASC_KW "ASC" WHITESPACE " " LIMIT_CLAUSE LIMIT_KW "LIMIT" diff --git a/crates/squawk_parser/tests/snapshots/tests__select_cte_err.snap b/crates/squawk_parser/tests/snapshots/tests__select_cte_err.snap index 324432cd..67f31416 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_cte_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_cte_err.snap @@ -44,6 +44,148 @@ SOURCE_FILE NAME_REF IDENT "t" SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- search depth missing item" + WHITESPACE "\n" + SELECT + WITH_CLAUSE + WITH_KW "with" + WHITESPACE " " + WITH_TABLE + NAME + IDENT "t" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE "\n" + SEARCH_KW "search" + WHITESPACE " " + DEPTH_KW "depth" + WHITESPACE " " + FIRST_KW "first" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + NAME_REF + IDENT "a" + COMMA "," + WHITESPACE " " + ERROR + COMMA "," + WHITESPACE " " + NAME_REF + IDENT "c" + WHITESPACE " " + SET_KW "set" + WHITESPACE " " + NAME_REF + IDENT "ordercol" + WHITESPACE "\n" + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + FROM_ITEM + NAME_REF + IDENT "t" + WHITESPACE " " + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY + NAME_REF + IDENT "ordercol" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- search depth missing comma" + WHITESPACE "\n" + SELECT + WITH_CLAUSE + WITH_KW "with" + WHITESPACE " " + WITH_TABLE + NAME + IDENT "t" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE "\n" + SEARCH_KW "search" + WHITESPACE " " + DEPTH_KW "depth" + WHITESPACE " " + FIRST_KW "first" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + NAME_REF + IDENT "a" + COMMA "," + WHITESPACE " " + NAME_REF + IDENT "b" + WHITESPACE " " + NAME_REF + IDENT "c" + WHITESPACE " " + SET_KW "set" + WHITESPACE " " + NAME_REF + IDENT "ordercol" + WHITESPACE "\n" + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + FROM_ITEM + NAME_REF + IDENT "t" + WHITESPACE " " + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY + NAME_REF + IDENT "ordercol" + SEMICOLON ";" WHITESPACE "\n" --- ERROR@24: unexpected comma +ERROR@140: unexpected comma, expected a column name +ERROR@270: expected COMMA 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 801f33e7..69600a9b 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_cte_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_cte_ok.snap @@ -549,8 +549,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "ordercol" + SORT_BY + NAME_REF + IDENT "ordercol" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- search breadth first (from pg docs)" @@ -740,8 +741,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "ordercol" + SORT_BY + NAME_REF + IDENT "ordercol" SEMICOLON ";" WHITESPACE "\n\n\n" COMMENT "-- search cycle (from pg docs)" @@ -1359,10 +1361,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "created_at" - WHITESPACE " " - DESC_KW "DESC" + SORT_BY + NAME_REF + IDENT "created_at" + WHITESPACE " " + SORT_DESC + DESC_KW "DESC" WHITESPACE "\n " R_PAREN ")" WHITESPACE "\n " 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 fa876662..711962ee 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_funcs_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_funcs_ok.snap @@ -3247,10 +3247,12 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - NAME_REF - IDENT "v" - WHITESPACE " " - DESC_KW "desc" + SORT_BY + NAME_REF + IDENT "v" + WHITESPACE " " + SORT_DESC + DESC_KW "desc" R_PAREN ")" WHITESPACE " " FROM_CLAUSE @@ -3286,8 +3288,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - NAME_REF - IDENT "v" + SORT_BY + NAME_REF + IDENT "v" R_PAREN ")" WHITESPACE " " FROM_CLAUSE @@ -3323,8 +3326,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - NAME_REF - IDENT "a" + SORT_BY + NAME_REF + IDENT "a" R_PAREN ")" WHITESPACE " " FROM_CLAUSE @@ -3356,12 +3360,14 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - NAME_REF - IDENT "a" + SORT_BY + NAME_REF + IDENT "a" COMMA "," WHITESPACE " " - LITERAL - STRING "','" + SORT_BY + LITERAL + STRING "','" R_PAREN ")" WHITESPACE " " FROM_CLAUSE @@ -3400,8 +3406,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - NAME_REF - IDENT "c" + SORT_BY + NAME_REF + IDENT "c" R_PAREN ")" WHITESPACE " " FROM_CLAUSE @@ -3843,8 +3850,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - NAME_REF - IDENT "a" + SORT_BY + NAME_REF + IDENT "a" R_PAREN ")" COMMA "," WHITESPACE " " @@ -3860,8 +3868,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - NAME_REF - IDENT "b" + SORT_BY + NAME_REF + IDENT "b" R_PAREN ")" SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__select_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_ok.snap index 785ad3ca..fe24df34 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_ok.snap @@ -3394,8 +3394,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - LITERAL - INT_NUMBER "1" + SORT_BY + LITERAL + INT_NUMBER "1" SEMICOLON ";" WHITESPACE "\n\n\n" COMMENT "-- nulls" @@ -3414,12 +3415,14 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - LITERAL - INT_NUMBER "1" - WHITESPACE " " - NULLS_KW "nulls" - WHITESPACE " " - FIRST_KW "first" + SORT_BY + LITERAL + INT_NUMBER "1" + WHITESPACE " " + NULLS_FIRST + NULLS_KW "nulls" + WHITESPACE " " + FIRST_KW "first" SEMICOLON ";" WHITESPACE "\n" SELECT @@ -3436,12 +3439,14 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - LITERAL - INT_NUMBER "1" - WHITESPACE " " - NULLS_KW "nulls" - WHITESPACE " " - LAST_KW "last" + SORT_BY + LITERAL + INT_NUMBER "1" + WHITESPACE " " + NULLS_LAST + NULLS_KW "nulls" + WHITESPACE " " + LAST_KW "last" SEMICOLON ";" WHITESPACE "\n\n\n" SELECT @@ -3458,16 +3463,19 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - LITERAL - INT_NUMBER "1" - WHITESPACE " " - USING_KW "using" - WHITESPACE " " - R_ANGLE ">" - WHITESPACE " " - NULLS_KW "nulls" - WHITESPACE " " - LAST_KW "last" + SORT_BY + LITERAL + INT_NUMBER "1" + WHITESPACE " " + SORT_USING + USING_KW "using" + WHITESPACE " " + R_ANGLE ">" + WHITESPACE " " + NULLS_LAST + NULLS_KW "nulls" + WHITESPACE " " + LAST_KW "last" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- select_window_clause" @@ -3521,8 +3529,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - LITERAL - INT_NUMBER "1" + SORT_BY + LITERAL + INT_NUMBER "1" R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" @@ -4157,8 +4166,9 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - LITERAL - INT_NUMBER "1" + SORT_BY + LITERAL + INT_NUMBER "1" WHITESPACE " " FETCH_CLAUSE FETCH_KW "fetch" @@ -5871,15 +5881,17 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - NAME_REF - IDENT "a" - WHITESPACE " " - USING_KW "using" - WHITESPACE " " - CUSTOM_OP - R_ANGLE ">" - R_ANGLE ">" - R_ANGLE ">" + SORT_BY + NAME_REF + IDENT "a" + WHITESPACE " " + SORT_USING + USING_KW "using" + WHITESPACE " " + CUSTOM_OP + R_ANGLE ">" + R_ANGLE ">" + R_ANGLE ">" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- order_by_regression" @@ -6013,12 +6025,14 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - NAME_REF - IDENT "sensor_id" + SORT_BY + NAME_REF + IDENT "sensor_id" COMMA "," WHITESPACE " " - NAME_REF - DAY_KW "day" + SORT_BY + NAME_REF + DAY_KW "day" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- select with uescape;" diff --git a/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap index 9dd4dda0..b23e5334 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap @@ -4138,22 +4138,23 @@ SOURCE_FILE WHITESPACE " " BY_KW "by" WHITESPACE " " - BIN_EXPR - NAME_REF - IDENT "a" - WHITESPACE " " - CUSTOM_OP - PIPE "|" - PIPE "|" - WHITESPACE " " + SORT_BY BIN_EXPR NAME_REF - IDENT "b" + IDENT "a" WHITESPACE " " - COLLATE_KW "collate" + CUSTOM_OP + PIPE "|" + PIPE "|" WHITESPACE " " - NAME_REF - IDENT "\"fr_FR\"" + BIN_EXPR + NAME_REF + IDENT "b" + WHITESPACE " " + COLLATE_KW "collate" + WHITESPACE " " + NAME_REF + IDENT "\"fr_FR\"" SEMICOLON ";" WHITESPACE "\n\n" SELECT diff --git a/crates/squawk_parser/tests/snapshots/tests__update_ok.snap b/crates/squawk_parser/tests/snapshots/tests__update_ok.snap index 6afe6ddf..7d72608e 100644 --- a/crates/squawk_parser/tests/snapshots/tests__update_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__update_ok.snap @@ -1196,12 +1196,13 @@ SOURCE_FILE WHITESPACE " " BY_KW "BY" WHITESPACE " " - FIELD_EXPR - NAME_REF - IDENT "w" - DOT "." - NAME_REF - IDENT "retry_timestamp" + SORT_BY + FIELD_EXPR + NAME_REF + IDENT "w" + DOT "." + NAME_REF + IDENT "retry_timestamp" WHITESPACE "\n " LOCKING_CLAUSE FOR_KW "FOR" diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index e8136348..d2ee3a64 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -6445,6 +6445,70 @@ impl NullConstraint { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NullsDistinct { + pub(crate) syntax: SyntaxNode, +} +impl NullsDistinct { + #[inline] + pub fn distinct_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::DISTINCT_KW) + } + #[inline] + pub fn nulls_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NULLS_KW) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NullsFirst { + pub(crate) syntax: SyntaxNode, +} +impl NullsFirst { + #[inline] + pub fn first_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::FIRST_KW) + } + #[inline] + pub fn nulls_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NULLS_KW) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NullsLast { + pub(crate) syntax: SyntaxNode, +} +impl NullsLast { + #[inline] + pub fn last_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::LAST_KW) + } + #[inline] + pub fn nulls_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NULLS_KW) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NullsNotDistinct { + pub(crate) syntax: SyntaxNode, +} +impl NullsNotDistinct { + #[inline] + pub fn distinct_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::DISTINCT_KW) + } + #[inline] + pub fn not_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NOT_KW) + } + #[inline] + pub fn nulls_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NULLS_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct OfType { pub(crate) syntax: SyntaxNode, @@ -6800,6 +6864,10 @@ pub struct OrderByClause { pub(crate) syntax: SyntaxNode, } impl OrderByClause { + #[inline] + pub fn sort_bys(&self) -> AstChildren { + support::children(&self.syntax) + } #[inline] pub fn by_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::BY_KW) @@ -8465,6 +8533,74 @@ impl SimilarTo { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SortAsc { + pub(crate) syntax: SyntaxNode, +} +impl SortAsc { + #[inline] + pub fn asc_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ASC_KW) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SortBy { + pub(crate) syntax: SyntaxNode, +} +impl SortBy { + #[inline] + pub fn expr(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn nulls_first(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn nulls_last(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn sort_asc(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn sort_desc(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn sort_using(&self) -> Option { + support::child(&self.syntax) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SortDesc { + pub(crate) syntax: SyntaxNode, +} +impl SortDesc { + #[inline] + pub fn desc_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::DESC_KW) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SortUsing { + pub(crate) syntax: SyntaxNode, +} +impl SortUsing { + #[inline] + pub fn op(&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 SourceFile { pub(crate) syntax: SyntaxNode, @@ -8806,24 +8942,20 @@ impl UniqueConstraint { support::child(&self.syntax) } #[inline] - pub fn using_index(&self) -> Option { + pub fn nulls_distinct(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn constraint_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::CONSTRAINT_KW) - } - #[inline] - pub fn distinct_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::DISTINCT_KW) + pub fn nulls_not_distinct(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn not_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::NOT_KW) + pub fn using_index(&self) -> Option { + support::child(&self.syntax) } #[inline] - pub fn nulls_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::NULLS_KW) + pub fn constraint_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::CONSTRAINT_KW) } #[inline] pub fn unique_token(&self) -> Option { @@ -15270,6 +15402,78 @@ impl AstNode for NullConstraint { &self.syntax } } +impl AstNode for NullsDistinct { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::NULLS_DISTINCT + } + #[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 NullsFirst { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::NULLS_FIRST + } + #[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 NullsLast { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::NULLS_LAST + } + #[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 NullsNotDistinct { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::NULLS_NOT_DISTINCT + } + #[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 OfType { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -17142,6 +17346,78 @@ impl AstNode for SimilarTo { &self.syntax } } +impl AstNode for SortAsc { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::SORT_ASC + } + #[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 SortBy { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::SORT_BY + } + #[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 SortDesc { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::SORT_DESC + } + #[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 SortUsing { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::SORT_USING + } + #[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 SourceFile { #[inline] fn can_cast(kind: SyntaxKind) -> bool { diff --git a/crates/squawk_syntax/src/postgresql.ungram b/crates/squawk_syntax/src/postgresql.ungram index b0690450..8b0c7258 100644 --- a/crates/squawk_syntax/src/postgresql.ungram +++ b/crates/squawk_syntax/src/postgresql.ungram @@ -303,12 +303,18 @@ Column = 'period'? (Name WithOptions? | Name Type Storage? CompressionMethod? Collate? | IndexExpr) +NullsDistinct = + 'nulls' 'distinct' + +NullsNotDistinct = + 'nulls' 'not' 'distinct' + UniqueConstraint = ('constraint' NameRef) 'unique' ( UsingIndex - | ( 'nulls' 'not'? 'distinct' )? ColumnList + | (NullsNotDistinct | NullsDistinct)? ColumnList ) PrimaryKeyConstraint = @@ -1005,8 +1011,26 @@ IntoClause = LockingClause = 'for' +NullsFirst = + 'nulls' 'first' + +NullsLast = + 'nulls' 'last' + +SortAsc = + 'asc' + +SortDesc = + 'desc' + +SortUsing = + 'using' Op + +SortBy = + Expr (SortAsc | SortDesc | SortUsing)? (NullsFirst | NullsLast)? + OrderByClause = - 'order' 'by' + 'order' 'by' (SortBy (',' SortBy)*) FromItem = 'only'? NameRef