Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
dist

.DS_Store
temp.sql
5 changes: 5 additions & 0 deletions crates/squawk_parser/src/generated/syntax_kind.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

220 changes: 111 additions & 109 deletions crates/squawk_parser/src/grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,20 +584,7 @@ fn json_table_arg_list(p: &mut Parser<'_>) {
name(p);
}
// [ PASSING { value AS varname } [, ...] ]
if p.eat(PASSING_KW) {
while !p.at(EOF) {
// value
if expr(p).is_none() {
p.error("expected expression");
}
opt_json_format_clause(p);
p.expect(AS_KW);
col_label(p);
if !p.eat(COMMA) {
break;
}
}
}
opt_json_passing_clause(p);
// COLUMNS ( json_table_column [, ...] )
if p.eat(COLUMNS_KW) {
p.expect(L_PAREN);
Expand All @@ -609,12 +596,7 @@ fn json_table_arg_list(p: &mut Parser<'_>) {
}
p.expect(R_PAREN);
}
// [ { ERROR | EMPTY [ARRAY]} ON ERROR ]
if p.eat(ERROR_KW) {
p.expect(ON_KW);
p.expect(ERROR_KW);
} else if p.eat(EMPTY_KW) {
p.eat(ARRAY_KW);
if opt_json_behavior(p) {
p.expect(ON_KW);
p.expect(ERROR_KW);
}
Expand All @@ -633,6 +615,7 @@ fn json_table_arg_list(p: &mut Parser<'_>) {
// [ { ERROR | TRUE | FALSE | UNKNOWN } ON ERROR ]
// | NESTED [ PATH ] path_expression [ AS json_path_name ] COLUMNS ( json_table_column [, ...] )
fn json_table_column(p: &mut Parser<'_>) {
let m = p.start();
// NESTED [ PATH ] path_expression [ AS json_path_name ] COLUMNS ( json_table_column [, ...] )
if p.eat(NESTED_KW) {
p.eat(PATH_KW);
Expand Down Expand Up @@ -669,60 +652,22 @@ fn json_table_column(p: &mut Parser<'_>) {
p.error("expected expression");
}
}
// [ { ERROR | TRUE | FALSE | UNKNOWN } ON ERROR ]
if p.at(ERROR_KW) || p.at(TRUE_KW) || p.at(FALSE_KW) || p.at(UNKNOWN_KW) {
p.expect(ON_KW);
p.expect(ERROR_KW);
}
opt_json_behavior_clause(p);
} else {
// [ FORMAT JSON [ENCODING UTF8]]
opt_json_format_clause(p);
// [ PATH path_expression ]
if p.eat(PATH_KW) {
// path_expression
if expr(p).is_none() {
p.error("expected expression");
}
}
// [ { WITHOUT | WITH { CONDITIONAL | [UNCONDITIONAL] } } [ ARRAY ] WRAPPER ]
if p.at(WITHOUT_KW) || p.at(WITH_KW) {
if p.eat(WITH_KW) {
let _ = p.eat(CONDITIONAL_KW) || p.eat(UNCONDITIONAL_KW);
} else {
p.bump(WITHOUT_KW);
}
p.eat(ARRAY_KW);
p.expect(WRAPPER_KW);
}
// [ { KEEP | OMIT } QUOTES [ ON SCALAR STRING ] ]
if p.eat(KEEP_KW) || p.eat(OMIT_KW) {
p.expect(QUOTES_KW);
if p.eat(ON_KW) {
p.expect(SCALAR_KW);
p.expect(STRING_KW);
}
}
// [ { ERROR | NULL | EMPTY { [ARRAY] | OBJECT } | DEFAULT expression } ON EMPTY ]
// [ { ERROR | NULL | EMPTY { [ARRAY] | OBJECT } | DEFAULT expression } ON ERROR ]
if p.at(ERROR_KW) || p.at(NULL_KW) || p.at(EMPTY_KW) || p.at(DEFAULT_KW) {
// EMPTY { [ARRAY] | OBJECT }
if p.eat(EMPTY_KW) {
let _ = p.eat(ARRAY_KW) || p.expect(OBJECT_KW);
// DEFAULT
} else if p.eat(DEFAULT_KW) {
if expr(p).is_none() {
p.error("expected an expression");
}
// ERROR | NULL
} else {
p.bump_any();
}
p.expect(ON_KW);
let _ = p.eat(EMPTY_KW) || p.expect(ERROR_KW);
string_literal(p);
}
opt_json_wrapper_behavior(p);
opt_json_quotes_clause(p);
opt_json_behavior_clause(p);
}
}
}
m.complete(p, JSON_TABLE_COLUMN);
}

