Skip to content

Commit 6194865

Browse files
committed
Import 'Add experimental draft support for GPML-style graph query parsing'
1 parent 24ab5bd commit 6194865

File tree

9 files changed

+647
-21
lines changed

9 files changed

+647
-21
lines changed

extension/partiql-extension-visualize/src/ast_to_dot.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ impl ToDot<ast::Expr> for AstToDot {
165165
Expr::CallAgg(c) => self.to_dot(&mut expr_subgraph, c),
166166
Expr::Query(q) => self.to_dot(&mut expr_subgraph, q),
167167
Expr::Error => todo!(),
168+
Expr::GraphMatch(_) => todo!(),
168169
}
169170
}
170171
}

partiql-ast/src/ast.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use rust_decimal::Decimal as RustDecimal;
1212

1313
use std::fmt;
14+
use std::num::NonZeroU32;
1415

1516
#[cfg(feature = "serde")]
1617
use serde::{Deserialize, Serialize};
@@ -401,6 +402,8 @@ pub enum Expr {
401402
Path(AstNode<Path>),
402403
Call(AstNode<Call>),
403404
CallAgg(AstNode<CallAgg>),
405+
/// <expr> MATCH <graph_pattern>
406+
GraphMatch(AstNode<GraphMatch>),
404407

405408
/// Query, e.g. `UNION` | `EXCEPT` | `INTERSECT` | `SELECT` and their parts.
406409
Query(AstNode<Query>),
@@ -832,6 +835,164 @@ pub enum JoinSpec {
832835
Natural,
833836
}
834837

838+
/// `<expr> MATCH <graph_pattern>`
839+
#[derive(Visit, Clone, Debug, PartialEq)]
840+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
841+
pub struct GraphMatch {
842+
pub expr: Box<Expr>,
843+
pub graph_expr: Box<AstNode<GraphMatchExpr>>,
844+
}
845+
846+
/// The direction of an edge
847+
/// | Orientation | Edge pattern | Abbreviation |
848+
/// |---------------------------+--------------+--------------|
849+
/// | Pointing left | <−[ spec ]− | <− |
850+
/// | Undirected | ~[ spec ]~ | ~ |
851+
/// | Pointing right | −[ spec ]−> | −> |
852+
/// | Left or undirected | <~[ spec ]~ | <~ |
853+
/// | Undirected or right | ~[ spec ]~> | ~> |
854+
/// | Left or right | <−[ spec ]−> | <−> |
855+
/// | Left, undirected or right | −[ spec ]− | − |
856+
///
857+
/// Fig. 5. Table of edge patterns:
858+
/// https://arxiv.org/abs/2112.06217
859+
#[derive(Clone, Debug, PartialEq, Eq)]
860+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
861+
pub enum GraphMatchDirection {
862+
Left,
863+
Undirected,
864+
Right,
865+
LeftOrUndirected,
866+
UndirectedOrRight,
867+
LeftOrRight,
868+
LeftOrUndirectedOrRight,
869+
}
870+
871+
/// A part of a graph pattern
872+
#[derive(Clone, Debug, PartialEq)]
873+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
874+
pub enum GraphMatchPatternPart {
875+
/// A single node in a graph pattern.
876+
Node(AstNode<GraphMatchNode>),
877+
878+
/// A single edge in a graph pattern.
879+
Edge(AstNode<GraphMatchEdge>),
880+
881+
/// A sub-pattern.
882+
Pattern(AstNode<GraphMatchPattern>),
883+
}
884+
885+
/// A quantifier for graph edges or patterns. (e.g., the `{2,5}` in `MATCH (x)->{2,5}(y)`)
886+
#[derive(Clone, Debug, PartialEq, Eq)]
887+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
888+
pub struct GraphMatchQuantifier {
889+
pub lower: u32,
890+
pub upper: Option<NonZeroU32>,
891+
}
892+
893+
/// A path restrictor
894+
/// | Keyword | Description
895+
/// |----------------+--------------
896+
/// | TRAIL | No repeated edges.
897+
/// | ACYCLIC | No repeated nodes.
898+
/// | SIMPLE | No repeated nodes, except that the first and last nodes may be the same.
899+
///
900+
/// Fig. 7. Table of restrictors:
901+
/// https://arxiv.org/abs/2112.06217
902+
#[derive(Clone, Debug, PartialEq, Eq)]
903+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
904+
pub enum GraphMatchRestrictor {
905+
Trail,
906+
Acyclic,
907+
Simple,
908+
}
909+
910+
/// A single node in a graph pattern.
911+
#[derive(Visit, Clone, Debug, PartialEq)]
912+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
913+
pub struct GraphMatchNode {
914+
/// an optional node pre-filter, e.g.: `WHERE c.name='Alarm'` in `MATCH (c WHERE c.name='Alarm')`
915+
pub prefilter: Option<Box<Expr>>,
916+
/// the optional element variable of the node match, e.g.: `x` in `MATCH (x)`
917+
#[visit(skip)]
918+
pub variable: Option<SymbolPrimitive>,
919+
/// the optional label(s) to match for the node, e.g.: `Entity` in `MATCH (x:Entity)`
920+
#[visit(skip)]
921+
pub label: Option<Vec<SymbolPrimitive>>,
922+
}
923+
924+
/// A single edge in a graph pattern.
925+
#[derive(Visit, Clone, Debug, PartialEq)]
926+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
927+
pub struct GraphMatchEdge {
928+
/// edge direction
929+
#[visit(skip)]
930+
pub direction: GraphMatchDirection,
931+
/// an optional quantifier for the edge match
932+
#[visit(skip)]
933+
pub quantifier: Option<AstNode<GraphMatchQuantifier>>,
934+
/// an optional edge pre-filter, e.g.: `WHERE t.capacity>100` in `MATCH −[t:hasSupply WHERE t.capacity>100]−>`
935+
pub prefilter: Option<Box<Expr>>,
936+
/// the optional element variable of the edge match, e.g.: `t` in `MATCH −[t]−>`
937+
#[visit(skip)]
938+
pub variable: Option<SymbolPrimitive>,
939+
/// the optional label(s) to match for the edge. e.g.: `Target` in `MATCH −[t:Target]−>`
940+
#[visit(skip)]
941+
pub label: Option<Vec<SymbolPrimitive>>,
942+
}
943+
944+
/// A single graph match pattern.
945+
#[derive(Visit, Clone, Debug, PartialEq)]
946+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
947+
pub struct GraphMatchPattern {
948+
#[visit(skip)]
949+
pub restrictor: Option<GraphMatchRestrictor>,
950+
/// an optional quantifier for the entire pattern match
951+
#[visit(skip)]
952+
pub quantifier: Option<AstNode<GraphMatchQuantifier>>,
953+
/// an optional pattern pre-filter, e.g.: `WHERE a.name=b.name` in `MATCH [(a)->(b) WHERE a.name=b.name]`
954+
pub prefilter: Option<Box<Expr>>,
955+
/// the optional element variable of the pattern, e.g.: `p` in `MATCH p = (a) −[t]−> (b)`
956+
#[visit(skip)]
957+
pub variable: Option<SymbolPrimitive>,
958+
/// the ordered pattern parts
959+
#[visit(skip)]
960+
pub parts: Vec<GraphMatchPatternPart>,
961+
}
962+
963+
/// A path selector
964+
/// | Keyword
965+
/// |------------------
966+
/// | ANY SHORTEST
967+
/// | ALL SHORTEST
968+
/// | ANY
969+
/// | ANY k
970+
/// | SHORTEST k
971+
/// | SHORTEST k GROUP
972+
///
973+
/// Fig. 8. Table of restrictors:
974+
/// https://arxiv.org/abs/2112.06217
975+
#[derive(Clone, Debug, PartialEq, Eq)]
976+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
977+
pub enum GraphMatchSelector {
978+
AnyShortest,
979+
AllShortest,
980+
Any,
981+
AnyK(NonZeroU32),
982+
ShortestK(NonZeroU32),
983+
ShortestKGroup(NonZeroU32),
984+
}
985+
986+
/// A graph match clause as defined in GPML
987+
/// See https://arxiv.org/abs/2112.06217
988+
#[derive(Visit, Clone, Debug, PartialEq)]
989+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
990+
pub struct GraphMatchExpr {
991+
#[visit(skip)]
992+
pub selector: Option<GraphMatchSelector>,
993+
pub patterns: Vec<AstNode<GraphMatchPattern>>,
994+
}
995+
835996
/// GROUP BY <`grouping_strategy`> <`group_key`>[, <`group_key`>]... \[AS <symbol>\]
836997
#[derive(Visit, Clone, Debug, PartialEq)]
837998
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

partiql-ast/src/pretty.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,9 @@ impl PrettyDoc for Expr {
329329
Expr::Error => {
330330
unreachable!();
331331
}
332+
Expr::GraphMatch(_inner) => {
333+
todo!("inner.pretty_doc(arena)")
334+
}
332335
}
333336
.group()
334337
}

