Skip to content

Commit 6705ef8

Browse files
committed
Refactor GPML evaluation
1 parent 440d5ac commit 6705ef8

File tree

22 files changed

+591
-407
lines changed

22 files changed

+591
-407
lines changed

partiql-eval/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,11 @@ partiql-logical-planner = { path = "../partiql-logical-planner", version = "0.11
5656
default = []
5757
serde = [
5858
"dep:serde",
59-
"rust_decimal/serde-with-str",
59+
"partiql-common/serde",
6060
"partiql-logical/serde",
6161
"partiql-value/serde",
62+
"partiql-extension-ion/serde",
63+
"rust_decimal/serde-with-str",
6264
]
6365

6466
[[bench]]
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
use crate::eval::eval_expr_wrapper::UnaryValueExpr;
2+
use crate::eval::expr::{BindError, BindEvalExpr, EvalExpr};
3+
use crate::eval::graph::evaluator::GraphEvaluator;
4+
use crate::eval::graph::simple_graph::engine::SimpleGraphEngine;
5+
6+
use crate::eval::graph::plan::PathPatternMatch;
7+
use crate::eval::graph::string_graph::StringGraphTypes;
8+
use partiql_types::{type_graph, PartiqlNoIdShapeBuilder};
9+
use partiql_value::Value::Missing;
10+
use partiql_value::{Graph, Value};
11+
12+
/// Represents an evaluation `MATCH` operator, e.g. in `graph MATCH () -> ()'`.
13+
#[derive(Debug)]
14+
pub(crate) struct EvalGraphMatch {
15+
pub(crate) pattern: PathPatternMatch<StringGraphTypes>,
16+
}
17+
18+
impl EvalGraphMatch {
19+
pub(crate) fn new(pattern: PathPatternMatch<StringGraphTypes>) -> Self {
20+
EvalGraphMatch { pattern }
21+
}
22+
}
23+
24+
impl BindEvalExpr for EvalGraphMatch {
25+
fn bind<const STRICT: bool>(
26+
self,
27+
args: Vec<Box<dyn EvalExpr>>,
28+
) -> Result<Box<dyn EvalExpr>, BindError> {
29+
// use DummyShapeBuilder, as we don't care about shape Ids for evaluation dispatch
30+
let mut bld = PartiqlNoIdShapeBuilder::default();
31+
UnaryValueExpr::create_typed::<{ STRICT }, _>([type_graph!(bld)], args, move |value| {
32+
match value {
33+
Value::Graph(graph) => match graph.as_ref() {
34+
Graph::Simple(g) => {
35+
let engine = SimpleGraphEngine::new(g.clone());
36+
let ge = GraphEvaluator::new(engine);
37+
ge.eval(&self.pattern)
38+
}
39+
},
40+
_ => Missing,
41+
}
42+
})
43+
}
44+
}
45+
46+
#[cfg(test)]
47+
mod tests {
48+
use crate::eval::expr::{BindEvalExpr, EvalGlobalVarRef, EvalGraphMatch};
49+
use crate::eval::graph::bind_name::FreshBinder;
50+
use crate::eval::graph::plan::{
51+
BindSpec, DirectionFilter, EdgeFilter, ElementFilterBuilder, NodeFilter, NodeMatch,
52+
PathMatch, PathPatternMatch, StepFilter, TripleFilter,
53+
};
54+
use crate::eval::graph::string_graph::StringGraphTypes;
55+
use crate::eval::{BasicContext, MapBindings};
56+
use crate::test_value::TestValue;
57+
use partiql_catalog::context::SystemContext;
58+
use partiql_common::pretty::ToPretty;
59+
60+
use partiql_value::{tuple, BindingsName, DateTime, Value};
61+
62+
/*
63+
A simple 3-node, 3-edge graph which is intended to be able to be exactly matched by:
64+
```(graph MATCH
65+
(n1:a WHERE n1 == 1) -[e12:e WHERE e12 == 1.2]-> (n2),
66+
(n2:b WHERE n2 == 2) -[e23:d WHERE e23 == 2.3]-> (n3),
67+
(n3:a WHERE n3 == 3) ~[e_u:self WHERE e_u == <<>>]~ (n3)
68+
)```
69+
*/
70+
fn graph() -> Value {
71+
let graph = r##"
72+
$graph::{
73+
nodes: [ {id: n1, labels: ["a"], payload: 1},
74+
{id: n2, labels: ["b"], payload: 2},
75+
{id: n3, labels: ["a"], payload: 3} ],
76+
edges: [ {id: e12, labels: ["e"], payload: 1.2, ends: (n1 -> n2) },
77+
{id: e23, labels: ["d"], payload: 2.3, ends: (n2 -> n3) },
78+
{id: e_u, labels: ["self"], payload: $bag::[] , ends: (n3 -- n3) } ]
79+
}
80+
"##;
81+
TestValue::from(graph).value
82+
}
83+
84+
fn bindings() -> MapBindings<Value> {
85+
let mut bindings: MapBindings<Value> = MapBindings::default();
86+
bindings.insert("graph", graph());
87+
bindings
88+
}
89+
90+
fn context() -> BasicContext<'static> {
91+
let sys = SystemContext {
92+
now: DateTime::from_system_now_utc(),
93+
};
94+
let ctx = BasicContext::new(bindings(), sys);
95+
ctx
96+
}
97+
98+
fn graph_reference() -> Box<EvalGlobalVarRef> {
99+
Box::new(EvalGlobalVarRef {
100+
name: BindingsName::CaseInsensitive("graph".to_string().into()),
101+
})
102+
}
103+
104+
#[track_caller]
105+
fn test_graph(matcher: PathPatternMatch<StringGraphTypes>, expected: &'static str) {
106+
let mut eval = EvalGraphMatch::new(matcher)
107+
.bind::<false>(vec![graph_reference()])
108+
.expect("graph match bind");
109+
110+
let bindings = tuple![("graph", graph())];
111+
let ctx = context();
112+
let res = eval.evaluate(&bindings, &ctx);
113+
let expected = crate::test_value::parse_partiql_value_str(expected);
114+
115+
let pretty = |v: &Value| v.to_pretty_string(80).unwrap();
116+
117+
assert_eq!(pretty(&expected), pretty(res.as_ref()));
118+
}
119+
120+
#[test]
121+
fn node() {
122+
// Query: (graph MATCH (x:a))
123+
let binder = BindSpec("x".to_string());
124+
let spec = NodeFilter::labeled("a".to_string());
125+
let matcher = NodeMatch { binder, spec };
126+
127+
test_graph(matcher.into(), "<< { 'x': 1 }, { 'x': 3 } >>")
128+
}
129+
130+
#[test]
131+
fn no_edge_matches() {
132+
let fresh = FreshBinder::default();
133+
134+
// Query: (graph MATCH () -[e:foo]- ())
135+
let binders = (
136+
BindSpec(fresh.node()),
137+
BindSpec("e".to_string()),
138+
BindSpec(fresh.node()),
139+
);
140+
let spec = StepFilter {
141+
dir: DirectionFilter::LUR,
142+
triple: TripleFilter {
143+
lhs: NodeFilter::any(),
144+
e: EdgeFilter::labeled("foo".to_string()),
145+
rhs: NodeFilter::any(),
146+
},
147+
};
148+
149+
let matcher: PathMatch<StringGraphTypes> = PathMatch { binders, spec };
150+
151+
test_graph(matcher.into(), "<< >>")
152+
}
153+
154+
#[test]
155+
fn no_node_matches() {
156+
let fresh = FreshBinder::default();
157+
158+
// Query: (graph MATCH (:foo) -[]- ())
159+
let binders = (
160+
BindSpec(fresh.node()),
161+
BindSpec(fresh.node()),
162+
BindSpec(fresh.node()),
163+
);
164+
let spec = StepFilter {
165+
dir: DirectionFilter::LUR,
166+
triple: TripleFilter {
167+
lhs: NodeFilter::labeled("foo".to_string()),
168+
e: EdgeFilter::any(),
169+
rhs: NodeFilter::any(),
170+
},
171+
};
172+
173+
let matcher: PathMatch<StringGraphTypes> = PathMatch { binders, spec };
174+
175+
test_graph(matcher.into(), "<< >>")
176+
}
177+
178+
#[test]
179+
fn node_edge_node() {
180+
// Query: (graph MATCH (x)<-[z:e]-(y))
181+
let binders = (
182+
BindSpec("x".to_string()),
183+
BindSpec("z".to_string()),
184+
BindSpec("y".to_string()),
185+
);
186+
let spec = StepFilter {
187+
dir: DirectionFilter::L,
188+
triple: TripleFilter {
189+
lhs: NodeFilter::any(),
190+
e: EdgeFilter::labeled("e".to_string()),
191+
rhs: NodeFilter::any(),
192+
},
193+
};
194+
195+
let matcher: PathMatch<StringGraphTypes> = PathMatch { binders, spec };
196+
197+
test_graph(matcher.into(), "<< {'x': 2, 'z': 1.2, 'y': 1} >>")
198+
}
199+
200+
#[test]
201+
fn edge() {
202+
let fresh = FreshBinder::default();
203+
204+
// Query: (graph MATCH -> )
205+
let binders = (
206+
BindSpec(fresh.node()),
207+
BindSpec(fresh.edge()),
208+
BindSpec(fresh.node()),
209+
);
210+
let spec = StepFilter {
211+
dir: DirectionFilter::R,
212+
triple: TripleFilter {
213+
lhs: NodeFilter::any(),
214+
e: EdgeFilter::any(),
215+
rhs: NodeFilter::any(),
216+
},
217+
};
218+
219+
let matcher: PathMatch<StringGraphTypes> = PathMatch { binders, spec };
220+
221+
test_graph(matcher.into(), "<< { }, { } >>")
222+
}
223+
224+
#[test]
225+
fn edge_outgoing() {
226+
let fresh = FreshBinder::default();
227+
228+
// Query: (graph MATCH <-[z]-> )
229+
let binders = (
230+
BindSpec(fresh.node()),
231+
BindSpec("z".to_string()),
232+
BindSpec(fresh.node()),
233+
);
234+
let spec = StepFilter {
235+
dir: DirectionFilter::LR,
236+
triple: TripleFilter {
237+
lhs: NodeFilter::any(),
238+
e: EdgeFilter::any(),
239+
rhs: NodeFilter::any(),
240+
},
241+
};
242+
243+
let matcher: PathMatch<StringGraphTypes> = PathMatch { binders, spec };
244+
245+
test_graph(
246+
matcher.into(),
247+
"<< { 'z': 1.2 }, { 'z': 1.2 }, { 'z': 2.3 }, { 'z': 2.3 } >>",
248+
)
249+
}
250+
251+
#[test]
252+
fn n_e_n_e_n() {
253+
// Query: (graph MATCH (x:b)-[z1]-(y1:a)-[z2]-(y2:b) )
254+
let binders = (
255+
BindSpec("x".to_string()),
256+
BindSpec("z1".to_string()),
257+
BindSpec("y1".to_string()),
258+
);
259+
let spec = StepFilter {
260+
dir: DirectionFilter::LUR,
261+
triple: TripleFilter {
262+
lhs: NodeFilter::labeled("b".to_string()),
263+
e: EdgeFilter::any(),
264+
rhs: NodeFilter::labeled("a".to_string()),
265+
},
266+
};
267+
let matcher1: PathMatch<StringGraphTypes> = PathMatch { binders, spec };
268+
269+
let binders = (
270+
BindSpec("y1".to_string()),
271+
BindSpec("z2".to_string()),
272+
BindSpec("y2".to_string()),
273+
);
274+
let spec = StepFilter {
275+
dir: DirectionFilter::LUR,
276+
triple: TripleFilter {
277+
lhs: NodeFilter::labeled("a".to_string()),
278+
e: EdgeFilter::any(),
279+
rhs: NodeFilter::labeled("b".to_string()),
280+
},
281+
};
282+
let matcher2: PathMatch<StringGraphTypes> = PathMatch { binders, spec };
283+
284+
let pattern_match = PathPatternMatch::Concat(vec![
285+
PathPatternMatch::Match(matcher1),
286+
PathPatternMatch::Match(matcher2),
287+
]);
288+
289+
test_graph(
290+
pattern_match,
291+
"<< { 'x': 2, 'z1': 1.2, 'y1': 1, 'z2': 1.2, 'y2': 2 }, \
292+
{ 'x': 2, 'z1': 2.3, 'y1': 3, 'z2': 2.3, 'y2': 2 } >>",
293+
)
294+
}
295+
#[test]
296+
fn cycle() {
297+
let fresh = FreshBinder::default();
298+
299+
// Query: (graph MATCH (x1) - (x2) - (x1))
300+
let binders = (
301+
BindSpec("x1".to_string()),
302+
BindSpec(fresh.edge()),
303+
BindSpec("x2".to_string()),
304+
);
305+
let spec = StepFilter {
306+
dir: DirectionFilter::LUR,
307+
triple: TripleFilter {
308+
lhs: NodeFilter::any(),
309+
e: EdgeFilter::any(),
310+
rhs: NodeFilter::any(),
311+
},
312+
};
313+
let matcher1: PathMatch<StringGraphTypes> = PathMatch { binders, spec };
314+
315+
let binders = (
316+
BindSpec("x2".to_string()),
317+
BindSpec(fresh.edge()),
318+
BindSpec("x1".to_string()),
319+
);
320+
let spec = StepFilter {
321+
dir: DirectionFilter::LUR,
322+
triple: TripleFilter {
323+
lhs: NodeFilter::any(),
324+
e: EdgeFilter::any(),
325+
rhs: NodeFilter::any(),
326+
},
327+
};
328+
let matcher2: PathMatch<StringGraphTypes> = PathMatch { binders, spec };
329+
330+
let pattern_match = PathPatternMatch::Concat(vec![
331+
PathPatternMatch::Match(matcher1),
332+
PathPatternMatch::Match(matcher2),
333+
]);
334+
test_graph(
335+
pattern_match,
336+
"<< { 'x1': 3, 'x2': 3 }, \
337+
{ 'x1': 1, 'x2': 2 }, \
338+
{ 'x1': 2, 'x2': 1 }, \
339+
{ 'x1': 2, 'x2': 3 }, \
340+
{ 'x1': 3, 'x2': 2 } >>",
341+
)
342+
}
343+
}

partiql-eval/src/eval/expr/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ mod path;
1414
pub(crate) use path::*;
1515
mod pattern_match;
1616
pub(crate) use pattern_match::*;
17+
18+
mod graph_match;
19+
pub(crate) use graph_match::*;
1720
mod functions;
1821
mod operators;
1922

partiql-eval/src/eval/graph/bind_name.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::sync::atomic::AtomicU32;
22
use std::sync::atomic::Ordering::Relaxed;
33

4-
/// A unicode non-character prefixed onto 'anonymous' bind names
4+
/// A Unicode non-character prefixed onto 'anonymous' bind names
55
const ANON_PREFIX: char = '\u{FDD0}';
66

77
pub trait BindNameExt {

partiql-eval/src/eval/graph/engine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::eval::graph::plan::{
22
DirectionFilter, GraphPlanConvert, NodeFilter, StepFilter, TripleFilter,
33
};
44
use crate::eval::graph::result::Triple;
5-
use crate::eval::graph::string_graph::types::StringGraphTypes;
5+
use crate::eval::graph::string_graph::StringGraphTypes;
66
use crate::eval::graph::types::GraphTypes;
77
use partiql_value::Value;
88

0 commit comments

Comments
 (0)