Skip to content

Commit 05e7289

Browse files
authored
parser: fix select precedence for trailing clauses (#544)
These now parse the same precedence wise: ```sql SELECT foo UNION SELECT bar ORDER BY baz; -- equal to: (SELECT foo UNION SELECT bar) ORDER BY baz; ```
1 parent 274ba82 commit 05e7289

File tree

4 files changed

+85
-51
lines changed

4 files changed

+85
-51
lines changed

crates/squawk_parser/src/grammar.rs

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ fn array_expr(p: &mut Parser<'_>, m: Option<Marker>) -> CompletedMarker {
5555
R_BRACK
5656
};
5757
while !p.at(EOF) && !p.at(closing) {
58-
if p.at_ts(SELECT_FIRST) && (select(p, None).is_none() || p.at(EOF) || p.at(closing)) {
58+
if p.at_ts(SELECT_FIRST)
59+
&& (select(p, None, &SelectRestrictions::default()).is_none()
60+
|| p.at(EOF)
61+
|| p.at(closing))
62+
{
5963
break;
6064
}
6165
if expr(p).is_none() {
@@ -73,6 +77,18 @@ fn array_expr(p: &mut Parser<'_>, m: Option<Marker>) -> CompletedMarker {
7377
m.complete(p, ARRAY_EXPR)
7478
}
7579

80+
struct SelectRestrictions {
81+
trailing_clauses: bool,
82+
}
83+
84+
impl Default for SelectRestrictions {
85+
fn default() -> Self {
86+
Self {
87+
trailing_clauses: true,
88+
}
89+
}
90+
}
91+
7692
fn opt_paren_select(p: &mut Parser<'_>) -> Option<CompletedMarker> {
7793
let m = p.start();
7894
if !p.eat(L_PAREN) {
@@ -83,7 +99,11 @@ fn opt_paren_select(p: &mut Parser<'_>) -> Option<CompletedMarker> {
8399
// saw_expr = true;
84100
// we want to check for select stuff before we get the the expr stuff maybe? Although select is an expr so maybe fine? but it's not prefix or postfix so maybe right here is good?
85101
//
86-
if p.at_ts(SELECT_FIRST) && (select(p, None).is_none() || p.at(EOF) || p.at(R_PAREN)) {
102+
if p.at_ts(SELECT_FIRST)
103+
&& (select(p, None, &SelectRestrictions::default()).is_none()
104+
|| p.at(EOF)
105+
|| p.at(R_PAREN))
106+
{
87107
break;
88108
}
89109
if opt_paren_select(p).is_none() {
@@ -94,14 +114,13 @@ fn opt_paren_select(p: &mut Parser<'_>) -> Option<CompletedMarker> {
94114
}
95115
}
96116
p.expect(R_PAREN);
97-
let cm = m.complete(p, PAREN_SELECT);
98-
let cm = if p.at_ts(COMPOUND_SELECT_FIRST) {
99-
compound_select(p, cm)
117+
if p.at_ts(COMPOUND_SELECT_FIRST) {
118+
let cm = m.complete(p, PAREN_SELECT);
119+
Some(compound_select(p, cm))
100120
} else {
101-
cm
102-
};
103-
select_trailing_clauses(p);
104-
Some(cm)
121+
select_trailing_clauses(p);
122+
Some(m.complete(p, PAREN_SELECT))
123+
}
105124
}
106125

107126
const SELECT_FIRST: TokenSet = TokenSet::new(&[SELECT_KW, TABLE_KW, WITH_KW, VALUES_KW]);
@@ -121,7 +140,11 @@ fn tuple_expr(p: &mut Parser<'_>) -> CompletedMarker {
121140
saw_expr = true;
122141
// we want to check for select stuff before we get the the expr stuff maybe? Although select is an expr so maybe fine? but it's not prefix or postfix so maybe right here is good?
123142
//
124-
if p.at_ts(SELECT_FIRST) && (select(p, None).is_none() || p.at(EOF) || p.at(R_PAREN)) {
143+
if p.at_ts(SELECT_FIRST)
144+
&& (select(p, None, &SelectRestrictions::default()).is_none()
145+
|| p.at(EOF)
146+
|| p.at(R_PAREN))
147+
{
125148
break;
126149
}
127150
if expr(p).is_none() {
@@ -691,7 +714,10 @@ fn json_array_fn_arg_list(p: &mut Parser<'_>) {
691714
// 1, 2, 3, 4
692715
while !p.at(EOF) && !p.at(R_PAREN) && !p.at(RETURNING_KW) {
693716
if p.at_ts(SELECT_FIRST) {
694-
if select(p, None).is_none() || p.at(EOF) || p.at(R_PAREN) {
717+
if select(p, None, &SelectRestrictions::default()).is_none()
718+
|| p.at(EOF)
719+
|| p.at(R_PAREN)
720+
{
695721
break;
696722
}
697723
opt_json_format_clause(p);
@@ -732,7 +758,7 @@ fn some_any_all_fn(p: &mut Parser<'_>) -> CompletedMarker {
732758
// args
733759
p.expect(L_PAREN);
734760
if p.at_ts(SELECT_FIRST) {
735-
select(p, None);
761+
select(p, None, &SelectRestrictions::default());
736762
} else {
737763
if expr(p).is_none() {
738764
p.error("expected expression or select");
@@ -826,7 +852,7 @@ fn exists_fn(p: &mut Parser<'_>) -> CompletedMarker {
826852
assert!(p.at(EXISTS_KW));
827853
custom_fn(p, EXISTS_KW, |p| {
828854
if p.at_ts(SELECT_FIRST) {
829-
select(p, None);
855+
select(p, None, &SelectRestrictions::default());
830856
} else {
831857
p.error("expected select")
832858
}
@@ -2457,18 +2483,25 @@ fn compound_select(p: &mut Parser<'_>, cm: CompletedMarker) -> CompletedMarker {
24572483
opt_paren_select(p);
24582484
} else {
24592485
if p.at_ts(SELECT_FIRST) {
2460-
select(p, None);
2486+
select(
2487+
p,
2488+
None,
2489+
&SelectRestrictions {
2490+
trailing_clauses: false,
2491+
},
2492+
);
24612493
} else {
24622494
p.error("expected start of a select statement")
24632495
}
24642496
}
2497+
select_trailing_clauses(p);
24652498
m.complete(p, COMPOUND_SELECT)
24662499
}
24672500

24682501
// error recovery:
24692502
// - <https://youtu.be/0HlrqwLjCxA?feature=shared&t=2172>
24702503
/// <https://www.postgresql.org/docs/17/sql-select.html>
2471-
fn select(p: &mut Parser, m: Option<Marker>) -> Option<CompletedMarker> {
2504+
fn select(p: &mut Parser, m: Option<Marker>, r: &SelectRestrictions) -> Option<CompletedMarker> {
24722505
assert!(p.at_ts(SELECT_FIRST));
24732506
let m = m.unwrap_or_else(|| p.start());
24742507

@@ -2506,7 +2539,9 @@ fn select(p: &mut Parser, m: Option<Marker>) -> Option<CompletedMarker> {
25062539
let cm = m.complete(p, SELECT);
25072540
return Some(compound_select(p, cm));
25082541
}
2509-
select_trailing_clauses(p);
2542+
if r.trailing_clauses {
2543+
select_trailing_clauses(p);
2544+
}
25102545
Some(m.complete(p, out_kind))
25112546
}
25122547

@@ -5339,7 +5374,7 @@ fn stmt(p: &mut Parser, r: &StmtRestrictions) -> Option<CompletedMarker> {
53395374
(ROLLBACK_KW, _) => Some(rollback(p)),
53405375
(SAVEPOINT_KW, _) => Some(savepoint(p)),
53415376
(SECURITY_KW, LABEL_KW) => Some(security_label(p)),
5342-
(SELECT_KW | TABLE_KW | VALUES_KW, _) => select(p, None),
5377+
(SELECT_KW | TABLE_KW | VALUES_KW, _) => select(p, None, &SelectRestrictions::default()),
53435378
(SET_KW, CONSTRAINTS_KW) => Some(set_constraints(p)),
53445379
(SET_KW, LOCAL_KW) => match p.nth(2) {
53455380
ROLE_KW => Some(set_role(p)),
@@ -12006,7 +12041,9 @@ fn create_schema(p: &mut Parser<'_>) -> CompletedMarker {
1200612041

1200712042
fn query(p: &mut Parser<'_>) {
1200812043
// TODO: this needs to be more general
12009-
if (!p.at_ts(SELECT_FIRST) || select(p, None).is_none()) && opt_paren_select(p).is_none() {
12044+
if (!p.at_ts(SELECT_FIRST) || select(p, None, &SelectRestrictions::default()).is_none())
12045+
&& opt_paren_select(p).is_none()
12046+
{
1201012047
p.error("expected select stmt")
1201112048
}
1201212049
}
@@ -12127,7 +12164,7 @@ fn set_clause(p: &mut Parser<'_>) {
1212712164
if p.eat(L_PAREN) {
1212812165
// ( sub-SELECT )
1212912166
if p.at(SELECT_KW) && !found_row {
12130-
if select(p, None).is_none() {
12167+
if select(p, None, &SelectRestrictions::default()).is_none() {
1213112168
p.error("expected sub-SELECT");
1213212169
}
1213312170
} else {
@@ -12222,7 +12259,7 @@ fn with(p: &mut Parser<'_>, m: Option<Marker>) -> Option<CompletedMarker> {
1222212259
with_query_clause(p);
1222312260
match p.current() {
1222412261
DELETE_KW => Some(delete(p, Some(m))),
12225-
SELECT_KW | TABLE_KW | VALUES_KW => select(p, Some(m)),
12262+
SELECT_KW | TABLE_KW | VALUES_KW => select(p, Some(m), &SelectRestrictions::default()),
1222612263
INSERT_KW => Some(insert(p, Some(m))),
1222712264
UPDATE_KW => Some(update(p, Some(m))),
1222812265
MERGE_KW => Some(merge(p, Some(m))),

crates/squawk_parser/tests/data/ok/precedence.sql

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ SELECT (((SELECT 2)) + 3);
33
SELECT (((SELECT 2)) UNION SELECT 2);
44

55

6-
-- TODO!
76
SELECT foo UNION SELECT bar ORDER BY baz;
87
-- equal to:
98
(SELECT foo UNION SELECT bar) ORDER BY baz;

crates/squawk_parser/tests/snapshots/tests__precedence_ok.snap

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,6 @@ SOURCE_FILE
7373
R_PAREN ")"
7474
SEMICOLON ";"
7575
WHITESPACE "\n\n\n"
76-
COMMENT "-- TODO!"
77-
WHITESPACE "\n"
7876
COMPOUND_SELECT
7977
SELECT
8078
SELECT_CLAUSE
@@ -95,14 +93,14 @@ SOURCE_FILE
9593
TARGET
9694
NAME_REF
9795
IDENT "bar"
96+
WHITESPACE " "
97+
ORDER_BY_CLAUSE
98+
ORDER_KW "ORDER"
9899
WHITESPACE " "
99-
ORDER_BY_CLAUSE
100-
ORDER_KW "ORDER"
101-
WHITESPACE " "
102-
BY_KW "BY"
103-
WHITESPACE " "
104-
NAME_REF
105-
IDENT "baz"
100+
BY_KW "BY"
101+
WHITESPACE " "
102+
NAME_REF
103+
IDENT "baz"
106104
SEMICOLON ";"
107105
WHITESPACE "\n"
108106
COMMENT "-- equal to:"
@@ -130,13 +128,13 @@ SOURCE_FILE
130128
NAME_REF
131129
IDENT "bar"
132130
R_PAREN ")"
133-
WHITESPACE " "
134-
ORDER_BY_CLAUSE
135-
ORDER_KW "ORDER"
136-
WHITESPACE " "
137-
BY_KW "BY"
138131
WHITESPACE " "
139-
NAME_REF
140-
IDENT "baz"
132+
ORDER_BY_CLAUSE
133+
ORDER_KW "ORDER"
134+
WHITESPACE " "
135+
BY_KW "BY"
136+
WHITESPACE " "
137+
NAME_REF
138+
IDENT "baz"
141139
SEMICOLON ";"
142140
WHITESPACE "\n"

crates/squawk_parser/tests/snapshots/tests__select_compound_union_select_ok.snap

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -236,22 +236,22 @@ SOURCE_FILE
236236
WHITESPACE " "
237237
LITERAL
238238
STRING "'nl-NL'"
239+
WHITESPACE " "
240+
ORDER_BY_CLAUSE
241+
ORDER_KW "ORDER"
239242
WHITESPACE " "
240-
ORDER_BY_CLAUSE
241-
ORDER_KW "ORDER"
242-
WHITESPACE " "
243-
BY_KW "BY"
244-
WHITESPACE " "
245-
NAME_REF
246-
IDENT "\"id\""
247-
WHITESPACE " "
248-
ASC_KW "ASC"
243+
BY_KW "BY"
249244
WHITESPACE " "
250-
LIMIT_CLAUSE
251-
LIMIT_KW "LIMIT"
252-
WHITESPACE " "
253-
LITERAL
254-
INT_NUMBER "1"
245+
NAME_REF
246+
IDENT "\"id\""
247+
WHITESPACE " "
248+
ASC_KW "ASC"
249+
WHITESPACE " "
250+
LIMIT_CLAUSE
251+
LIMIT_KW "LIMIT"
252+
WHITESPACE " "
253+
LITERAL
254+
INT_NUMBER "1"
255255
WHITESPACE "\n"
256256
R_PAREN ")"
257257
WHITESPACE " "

0 commit comments

Comments
 (0)