partiql-ast/src/visit.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,44 @@ pub trait Visitor<'ast> {
513513
fn exit_join_spec(&mut self, _join_spec: &'ast ast::JoinSpec) -> Traverse {
514514
Traverse::Continue
515515
}
516+
517+
fn enter_graph_match(&mut self, _graph_pattern: &'ast ast::GraphMatch) -> Traverse {
518+
Traverse::Continue
519+
}
520+
fn exit_graph_match(&mut self, _graph_pattern: &'ast ast::GraphMatch) -> Traverse {
521+
Traverse::Continue
522+
}
523+
fn enter_graph_match_expr(&mut self, _graph_pattern: &'ast ast::GraphMatchExpr) -> Traverse {
524+
Traverse::Continue
525+
}
526+
fn exit_graph_match_expr(&mut self, _graph_pattern: &'ast ast::GraphMatchExpr) -> Traverse {
527+
Traverse::Continue
528+
}
529+
fn enter_graph_match_pattern(
530+
&mut self,
531+
_graph_pattern: &'ast ast::GraphMatchPattern,
532+
) -> Traverse {
533+
Traverse::Continue
534+
}
535+
fn exit_graph_match_pattern(
536+
&mut self,
537+
_graph_pattern: &'ast ast::GraphMatchPattern,
538+
) -> Traverse {
539+
Traverse::Continue
540+
}
541+
fn enter_graph_match_node(&mut self, _graph_pattern: &'ast ast::GraphMatchNode) -> Traverse {
542+
Traverse::Continue
543+
}
544+
fn exit_graph_match_node(&mut self, _graph_pattern: &'ast ast::GraphMatchNode) -> Traverse {
545+
Traverse::Continue
546+
}
547+
fn enter_graph_match_edge(&mut self, _graph_pattern: &'ast ast::GraphMatchEdge) -> Traverse {
548+
Traverse::Continue
549+
}
550+
fn exit_graph_match_edge(&mut self, _graph_pattern: &'ast ast::GraphMatchEdge) -> Traverse {
551+
Traverse::Continue
552+
}
553+
516554
fn enter_group_by_expr(&mut self, _group_by_expr: &'ast ast::GroupByExpr) -> Traverse {
517555
Traverse::Continue
518556
}