// json_array (
Expand Down Expand Up @@ -2508,11 +2453,9 @@ fn compound_select(p: &mut Parser<'_>, cm: CompletedMarker) -> CompletedMarker {
fn select(p: &mut Parser, m: Option<Marker>) -> Option<CompletedMarker> {
assert!(p.at_ts(SELECT_FIRST));
let m = m.unwrap_or_else(|| p.start());
// table [only] name [*]
if p.eat(TABLE_KW) {
relation_name(p);
return Some(m.complete(p, TABLE));
}

let mut out_kind = SELECT;

// with aka cte
// [ WITH [ RECURSIVE ] with_query [, ...] ]
if p.at(WITH_KW) {
Expand All @@ -2526,8 +2469,16 @@ fn select(p: &mut Parser, m: Option<Marker>) -> Option<CompletedMarker> {
return Some(cm);
}
}
select_clause(p);
let is_select_into = opt_into_clause(p).is_some();
// table [only] name [*]
if p.eat(TABLE_KW) {
relation_name(p);
out_kind = TABLE;
} else {
select_clause(p);
}
if opt_into_clause(p).is_some() {
out_kind = SELECT_INTO;
}
opt_from_clause(p);
opt_where_clause(p);
opt_group_by_clause(p);
Expand All @@ -2554,7 +2505,7 @@ fn select(p: &mut Parser, m: Option<Marker>) -> Option<CompletedMarker> {
opt_locking_clause(p);
}
}
Some(m.complete(p, if is_select_into { SELECT_INTO } else { SELECT }))
Some(m.complete(p, out_kind))
}

// INTO [ TEMPORARY | TEMP | UNLOGGED ] [ TABLE ] new_table
Expand Down Expand Up @@ -2918,8 +2869,11 @@ fn data_source(p: &mut Parser<'_>) {
p.expect(FROM_KW);
p.expect(L_PAREN);
call_expr(p);
// TODO: we should restrict this more
opt_alias(p);
while !p.at(EOF) && p.eat(COMMA) {
call_expr(p);
opt_alias(p);
}
p.expect(R_PAREN);
// [ WITH ORDINALITY ]
if p.eat(WITH_KW) {
Expand Down Expand Up @@ -4153,27 +4107,59 @@ fn opt_group_by_clause(p: &mut Parser<'_>) -> Option<CompletedMarker> {
if p.at(ALL_KW) || p.at(DISTINCT_KW) {
p.bump_any();
}
group_by_list(p);

Some(m.complete(p, GROUP_BY_CLAUSE))
}

fn group_by_list(p: &mut Parser<'_>) {
// From pg docs:
// An expression used inside a grouping_element can be an input column name,
// or the name or ordinal number of an output column (SELECT list item), or
// an arbitrary expression formed from input-column values. In case of
// 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) {
// TODO: handle errors?
p.eat(ROLLUP_KW);
p.eat(CUBE_KW);
if p.eat(GROUPING_KW) {
p.expect(SETS_KW);
}
if expr(p).is_none() {
p.error("expected an expression");
}
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);
if !p.eat(COMMA) {
break;
}
}
Some(m.complete(p, GROUP_BY_CLAUSE))
}

