Skip to content

Commit da9f6fc

Browse files
committed
GRAPH_TABLE updates to logical plan lowering
1 parent 40b934d commit da9f6fc

14 files changed

+581
-97
lines changed

partiql-logical-planner/src/graph.rs

Lines changed: 107 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use num::Integer;
22
use partiql_ast::ast;
3-
use partiql_ast::ast::{GraphMatchDirection, GraphPathPatternPart};
3+
use partiql_ast::ast::{GraphMatchDirection, GraphMatchLabel, GraphMatchPathPattern};
44
use partiql_logical::graph::bind_name::FreshBinder;
55
use partiql_logical::graph::{
66
BindSpec, DirectionFilter, EdgeFilter, EdgeMatch, LabelFilter, NodeFilter, NodeMatch,
77
PathMatch, PathPattern, PathPatternMatch, StepFilter, TripleFilter, ValueFilter,
88
};
9+
use petgraph::visit::Walker;
910
use std::mem::take;
1011

1112
#[macro_export]
@@ -165,94 +166,156 @@ impl GraphToLogical {
165166
}
166167
pub(crate) fn plan_graph_match(
167168
&self,
168-
match_expr: &ast::GraphMatchExpr,
169+
graph_match: &ast::GraphMatch,
169170
) -> Result<PathPatternMatch, String> {
170-
if match_expr.selector.is_some() {
171-
not_yet_implemented_result!("MATCH expression selectors are not yet supported.");
171+
if graph_match.shape.cols.is_some() {
172+
not_yet_implemented_result!("MATCH expression COLUMNS are not yet supported.");
173+
}
174+
if graph_match.shape.export.is_some() {
175+
not_yet_implemented_result!("MATCH expression EXPORT are not yet supported.");
176+
}
177+
if graph_match.shape.rows.is_some() {
178+
not_yet_implemented_result!("MATCH expression ROWS are not yet supported.");
179+
}
180+
181+
let pattern = self.plan_graph_pattern(&graph_match.pattern)?;
182+
let normalized = self.normalize(pattern)?;
183+
let expanded = self.expand(normalized)?;
184+
self.plan(expanded)
185+
}
186+
187+
fn plan_graph_pattern(&self, pattern: &ast::GraphPattern) -> Result<Vec<MatchElement>, String> {
188+
if pattern.mode.is_some() {
189+
not_yet_implemented_result!("MATCH expression MATCH MODE is not yet supported.");
190+
}
191+
if pattern.keep.is_some() {
192+
not_yet_implemented_result!("MATCH expression KEEP is not yet supported.");
193+
}
194+
if pattern.where_clause.is_some() {
195+
not_yet_implemented_result!("MATCH expression WHERE is not yet supported.");
172196
}
173197

174-
if match_expr.patterns.len() != 1 {
198+
if pattern.patterns.len() != 1 {
175199
not_yet_implemented_result!(
176200
"MATCH expression with multiple patterns are not yet supported."
177201
);
178202
}
179203

180-
let first_pattern = &match_expr.patterns[0].node;
181-
let pattern = self.plan_graph_pattern(first_pattern)?;
182-
let normalized = self.normalize(pattern)?;
183-
let expanded = self.expand(normalized)?;
184-
self.plan(expanded)
204+
let first_pattern = &pattern.patterns[0];
205+
self.plan_graph_path_pattern(first_pattern)
185206
}
186207

187-
fn plan_graph_pattern(
208+
fn plan_graph_path_pattern(
188209
&self,
189210
pattern: &ast::GraphPathPattern,
190211
) -> Result<Vec<MatchElement>, String> {
191-
if pattern.restrictor.is_some() {
192-
not_yet_implemented_result!("MATCH pattern restrictors are not yet supported.");
212+
if pattern.prefix.is_some() {
213+
not_yet_implemented_result!("MATCH pattern SEARCH/MODE prefix are not yet supported.");
193214
}
194-
if pattern.quantifier.is_some() {
195-
not_yet_implemented_result!("MATCH pattern quantifiers are not yet supported.");
215+
if pattern.variable.is_some() {
216+
not_yet_implemented_result!("MATCH pattern path variables are not yet supported.");
196217
}
197-
if pattern.prefilter.is_some() {
198-
not_yet_implemented_result!("MATCH pattern prefilters are not yet supported.");
218+
219+
self.plan_graph_match_path_pattern(&pattern.path)
220+
}
221+
222+
fn plan_graph_subpath_pattern(
223+
&self,
224+
pattern: &ast::GraphPathSubPattern,
225+
) -> Result<Vec<MatchElement>, String> {
226+
if pattern.mode.is_some() {
227+
not_yet_implemented_result!("MATCH pattern MODE prefix are not yet supported.");
199228
}
200229
if pattern.variable.is_some() {
201230
not_yet_implemented_result!("MATCH pattern path variables are not yet supported.");
202231
}
232+
if pattern.where_clause.is_some() {
233+
not_yet_implemented_result!("MATCH expression WHERE is not yet supported.");
234+
}
203235

204-
let parts = pattern
205-
.parts
206-
.iter()
207-
.map(|p| self.plan_graph_pattern_part(p));
208-
let result: Result<Vec<_>, _> = parts.collect();
209-
210-
result.map(|r| r.into_iter().flatten().collect())
236+
self.plan_graph_match_path_pattern(&pattern.path)
211237
}
212238

213-
fn plan_graph_pattern_part(
239+
fn plan_graph_match_path_pattern(
214240
&self,
215-
part: &ast::GraphPathPatternPart,
241+
pattern: &ast::GraphMatchPathPattern,
216242
) -> Result<Vec<MatchElement>, String> {
217-
match part {
218-
GraphPathPatternPart::Node(n) => self.plan_graph_pattern_part_node(&n.node),
219-
GraphPathPatternPart::Edge(e) => self.plan_graph_pattern_part_edge(&e.node),
220-
GraphPathPatternPart::Pattern(pattern) => self.plan_graph_pattern(&pattern.node),
243+
match pattern {
244+
GraphMatchPathPattern::Path(path) => {
245+
let path: Result<Vec<Vec<_>>, _> = path
246+
.iter()
247+
.map(|elt| self.plan_graph_match_path_pattern(elt))
248+
.collect();
249+
Ok(path?.into_iter().flatten().collect())
250+
}
251+
GraphMatchPathPattern::Union(_) => {
252+
not_yet_implemented_result!("MATCH expression UNION is not yet supported.")
253+
}
254+
GraphMatchPathPattern::Multiset(_) => {
255+
not_yet_implemented_result!("MATCH expression MULTISET is not yet supported.")
256+
}
257+
GraphMatchPathPattern::Questioned(_) => {
258+
not_yet_implemented_result!("MATCH expression QUESTIONED is not yet supported.")
259+
}
260+
GraphMatchPathPattern::Quantified(_, _) => {
261+
not_yet_implemented_result!("MATCH expression QUANTIFIED is not yet supported.")
262+
}
263+
GraphMatchPathPattern::Sub(subpath) => self.plan_graph_subpath_pattern(subpath),
264+
GraphMatchPathPattern::Node(n) => self.plan_graph_pattern_part_node(&n),
265+
GraphMatchPathPattern::Edge(e) => self.plan_graph_pattern_part_edge(&e),
266+
GraphMatchPathPattern::Simplified(_) => {
267+
not_yet_implemented_result!(
268+
"MATCH expression Simplified Edge Expressions are not yet supported."
269+
)
270+
}
221271
}
222272
}
223273

224274
fn plan_graph_pattern_label(
225275
&self,
226-
label: &Option<Vec<ast::SymbolPrimitive>>,
276+
label: Option<&GraphMatchLabel>,
227277
) -> Result<LabelFilter, String> {
228-
match label {
229-
None => Ok(LabelFilter::Always),
230-
Some(labels) => {
231-
if labels.len() != 1 {
278+
if let Some(label) = label {
279+
match label {
280+
GraphMatchLabel::Name(n) => Ok(LabelFilter::Named(n.value.clone())),
281+
GraphMatchLabel::Wildcard => Ok(LabelFilter::Always),
282+
GraphMatchLabel::Negated(_) => {
283+
not_yet_implemented_result!(
284+
"MATCH expression label negation is not yet supported."
285+
);
286+
}
287+
GraphMatchLabel::Conjunction(_) => {
232288
not_yet_implemented_result!(
233-
"MATCH expression with multiple patterns are not yet supported."
289+
"MATCH expression label conjunction is not yet supported."
290+
);
291+
}
292+
GraphMatchLabel::Disjunction(_) => {
293+
not_yet_implemented_result!(
294+
"MATCH expression label disjunction is not yet supported."
234295
);
235296
}
236-
Ok(LabelFilter::Named(labels[0].value.clone()))
237297
}
298+
} else {
299+
Ok(LabelFilter::Always)
238300
}
239301
}
240302

241303
fn plan_graph_pattern_part_node(
242304
&self,
243305
node: &ast::GraphMatchNode,
244306
) -> Result<Vec<MatchElement>, String> {
245-
if node.prefilter.is_some() {
246-
not_yet_implemented_result!("MATCH node prefilters are not yet supported.");
307+
if node.where_clause.is_some() {
308+
not_yet_implemented_result!("MATCH node where_clauses are not yet supported.");
247309
}
248310
let binder = match &node.variable {
249311
None => self.graph_id.node(),
250312
Some(v) => v.value.clone(),
251313
};
314+
let label = self.plan_graph_pattern_label(node.label.as_deref())?;
252315
let node_match = NodeMatch {
253316
binder: BindSpec(binder),
254317
spec: NodeFilter {
255-
label: self.plan_graph_pattern_label(&node.label)?,
318+
label,
256319
filter: ValueFilter::Always,
257320
},
258321
};
@@ -266,8 +329,8 @@ impl GraphToLogical {
266329
if edge.quantifier.is_some() {
267330
not_yet_implemented_result!("MATCH edge quantifiers are not yet supported.");
268331
}
269-
if edge.prefilter.is_some() {
270-
not_yet_implemented_result!("MATCH edge prefilters are not yet supported.");
332+
if edge.where_clause.is_some() {
333+
not_yet_implemented_result!("MATCH edge where_clauses are not yet supported.");
271334
}
272335
let direction = match &edge.direction {
273336
GraphMatchDirection::Left => DirectionFilter::L,
@@ -282,10 +345,11 @@ impl GraphToLogical {
282345
None => self.graph_id.node(),
283346
Some(v) => v.value.clone(),
284347
};
348+
let label = self.plan_graph_pattern_label(edge.label.as_deref())?;
285349
let edge_match = EdgeMatch {
286350
binder: BindSpec(binder),
287351
spec: EdgeFilter {
288-
label: self.plan_graph_pattern_label(&edge.label)?,
352+
label,
289353
filter: ValueFilter::Always,
290354
},
291355
};

partiql-logical-planner/src/lower.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,11 @@ impl<'ast> Visitor<'ast> for AstToLogical<'_> {
15121512
as_key,
15131513
at_key,
15141514
}),
1515+
FromLetKind::GraphTable => logical::BindingsOp::Scan(logical::Scan {
1516+
expr,
1517+
as_key,
1518+
at_key,
1519+
}),
15151520
};
15161521
let id = self.plan.add_operator(bexpr);
15171522
self.push_bexpr(id);
@@ -1899,16 +1904,19 @@ impl<'ast> Visitor<'ast> for AstToLogical<'_> {
18991904
self.enter_env();
19001905
Traverse::Continue
19011906
}
1902-
fn exit_graph_match(&mut self, graph_pattern: &'ast GraphMatch) -> Traverse {
1907+
fn exit_graph_match(&mut self, graph_match: &'ast GraphMatch) -> Traverse {
19031908
let mut env = self.exit_env();
19041909
true_or_fault!(self, env.len() == 1, "env.len() is not 1");
19051910

1906-
let value = Box::new(env.pop().unwrap());
1911+
let graph_reference = Box::new(env.pop().unwrap());
19071912
let graph_planner = crate::graph::GraphToLogical::default();
1908-
match graph_planner.plan_graph_match(&graph_pattern.graph_expr.node) {
1909-
Ok(pattern) => {
1910-
self.push_vexpr(ValueExpr::GraphMatch(GraphMatchExpr { value, pattern }));
19111913

1914+
match graph_planner.plan_graph_match(&graph_match) {
1915+
Ok(pattern) => {
1916+
self.push_vexpr(ValueExpr::GraphMatch(GraphMatchExpr {
1917+
value: graph_reference,
1918+
pattern,
1919+
}));
19121920
Traverse::Continue
19131921
}
19141922
Err(e) => {

partiql/tests/graph.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1+
use insta::assert_snapshot;
2+
use partiql_catalog::catalog::PartiqlCatalog;
3+
use partiql_catalog::context::SystemContext;
4+
use partiql_common::pretty::ToPretty;
5+
use partiql_eval::eval::graph::string_graph::StringGraphTypes;
6+
use partiql_eval::eval::BasicContext;
7+
use partiql_eval::plan::EvaluationMode;
18
use partiql_extension_ion::decode::{IonDecodeResult, IonDecoderBuilder, IonDecoderConfig};
29

10+
use crate::common::{compile, evaluate, lower, parse};
311
use partiql_extension_ion::Encoding;
12+
use partiql_value::{tuple, DateTime, Value};
13+
414
mod common;
515

616
#[track_caller]
@@ -155,3 +165,34 @@ fn repeated_identifers() {
155165
{id:e1, ends: (n1 -- n1)} ] }"##,
156166
)
157167
}
168+
169+
#[track_caller]
170+
fn snapshot_test_graph_eval(name: &'static str, contents: &'static str, query: &'static str) {
171+
let graph = decode_ion_text(contents, Encoding::PartiqlEncodedAsIon).expect("graph decode");
172+
let bindings = tuple![("g", graph)];
173+
174+
let parsed = parse(query).expect("parse");
175+
let catalog = PartiqlCatalog::default();
176+
let lowered = lower(&catalog, &parsed).expect("lower");
177+
let plan = compile(EvaluationMode::Permissive, &catalog, lowered).expect("compile");
178+
let res = evaluate(plan, bindings.into());
179+
assert!(res.is_ok());
180+
assert_snapshot!(name, res.unwrap().result);
181+
}
182+
183+
#[test]
184+
fn select_star_rfc_0025() {
185+
// The RFC 0025 graph has no payloads so these just return an appropriate number of `{}`
186+
let contents = include_str!("resources/rfc0025-example.ion");
187+
snapshot_test_graph_eval("RFC0025 Nodes", contents, "(g MATCH (x))");
188+
snapshot_test_graph_eval("RFC0025 L Triples", contents, "(g MATCH (x) -> (y))");
189+
snapshot_test_graph_eval("RFC0025 LUR Triples", contents, "(g MATCH (x) - (y))");
190+
}
191+
192+
#[test]
193+
fn select_star_gpml_paper() {
194+
let contents = include_str!("resources/gpml-paper-example.ion");
195+
snapshot_test_graph_eval("GPML Nodes", contents, "(g MATCH (x))");
196+
snapshot_test_graph_eval("GPML L Triples", contents, "(g MATCH (x) -[e]-> (y))");
197+
snapshot_test_graph_eval("GPML LUR Triples", contents, "(g MATCH (x) -[e]- (y))");
198+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
source: partiql/tests/graph.rs
3+
expression: res.unwrap().result
4+
---
5+
<<
6+
{ 'x': { 'owner': 'Scott', 'isBlocked': 'no' }, 'y': { 'name': 'Zembla' } },
7+
{
8+
'x': { 'owner': 'Aretha', 'isBlocked': 'no' },
9+
'y': { 'name': 'Ankh-Morpork' }
10+
},
11+
{ 'x': { 'owner': 'Mike', 'isBlocked': 'no' }, 'y': { 'name': 'Zembla' } },
12+
{
13+
'x': { 'owner': 'Jay', 'isBlocked': 'yes' },
14+
'y': { 'name': 'Ankh-Morpork' }
15+
},
16+
{ 'x': { 'owner': 'Charles', 'isBlocked': 'no' }, 'y': { 'name': 'Zembla' } },
17+
{
18+
'x': { 'owner': 'Scott', 'isBlocked': 'no' },
19+
'e': { 'date': TIMESTAMP '2020-01-01 0:00:00.0', 'amount': 8000000.00 },
20+
'y': { 'owner': 'Mike', 'isBlocked': 'no' }
21+
},
22+
{
23+
'x': { 'owner': 'Mike', 'isBlocked': 'no' },
24+
'e': { 'date': TIMESTAMP '2020-02-01 0:00:00.0', 'amount': 10000000.00 },
25+
'y': { 'owner': 'Aretha', 'isBlocked': 'no' }
26+
},
27+
{
28+
'x': { 'owner': 'Aretha', 'isBlocked': 'no' },
29+
'e': { 'date': TIMESTAMP '2020-03-01 0:00:00.0', 'amount': 10000000.00 },
30+
'y': { 'owner': 'Jay', 'isBlocked': 'yes' }
31+
},
32+
{
33+
'x': { 'owner': 'Jay', 'isBlocked': 'yes' },
34+
'e': { 'date': TIMESTAMP '2020-04-01 0:00:00.0', 'amount': 10000000.00 },
35+
'y': { 'owner': 'Dave', 'isBlocked': 'no' }
36+
},
37+
{
38+
'x': { 'owner': 'Dave', 'isBlocked': 'no' },
39+
'e': { 'date': TIMESTAMP '2020-06-01 0:00:00.0', 'amount': 10000000.00 },
40+
'y': { 'owner': 'Mike', 'isBlocked': 'no' }
41+
},
42+
{
43+
'x': { 'owner': 'Dave', 'isBlocked': 'no' },
44+
'e': { 'date': TIMESTAMP '2020-07-01 0:00:00.0', 'amount': 4000000.00 },
45+
'y': { 'owner': 'Charles', 'isBlocked': 'no' }
46+
},
47+
{
48+
'x': { 'owner': 'Mike', 'isBlocked': 'no' },
49+
'e': { 'date': TIMESTAMP '2020-08-01 0:00:00.0', 'amount': 6000000.00 },
50+
'y': { 'owner': 'Charles', 'isBlocked': 'no' }
51+
},
52+
{
53+
'x': { 'owner': 'Charles', 'isBlocked': 'no' },
54+
'e': { 'date': TIMESTAMP '2020-09-01 0:00:00.0', 'amount': 9000000.00 },
55+
'y': { 'owner': 'Scott', 'isBlocked': 'no' }
56+
}
57+
>>

0 commit comments

Comments
 (0)