partiql-parser/benches/bench_parse.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ const Q_COMPLEX_FEXPR: &str = r#"
3434
AS deltas FROM SOURCE_VIEW_DELTA_FULL_TRANSACTIONS delta_full_transactions
3535
"#;
3636

37+
const Q_COMPLEX_MATCH: &str = r#"
38+
SELECT (
39+
SELECT numRec, data
40+
FROM
41+
(deltaGraph MATCH (t) -[:hasChange]-> (dt), (dt) -[:checkPointedBy]-> (t1)),
42+
(
43+
SELECT foo(u.id), bar(review), rindex
44+
FROM delta.data as u CROSS JOIN UNPIVOT u.reviews as review AT rindex
45+
) as data,
46+
delta.numRec as numRec
47+
)
48+
AS deltas FROM SOURCE_VIEW_DELTA_FULL_TRANSACTIONS delta_full_transactions
49+
"#;
50+
3751
fn parse_bench(c: &mut Criterion) {
3852
fn parse(text: &str) -> ParserResult {
3953
Parser::default().parse(text)
@@ -45,6 +59,9 @@ fn parse_bench(c: &mut Criterion) {
4559
c.bench_function("parse-complex-fexpr", |b| {
4660
b.iter(|| parse(black_box(Q_COMPLEX_FEXPR)))
4761
});
62+
c.bench_function("parse-complex-match", |b| {
63+
b.iter(|| parse(black_box(Q_COMPLEX_MATCH)))
64+
});
4865
}
4966

5067
criterion_group! {

partiql-parser/src/lexer/mod.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ mod tests {
8989
"WiTH Where Value uSiNg Unpivot UNION True Select right Preserve pivoT Outer Order Or \
9090
On Offset Nulls Null Not Natural Missing Limit Like Left Lateral Last Join \
9191
Intersect Is Inner In Having Group From For Full First False Except Escape Desc \
92-
Cross Table Time Timestamp Date By Between At As And Asc All Values Case When Then Else End";
92+
Cross Table Time Timestamp Date By Between At As And Asc All Values Case When Then Else End \
93+
Match Any Shortest Trail Acyclic Simple";
9394
let symbols = symbols.split(' ').chain(primitives.split(' '));
9495
let keywords = keywords.split(' ');
9596

@@ -111,7 +112,7 @@ mod tests {
111112
"<unquoted_atident:UNQUOTED_ATIDENT>", "GROUP", "<quoted_atident:QUOTED_ATIDENT>",
112113
"FROM", "FOR", "FULL", "FIRST", "FALSE", "EXCEPT", "ESCAPE", "DESC", "CROSS", "TABLE",
113114
"TIME", "TIMESTAMP", "DATE", "BY", "BETWEEN", "AT", "AS", "AND", "ASC", "ALL", "VALUES",
114-
"CASE", "WHEN", "THEN", "ELSE", "END"
115+
"CASE", "WHEN", "THEN", "ELSE", "END", "MATCH", "ANY", "SHORTEST", "TRAIL", "ACYCLIC", "SIMPLE"
115116
];
116117
let displayed = toks
117118
.into_iter()
@@ -408,25 +409,18 @@ mod tests {
408409
/// the following test will need to be modified.
409410
#[test]
410411
fn select_non_reserved_keywords() -> Result<(), ParseError<'static, BytePosition>> {
411-
let query =
412-
"SELECT acyclic, BoTh, DOMAIN, SiMpLe, Trail, leading, TRailing, USER\nfrom @\"foo\"";
412+
let query = "SELECT BoTh, DOMAIN, leading, TRailing, USER\nfrom @\"foo\"";
413413
let mut offset_tracker = LineOffsetTracker::default();
414414
let lexer = PartiqlLexer::new(query, &mut offset_tracker);
415415
let toks: Vec<_> = lexer.collect::<Result<_, _>>()?;
416416

417417
assert_eq!(
418418
vec![
419419
Token::Select,
420-
Token::UnquotedIdent("acyclic"),
421-
Token::Comma,
422420
Token::UnquotedIdent("BoTh"),
423421
Token::Comma,
424422
Token::UnquotedIdent("DOMAIN"),
425423
Token::Comma,
426-
Token::UnquotedIdent("SiMpLe"),
427-
Token::Comma,
428-
Token::UnquotedIdent("Trail"),
429-
Token::Comma,
430424
Token::UnquotedIdent("leading"),
431425
Token::Comma,
432426
Token::UnquotedIdent("TRailing"),

0 commit comments

Comments
 (0)