diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index a16cbbff..f749e7fb 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -123,7 +123,7 @@ fn opt_paren_select(p: &mut Parser<'_>, m: Option) -> Option) -> CompletedMarker { @@ -2697,6 +2697,12 @@ fn compound_select(p: &mut Parser<'_>, cm: CompletedMarker) -> CompletedMarker { m.complete(p, COMPOUND_SELECT) } +fn opt_select_clause(p: &mut Parser<'_>) { + if p.at(SELECT_KW) { + select_clause(p); + } +} + // error recovery: // - /// @@ -2724,7 +2730,11 @@ fn select(p: &mut Parser, m: Option, r: &SelectRestrictions) -> Option Option { (ROLLBACK_KW, _) => Some(rollback(p)), (SAVEPOINT_KW, _) => Some(savepoint(p)), (SECURITY_KW, LABEL_KW) => Some(security_label(p)), - (SELECT_KW | TABLE_KW | VALUES_KW, _) => select(p, None, &SelectRestrictions::default()), + (SELECT_KW | TABLE_KW | VALUES_KW | FROM_KW, _) => { + select(p, None, &SelectRestrictions::default()) + } (SET_KW, CONSTRAINTS_KW) => Some(set_constraints(p)), (SET_KW, LOCAL_KW) => match p.nth(2) { ROLE_KW => Some(set_role(p)), @@ -12622,7 +12634,9 @@ fn with(p: &mut Parser<'_>, m: Option) -> Option { with_query_clause(p); match p.current() { DELETE_KW => Some(delete(p, Some(m))), - SELECT_KW | TABLE_KW | VALUES_KW => select(p, Some(m), &SelectRestrictions::default()), + SELECT_KW | TABLE_KW | VALUES_KW | FROM_KW => { + select(p, Some(m), &SelectRestrictions::default()) + } INSERT_KW => Some(insert(p, Some(m))), UPDATE_KW => Some(update(p, Some(m))), MERGE_KW => Some(merge(p, Some(m))), diff --git a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__select_validation.snap b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__select_validation.snap new file mode 100644 index 00000000..c05c620f --- /dev/null +++ b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__select_validation.snap @@ -0,0 +1,80 @@ +--- +source: crates/squawk_syntax/src/test.rs +input_file: crates/squawk_syntax/test_data/validation/select.sql +--- +SOURCE_FILE@0..64 + WHITESPACE@0..1 "\n" + SELECT@1..16 + FROM_CLAUSE@1..7 + FROM_KW@1..5 "from" + WHITESPACE@5..6 " " + FROM_ITEM@6..7 + NAME_REF@6..7 + IDENT@6..7 "t" + WHITESPACE@7..8 " " + SELECT_CLAUSE@8..16 + SELECT_KW@8..14 "select" + WHITESPACE@14..15 " " + TARGET_LIST@15..16 + TARGET@15..16 + NAME_REF@15..16 + IDENT@15..16 "c" + SEMICOLON@16..17 ";" + WHITESPACE@17..19 "\n\n" + SELECT@19..25 + FROM_CLAUSE@19..25 + FROM_KW@19..23 "from" + WHITESPACE@23..24 " " + FROM_ITEM@24..25 + NAME_REF@24..25 + IDENT@24..25 "t" + SEMICOLON@25..26 ";" + WHITESPACE@26..28 "\n\n" + SELECT@28..62 + WITH_CLAUSE@28..46 + WITH_KW@28..32 "with" + WHITESPACE@32..33 " " + WITH_TABLE@33..46 + NAME@33..34 + IDENT@33..34 "t" + WHITESPACE@34..35 " " + AS_KW@35..37 "as" + WHITESPACE@37..38 " " + L_PAREN@38..39 "(" + SELECT@39..45 + FROM_CLAUSE@39..45 + FROM_KW@39..43 "from" + WHITESPACE@43..44 " " + FROM_ITEM@44..45 + NAME_REF@44..45 + IDENT@44..45 "k" + R_PAREN@45..46 ")" + WHITESPACE@46..47 "\n" + SELECT_CLAUSE@47..55 + SELECT_KW@47..53 "select" + WHITESPACE@53..54 " " + TARGET_LIST@54..55 + TARGET@54..55 + STAR@54..55 "*" + WHITESPACE@55..56 " " + FROM_CLAUSE@56..62 + FROM_KW@56..60 "from" + WHITESPACE@60..61 " " + FROM_ITEM@61..62 + NAME_REF@61..62 + IDENT@61..62 "t" + SEMICOLON@62..63 ";" + WHITESPACE@63..64 "\n" + +error[syntax-error]: Leading from clauses are not supported in Postgres + ╭▸ +2 │ from t select c; + ╰╴━━━━━━ +error[syntax-error]: Missing select clause + ╭▸ +4 │ from t; + ╰╴━ +error[syntax-error]: Missing select clause + ╭▸ +6 │ with t as (from k) + ╰╴ ━ diff --git a/crates/squawk_syntax/src/validation.rs b/crates/squawk_syntax/src/validation.rs index c535999b..d4cc6f1f 100644 --- a/crates/squawk_syntax/src/validation.rs +++ b/crates/squawk_syntax/src/validation.rs @@ -21,12 +21,34 @@ pub(crate) fn validate(root: &SyntaxNode, errors: &mut Vec) { ast::JoinExpr(it) => validate_join_expr(it, errors), ast::Literal(it) => validate_literal(it, errors), ast::NonStandardParam(it) => validate_non_standard_param(it, errors), + ast::Select(it) => validate_select(it, errors), _ => (), } } } } +fn validate_select(it: ast::Select, acc: &mut Vec) { + let Some(from_clause) = it.from_clause() else { + return; + }; + if let Some(select_clause) = it.select_clause() { + if from_clause.syntax().text_range().end() < select_clause.syntax().text_range().start() { + // Postgres dialect doesn't support leading from clauses, e.g., `from t select c` + acc.push(SyntaxError::new( + "Leading from clauses are not supported in Postgres", + from_clause.syntax().text_range(), + )); + } + } else { + // Postgres dialect doesn't support missing select clauses, e.g., `from t` + acc.push(SyntaxError::new( + "Missing select clause", + TextRange::empty(from_clause.syntax().text_range().start()), + )); + } +} + fn validate_create_table(it: ast::CreateTable, acc: &mut Vec) { let Some(arg_list) = it.table_arg_list() else { return; diff --git a/crates/squawk_syntax/test_data/validation/select.sql b/crates/squawk_syntax/test_data/validation/select.sql new file mode 100644 index 00000000..dc6f52fe --- /dev/null +++ b/crates/squawk_syntax/test_data/validation/select.sql @@ -0,0 +1,7 @@ + +from t select c; + +from t; + +with t as (from k) +select * from t;