Skip to content

Commit 5629e88

Browse files
jpschorralancai98
andauthored
Modify parser&AST to allow orderby/limit/offset on children of setop (#401)
* Modify parser&AST to allow orderby/limit/offset on children of setop * Add docs, update changelog, additional parse tests, minor refactor --------- Co-authored-by: Alan Cai <[email protected]>
1 parent bd7716d commit 5629e88

File tree

8 files changed

+174
-48
lines changed

8 files changed

+174
-48
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Changed
1010
- *BREAKING:* partiql-logical-planner: moves `NameResolver` to `partiql-ast-passes`
1111
- *BREAKING:* partiql-values: removes `partiql` from value macro_rules; e.g. `partiql_bag` renames to `bag`.
12+
- *BREAKING:* partiql-ast: changed modeling of `Query` and `SetExpr` nodes to support `ORDER BY`, `LIMIT`, `OFFSET` in children of set operators
13+
- Also affects the `Visitor` trait
1214

1315
### Added
1416
- Add ability for partiql-extension-ion extension encoding/decoding of `Value` to/from Ion `Element`
1517
- Add `partiql-types` crate that includes data models for PartiQL Types.
1618
- Add `partiql_ast_passes::static_typer` for type annotating the AST.
19+
- Add ability to parse `ORDER BY`, `LIMIT`, `OFFSET` in children of set operators
1720

1821
### Fixes
1922

partiql-ast-passes/src/partiql_typer.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ mod tests {
221221
use super::*;
222222
use assert_matches::assert_matches;
223223
use partiql_ast::ast;
224-
use partiql_catalog::{PartiqlCatalog, TypeEnvEntry};
225-
use partiql_types::{PartiqlType, StructConstraint, StructField, TypeKind};
224+
use partiql_catalog::PartiqlCatalog;
225+
use partiql_types::{PartiqlType, TypeKind};
226226

227227
#[test]
228228
fn simple_test() {
@@ -263,7 +263,7 @@ mod tests {
263263

264264
let typer = AstPartiqlTyper::new(catalog);
265265
if let ast::Expr::Query(q) = parsed.ast.as_ref() {
266-
typer.type_nodes(&q)
266+
typer.type_nodes(q)
267267
} else {
268268
panic!("Typing statement other than `Query` are unsupported")
269269
}

partiql-ast/src/ast.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,14 @@ pub enum ConflictAction {
236236

237237
#[derive(Visit, Clone, Debug, PartialEq)]
238238
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
239-
pub struct Query {
239+
pub struct TopLevelQuery {
240240
pub with: Option<AstNode<WithClause>>,
241+
pub query: AstNode<Query>,
242+
}
243+
244+
#[derive(Visit, Clone, Debug, PartialEq)]
245+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
246+
pub struct Query {
241247
pub set: AstNode<QuerySet>,
242248
pub order_by: Option<Box<AstNode<OrderByExpr>>>,
243249
pub limit_offset: Option<Box<AstNode<LimitOffsetClause>>>,
@@ -278,8 +284,8 @@ pub struct SetExpr {
278284
pub setop: SetOperator,
279285
#[visit(skip)]
280286
pub setq: SetQuantifier,
281-
pub lhs: Box<AstNode<QuerySet>>,
282-
pub rhs: Box<AstNode<QuerySet>>,
287+
pub lhs: Box<AstNode<Query>>,
288+
pub rhs: Box<AstNode<Query>>,
283289
}
284290

285291
#[derive(Clone, Debug, PartialEq, Eq)]

partiql-ast/src/visit.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,12 @@ pub trait Visitor<'ast> {
207207
fn exit_on_conflict(&mut self, _on_conflict: &'ast ast::OnConflict) -> Traverse {
208208
Traverse::Continue
209209
}
210+
fn enter_top_level_query(&mut self, _query: &'ast ast::TopLevelQuery) -> Traverse {
211+
Traverse::Continue
212+
}
213+
fn exit_top_level_query(&mut self, _query: &'ast ast::TopLevelQuery) -> Traverse {
214+
Traverse::Continue
215+
}
210216
fn enter_query(&mut self, _query: &'ast ast::Query) -> Traverse {
211217
Traverse::Continue
212218
}

partiql-ast/tests/test_ast.rs

Lines changed: 0 additions & 21 deletions
This file was deleted.

partiql-parser/src/parse/mod.rs

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ mod grammar {
3333

3434
type LalrpopError<'input> =
3535
lpop::ParseError<ByteOffset, lexer::Token<'input>, ParseError<'input, BytePosition>>;
36-
type LalrpopResult<'input> = Result<Box<ast::Expr>, LalrpopError<'input>>;
36+
type LalrpopResult<'input> = Result<ast::AstNode<ast::TopLevelQuery>, LalrpopError<'input>>;
3737
type LalrpopErrorRecovery<'input> =
3838
lpop::ErrorRecovery<ByteOffset, lexer::Token<'input>, ParseError<'input, BytePosition>>;
3939

@@ -65,7 +65,7 @@ fn parse_partiql_with_state<'input, Id: IdGenerator>(
6565
let lexer = PreprocessingPartiqlLexer::new(s, &mut offsets, &BUILT_INS);
6666
let lexer = CommentSkippingLexer::new(lexer);
6767

68-
let result: LalrpopResult = grammar::QueryParser::new().parse(s, &mut state, lexer);
68+
let result: LalrpopResult = grammar::TopLevelQueryParser::new().parse(s, &mut state, lexer);
6969

7070
let ParserState {
7171
locations, errors, ..
@@ -87,11 +87,14 @@ fn parse_partiql_with_state<'input, Id: IdGenerator>(
8787
errors.push(ParseError::from(e));
8888
Err(ErrorData { errors, offsets })
8989
}
90-
(Ok(ast), true) => Ok(AstData {
91-
ast,
92-
locations,
93-
offsets,
94-
}),
90+
(Ok(ast), true) => {
91+
let ast = Box::new(ast::Expr::Query(ast.node.query));
92+
Ok(AstData {
93+
ast,
94+
locations,
95+
offsets,
96+
})
97+
}
9598
}
9699
}
97100

@@ -606,6 +609,38 @@ mod tests {
606609
assert_ne!(l, r2);
607610
assert_ne!(r, r2);
608611
}
612+
613+
#[test]
614+
fn complex_set() {
615+
parse_null_id!(
616+
r#"(SELECT a1 FROM b1 ORDER BY c1 LIMIT d1 OFFSET e1)
617+
OUTER UNION ALL
618+
(SELECT a2 FROM b2 ORDER BY c2 LIMIT d2 OFFSET e2)
619+
ORDER BY c3 LIMIT d3 OFFSET e3"#
620+
);
621+
parse_null_id!(
622+
r#"(SELECT a1 FROM b1 ORDER BY c1 LIMIT d1 OFFSET e1)
623+
OUTER INTERSECT ALL
624+
(SELECT a2 FROM b2 ORDER BY c2 LIMIT d2 OFFSET e2)
625+
ORDER BY c3 LIMIT d3 OFFSET e3"#
626+
);
627+
parse_null_id!(
628+
r#"(SELECT a1 FROM b1 ORDER BY c1 LIMIT d1 OFFSET e1)
629+
OUTER EXCEPT ALL
630+
(SELECT a2 FROM b2 ORDER BY c2 LIMIT d2 OFFSET e2)
631+
ORDER BY c3 LIMIT d3 OFFSET e3"#
632+
);
633+
parse_null_id!(
634+
r#"(
635+
(SELECT a1 FROM b1 ORDER BY c1 LIMIT d1 OFFSET e1)
636+
UNION DISTINCT
637+
(SELECT a2 FROM b2 ORDER BY c2 LIMIT d2 OFFSET e2)
638+
)
639+
OUTER UNION ALL
640+
(SELECT a3 FROM b3 ORDER BY c3 LIMIT d3 OFFSET e3)
641+
ORDER BY c4 LIMIT d4 OFFSET e4"#
642+
);
643+
}
609644
}
610645

611646
mod case_expr {

partiql-parser/src/parse/parse_util.rs

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use partiql_ast::ast;
22

3+
use crate::parse::parser_state::{IdGenerator, ParserState};
34
use bitflags::bitflags;
5+
use partiql_source_map::location::ByteOffset;
46

57
bitflags! {
68
/// Set of AST node attributes to use as synthesized attributes.
@@ -59,13 +61,98 @@ pub(crate) enum CallSite {
5961
}
6062

6163
#[inline]
62-
// if this is just a parenthesized expr, lift it out of the query AST, otherwise return input
63-
// e.g. `(1+2)` should be a ExprKind::Expr, not wrapped deep in a ExprKind::Query
64-
pub(crate) fn strip_query(q: Box<ast::Expr>) -> Box<ast::Expr> {
65-
if let ast::Expr::Query(ast::AstNode {
64+
// Removes extra `Query` nesting if it exists, otherwise return the input.
65+
// e.g. `(SELECT a FROM b ORDER BY c LIMIT d OFFSET e)` should be a Query with no additional nesting.
66+
// Put another way: if `q` is a Query(QuerySet::Expr(Query(inner_q), ...), return Query(inner_q).
67+
// Otherwise, return `q`.
68+
pub(crate) fn strip_query(q: ast::AstNode<ast::Query>) -> ast::AstNode<ast::Query> {
69+
let outer_id = q.id;
70+
if let ast::AstNode {
71+
node: ast::QuerySet::Expr(e),
72+
id: inner_id,
73+
} = q.node.set
74+
{
75+
if let ast::Expr::Query(
76+
inner_q @ ast::AstNode {
77+
node: ast::Query { .. },
78+
..
79+
},
80+
) = *e
81+
{
82+
inner_q
83+
} else {
84+
let set = ast::AstNode {
85+
id: inner_id,
86+
node: ast::QuerySet::Expr(e),
87+
};
88+
ast::AstNode {
89+
id: outer_id,
90+
node: ast::Query {
91+
set,
92+
order_by: None,
93+
limit_offset: None,
94+
},
95+
}
96+
}
97+
} else {
98+
q
99+
}
100+
}
101+
102+
#[inline]
103+
// If `qs` is a `QuerySet::Expr(Expr::Query(inner_q))`, return Query(inner_q). Otherwise, return `qs` wrapped
104+
// in a `Query` with `None` as the `OrderBy` and `LimitOffset`
105+
pub(crate) fn strip_query_set<Id>(
106+
qs: ast::AstNode<ast::QuerySet>,
107+
state: &mut ParserState<Id>,
108+
lo: ByteOffset,
109+
hi: ByteOffset,
110+
) -> ast::AstNode<ast::Query>
111+
where
112+
Id: IdGenerator,
113+
{
114+
if let ast::AstNode {
115+
node: ast::QuerySet::Expr(q),
116+
id: inner_id,
117+
} = qs
118+
{
119+
if let ast::Expr::Query(
120+
inner_q @ ast::AstNode {
121+
node: ast::Query { .. },
122+
..
123+
},
124+
) = *q
125+
{
126+
// preserve query including limit/offset & order by if present
127+
inner_q
128+
} else {
129+
let query = ast::Query {
130+
set: ast::AstNode {
131+
id: inner_id,
132+
node: ast::QuerySet::Expr(q),
133+
},
134+
order_by: None,
135+
limit_offset: None,
136+
};
137+
state.node(query, lo..hi)
138+
}
139+
} else {
140+
let query = ast::Query {
141+
set: qs,
142+
order_by: None,
143+
limit_offset: None,
144+
};
145+
state.node(query, lo..hi)
146+
}
147+
}
148+
149+
#[inline]
150+
// If this is just a parenthesized expr, lift it out of the query AST, otherwise return input
151+
// e.g. `(1+2)` should be an `Expr`, not wrapped deep in a `Query`
152+
pub(crate) fn strip_expr(q: ast::AstNode<ast::Query>) -> Box<ast::Expr> {
153+
if let ast::AstNode {
66154
node:
67155
ast::Query {
68-
with: None,
69156
set:
70157
ast::AstNode {
71158
node: ast::QuerySet::Expr(e),
@@ -75,10 +162,10 @@ pub(crate) fn strip_query(q: Box<ast::Expr>) -> Box<ast::Expr> {
75162
limit_offset: None,
76163
},
77164
..
78-
}) = *q
165+
} = q
79166
{
80167
e
81168
} else {
82-
q
169+
Box::new(ast::Expr::Query(q))
83170
}
84171
}

partiql-parser/src/parse/partiql.lalrpop

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,28 @@ use partiql_ast::ast;
99

1010
use partiql_source_map::location::{ByteOffset, BytePosition, Location, ToLocated};
1111

12-
use crate::parse::parse_util::{strip_query, CallSite, Attrs, Synth};
12+
use crate::parse::parse_util::{strip_expr, strip_query, strip_query_set, CallSite, Attrs, Synth};
1313
use crate::parse::parser_state::{ParserState, IdGenerator};
1414

1515
grammar<'input, 'state, Id>(input: &'input str, state: &'state mut ParserState<'input, Id>) where Id: IdGenerator;
1616

1717

18-
pub(crate) Query: Box<ast::Expr> = {
18+
pub(crate) TopLevelQuery: ast::AstNode<ast::TopLevelQuery> = {
1919
<lo:@L>
2020
<with:WithClause?>
21+
<query:Query>
22+
<hi:@R> => {
23+
state.node(ast::TopLevelQuery { with, query }, lo..hi)
24+
}
25+
}
26+
27+
Query: ast::AstNode<ast::Query> = {
28+
<lo:@L>
2129
<set:QuerySet>
2230
<order_by:OrderByClause?>
2331
<limit_offset:LimitOffsetClause>
2432
<hi:@R> => {
25-
Box::new(ast::Expr::Query( state.node(ast::Query { with, set, order_by, limit_offset }, lo..hi) ) )
33+
state.node(ast::Query { set, order_by, limit_offset }, lo..hi)
2634
}
2735
}
2836

@@ -94,7 +102,9 @@ WithCycleClause : () = {
94102
// - all set operations are left-associative and are thus expressed as left-self-recursive rules
95103

96104
QuerySet: ast::AstNode<ast::QuerySet> = {
97-
<lo:@L> <lhs:QuerySet> <setop:SetOp> <setq:SetQuantifier> <rhs:SingleQuery> <hi:@R> => {
105+
<lo:@L> <lhs:Query> <setop:SetOp> <setq:SetQuantifier> <rhs:SingleQuery> <hi:@R> => {
106+
let lhs = strip_query(lhs);
107+
let rhs = strip_query_set(rhs, state, lo, hi);
98108
let set_expr = state.node(ast::SetExpr {
99109
setop,
100110
setq,
@@ -133,7 +143,7 @@ SetQuantifier: ast::SetQuantifier = {
133143
SingleQuery: ast::AstNode<ast::QuerySet> = {
134144
<lo:@L> <expr:ExprQuery> <hi:@R> => {
135145
match *expr {
136-
ast::Expr::Query(ast::AstNode{ node: ast::Query{with: None, set, order_by:None, limit_offset:None} , .. }) => set,
146+
ast::Expr::Query(ast::AstNode{ node: ast::Query{set, order_by:None, limit_offset:None} , .. }) => set,
137147
_ => state.node(ast::QuerySet::Expr( expr ), lo..hi),
138148
}
139149
},
@@ -907,7 +917,7 @@ ExprTerm: Synth<ast::Expr> = {
907917
}
908918

909919
SubQuery: ast::Expr = {
910-
"(" <q:Query> ")" => *strip_query(q),
920+
"(" <q:Query> ")" => *strip_expr(q),
911921
}
912922

913923
SubQueryAst: ast::AstNode<ast::Expr> = {
@@ -1050,7 +1060,7 @@ FunctionCallArgs: Vec<ast::AstNode<ast::CallArg>> = {
10501060
// Special case subquery when it is the only sub-expression of a function call (e.g., `SELECT AVG(SELECT VALUE price FROM g AS v))... `)
10511061
<lo:@L> <subq:SfwQuery> <hi:@R> => {
10521062
let qset = state.node(ast::QuerySet::Select(Box::new(subq)), lo..hi);
1053-
let query = state.node(ast::Query{ with: None, set: qset, order_by: None, limit_offset:None }, lo..hi);
1063+
let query = state.node(ast::Query{ set: qset, order_by: None, limit_offset:None }, lo..hi);
10541064
vec![state.node(ast::CallArg::Positional(Box::new(ast::Expr::Query(query))), lo..hi)]
10551065
},
10561066
}

0 commit comments

Comments
 (0)