/// <https://www.postgresql.org/docs/17/sql-select.html#SQL-HAVING>
Expand All @@ -4196,22 +4182,20 @@ fn opt_having_clause(p: &mut Parser<'_>) -> Option<CompletedMarker> {
// offset PRECEDING
// offset FOLLOWING
fn frame_start_end(p: &mut Parser<'_>) {
if p.eat(UNBOUNDED_KW) {
if p.at(PRECEDING_KW) || p.at(FOLLOWING_KW) {
match (p.current(), p.nth(1)) {
(CURRENT_KW, ROW_KW) | (UNBOUNDED_KW, PRECEDING_KW | FOLLOWING_KW) => {
p.bump_any();
} else {
p.err_and_bump("expected preceding or following");
}
} else if p.eat(CURRENT_KW) {
p.expect(ROW_KW);
} else {
if expr(p).is_none() {
p.error("expected an expression");
}
if p.at(PRECEDING_KW) || p.at(FOLLOWING_KW) {
p.bump_any();
} else {
p.err_and_bump("expected preceding or following");
}
_ => {
if expr(p).is_none() {
p.error("expected an expression");
}
if p.at(PRECEDING_KW) || p.at(FOLLOWING_KW) {
p.bump_any();
} else {
p.err_and_bump("expected preceding or following");
}
}
}
}
Expand Down Expand Up @@ -4290,12 +4274,19 @@ fn opt_window_clause(p: &mut Parser<'_>) -> Option<CompletedMarker> {
}
let m = p.start();
p.bump(WINDOW_KW);
p.expect(IDENT);
window_def(p);
while !p.at(EOF) && p.eat(COMMA) {
window_def(p);
}
Some(m.complete(p, WINDOW_CLAUSE))
}

fn window_def(p: &mut Parser<'_>) {
name(p);
p.expect(AS_KW);
p.expect(L_PAREN);
window_definition(p);
p.expect(R_PAREN);
Some(m.complete(p, WINDOW_CLAUSE))
}

// [ LIMIT { count | ALL } ]
Expand Down Expand Up @@ -4848,13 +4839,20 @@ fn create_table(p: &mut Parser<'_>) -> CompletedMarker {
// AS query
// [ WITH [ NO ] DATA ]
if p.eat(AS_KW) {
// query
if p.at_ts(SELECT_FIRST) {
select(p, None);
} else if p.at(EXECUTE_KW) {
execute(p);
} else {
p.error("expected SELECT, TABLE, VALUES, or EXECUTE");
match stmt(
p,
&StmtRestrictions {
begin_end_allowed: false,
},
)
.map(|x| x.kind())
{
Some(
SELECT | COMPOUND_SELECT | SELECT_INTO | PAREN_SELECT | TABLE | VALUES | EXECUTE,
) => (),
_ => {
p.error("expected SELECT, TABLE, VALUES, or EXECUTE");
}
}
if p.eat(WITH_KW) {
p.eat(NO_KW);
Expand Down Expand Up @@ -5272,7 +5270,7 @@ fn stmt(p: &mut Parser, r: &StmtRestrictions) -> Option<CompletedMarker> {
(GRANT_KW, _) => Some(grant(p)),
(IMPORT_KW, FOREIGN_KW) => Some(import_foreign_schema(p)),
(INSERT_KW, _) => Some(insert(p, None)),
(L_PAREN, L_PAREN | SELECT_KW) => {
(L_PAREN, _) if p.nth_at_ts(1, SELECT_FIRST) || p.at(L_PAREN) => {
// can have select nested in parens, i.e., ((select 1));
let cm = paren_select(p)?;
let cm = if p.at_ts(COMPOUND_SELECT_FIRST) {
Expand Down Expand Up @@ -6913,11 +6911,15 @@ fn alter_default_privileges(p: &mut Parser<'_>) -> CompletedMarker {

fn privilege_target_type(p: &mut Parser<'_>) {
match p.current() {
LARGE_KW => {
p.bump(LARGE_KW);
p.expect(OBJECTS_KW);
}
TABLES_KW | FUNCTIONS_KW | ROUTINES_KW | SEQUENCES_KW | TYPES_KW | SCHEMAS_KW => {
p.bump_any();
}
_ => p.error(
"expected privilege target, TABLES, FUNCTIONS, ROUTINES, SEQEUNCES, TYPES, SCHEMAS",
"expected privilege target, TABLES, FUNCTIONS, ROUTINES, SEQEUNCES, TYPES, SCHEMAS, LARGE OBJECTS",
),
}
}
Expand Down Expand Up @@ -10387,7 +10389,7 @@ fn grant_role_option_list(p: &mut Parser<'_>) {
p.error("expected OPTION, TRUE, or FALSE")
}
if !p.eat(COMMA) {
if p.at_ts(COL_LABEL_FIRST) {
if p.at_ts(COL_LABEL_FIRST) && !p.at(GRANTED_KW) {
p.error("missing comma");
} else {
break;
Expand Down
4 changes: 4 additions & 0 deletions crates/squawk_parser/tests/data/ok/select_funcs.sql
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,7 @@ select max(a) over (partition by b) as c from t;

-- window name
select max(a) over w_name from t;

-- window clause
select * from t window owner as (order by a), w2 as (order by b);
-- ^^^^^ make sure we allow using keywords
Loading
Loading