Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion partiql-eval/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ partiql-logical-planner = { path = "../partiql-logical-planner", version = "0.11
default = []
serde = [
"dep:serde",
"rust_decimal/serde-with-str",
"partiql-common/serde",
"partiql-logical/serde",
"partiql-value/serde",
"partiql-extension-ion/serde",
"rust_decimal/serde-with-str",
]

[[bench]]
Expand Down
343 changes: 343 additions & 0 deletions partiql-eval/src/eval/expr/graph_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,343 @@
use crate::eval::eval_expr_wrapper::UnaryValueExpr;
use crate::eval::expr::{BindError, BindEvalExpr, EvalExpr};
use crate::eval::graph::evaluator::GraphEvaluator;
use crate::eval::graph::simple_graph::engine::SimpleGraphEngine;

use crate::eval::graph::plan::PathPatternMatch;
use crate::eval::graph::string_graph::StringGraphTypes;
use partiql_types::{type_graph, PartiqlNoIdShapeBuilder};
use partiql_value::Value::Missing;
use partiql_value::{Graph, Value};

/// Represents an evaluation `MATCH` operator, e.g. in `graph MATCH () -> ()'`.
#[derive(Debug)]
pub(crate) struct EvalGraphMatch {
pub(crate) pattern: PathPatternMatch<StringGraphTypes>,
}

impl EvalGraphMatch {
pub(crate) fn new(pattern: PathPatternMatch<StringGraphTypes>) -> Self {
EvalGraphMatch { pattern }
}
}

impl BindEvalExpr for EvalGraphMatch {
fn bind<const STRICT: bool>(
self,
args: Vec<Box<dyn EvalExpr>>,
) -> Result<Box<dyn EvalExpr>, BindError> {
// use DummyShapeBuilder, as we don't care about shape Ids for evaluation dispatch
let mut bld = PartiqlNoIdShapeBuilder::default();
UnaryValueExpr::create_typed::<{ STRICT }, _>([type_graph!(bld)], args, move |value| {
match value {
Value::Graph(graph) => match graph.as_ref() {
Graph::Simple(g) => {
let engine = SimpleGraphEngine::new(g.clone());
let ge = GraphEvaluator::new(engine);
ge.eval(&self.pattern)
}
},
_ => Missing,
}
})
}
}

#[cfg(test)]
mod tests {
use crate::eval::expr::{BindEvalExpr, EvalGlobalVarRef, EvalGraphMatch};
use crate::eval::graph::bind_name::FreshBinder;
use crate::eval::graph::plan::{
BindSpec, DirectionFilter, EdgeFilter, ElementFilterBuilder, NodeFilter, NodeMatch,
PathMatch, PathPatternMatch, StepFilter, TripleFilter,
};
use crate::eval::graph::string_graph::StringGraphTypes;
use crate::eval::{BasicContext, MapBindings};
use crate::test_value::TestValue;
use partiql_catalog::context::SystemContext;
use partiql_common::pretty::ToPretty;

use partiql_value::{tuple, BindingsName, DateTime, Value};

/*
A simple 3-node, 3-edge graph which is intended to be able to be exactly matched by:
```(graph MATCH
(n1:a WHERE n1 == 1) -[e12:e WHERE e12 == 1.2]-> (n2),
(n2:b WHERE n2 == 2) -[e23:d WHERE e23 == 2.3]-> (n3),
(n3:a WHERE n3 == 3) ~[e_u:self WHERE e_u == <<>>]~ (n3)
)```
*/
fn graph() -> Value {
let graph = r##"
$graph::{
nodes: [ {id: n1, labels: ["a"], payload: 1},
{id: n2, labels: ["b"], payload: 2},
{id: n3, labels: ["a"], payload: 3} ],
edges: [ {id: e12, labels: ["e"], payload: 1.2, ends: (n1 -> n2) },
{id: e23, labels: ["d"], payload: 2.3, ends: (n2 -> n3) },
{id: e_u, labels: ["self"], payload: $bag::[] , ends: (n3 -- n3) } ]
}
"##;
TestValue::from(graph).value
}

fn bindings() -> MapBindings<Value> {
let mut bindings: MapBindings<Value> = MapBindings::default();
bindings.insert("graph", graph());
bindings
}

fn context() -> BasicContext<'static> {
let sys = SystemContext {
now: DateTime::from_system_now_utc(),
};
let ctx = BasicContext::new(bindings(), sys);
ctx
}

fn graph_reference() -> Box<EvalGlobalVarRef> {
Box::new(EvalGlobalVarRef {
name: BindingsName::CaseInsensitive("graph".to_string().into()),
})
}

#[track_caller]
fn test_graph(matcher: PathPatternMatch<StringGraphTypes>, expected: &'static str) {
let eval = EvalGraphMatch::new(matcher)
.bind::<false>(vec![graph_reference()])
.expect("graph match bind");

let bindings = tuple![("graph", graph())];
let ctx = context();
let res = eval.evaluate(&bindings, &ctx);
let expected = crate::test_value::parse_partiql_value_str(expected);

let pretty = |v: &Value| v.to_pretty_string(80).unwrap();

assert_eq!(pretty(&expected), pretty(res.as_ref()));
}

#[test]
fn node() {
// Query: (graph MATCH (x:a))
let binder = BindSpec("x".to_string());
let spec = NodeFilter::labeled("a".to_string());
let matcher = NodeMatch { binder, spec };

test_graph(matcher.into(), "<< { 'x': 1 }, { 'x': 3 } >>")
}

#[test]
fn no_edge_matches() {
let fresh = FreshBinder::default();

// Query: (graph MATCH () -[e:foo]- ())
let binders = (
BindSpec(fresh.node()),
BindSpec("e".to_string()),
BindSpec(fresh.node()),
);
let spec = StepFilter {
dir: DirectionFilter::LUR,
triple: TripleFilter {
lhs: NodeFilter::any(),
e: EdgeFilter::labeled("foo".to_string()),
rhs: NodeFilter::any(),
},
};

let matcher: PathMatch<StringGraphTypes> = PathMatch { binders, spec };

test_graph(matcher.into(), "<< >>")
}

#[test]
fn no_node_matches() {
let fresh = FreshBinder::default();

// Query: (graph MATCH (:foo) -[]- ())
let binders = (
BindSpec(fresh.node()),
BindSpec(fresh.node()),
BindSpec(fresh.node()),
);
let spec = StepFilter {
dir: DirectionFilter::LUR,
triple: TripleFilter {
lhs: NodeFilter::labeled("foo".to_string()),
e: EdgeFilter::any(),
rhs: NodeFilter::any(),
},
};

let matcher: PathMatch<StringGraphTypes> = PathMatch { binders, spec };

test_graph(matcher.into(), "<< >>")
}

#[test]
fn node_edge_node() {
// Query: (graph MATCH (x)<-[z:e]-(y))
let binders = (
BindSpec("x".to_string()),
BindSpec("z".to_string()),
BindSpec("y".to_string()),
);
let spec = StepFilter {
dir: DirectionFilter::L,
triple: TripleFilter {
lhs: NodeFilter::any(),
e: EdgeFilter::labeled("e".to_string()),
rhs: NodeFilter::any(),
},
};

let matcher: PathMatch<StringGraphTypes> = PathMatch { binders, spec };

test_graph(matcher.into(), "<< {'x': 2, 'z': 1.2, 'y': 1} >>")
}

#[test]
fn edge() {
let fresh = FreshBinder::default();

// Query: (graph MATCH -> )
let binders = (
BindSpec(fresh.node()),
BindSpec(fresh.edge()),
BindSpec(fresh.node()),
);
let spec = StepFilter {
dir: DirectionFilter::R,
triple: TripleFilter {
lhs: NodeFilter::any(),
e: EdgeFilter::any(),
rhs: NodeFilter::any(),
},
};

let matcher: PathMatch<StringGraphTypes> = PathMatch { binders, spec };

test_graph(matcher.into(), "<< { }, { } >>")
}

#[test]
fn edge_outgoing() {
let fresh = FreshBinder::default();

// Query: (graph MATCH <-[z]-> )
let binders = (
BindSpec(fresh.node()),
BindSpec("z".to_string()),
BindSpec(fresh.node()),
);
let spec = StepFilter {
dir: DirectionFilter::LR,
triple: TripleFilter {
lhs: NodeFilter::any(),
e: EdgeFilter::any(),
rhs: NodeFilter::any(),
},
};

let matcher: PathMatch<StringGraphTypes> = PathMatch { binders, spec };

test_graph(
matcher.into(),
"<< { 'z': 1.2 }, { 'z': 1.2 }, { 'z': 2.3 }, { 'z': 2.3 } >>",
)
}

#[test]
fn n_e_n_e_n() {
// Query: (graph MATCH (x:b)-[z1]-(y1:a)-[z2]-(y2:b) )
let binders = (
BindSpec("x".to_string()),
BindSpec("z1".to_string()),
BindSpec("y1".to_string()),
);
let spec = StepFilter {
dir: DirectionFilter::LUR,
triple: TripleFilter {
lhs: NodeFilter::labeled("b".to_string()),
e: EdgeFilter::any(),
rhs: NodeFilter::labeled("a".to_string()),
},
};
let matcher1: PathMatch<StringGraphTypes> = PathMatch { binders, spec };

let binders = (
BindSpec("y1".to_string()),
BindSpec("z2".to_string()),
BindSpec("y2".to_string()),
);
let spec = StepFilter {
dir: DirectionFilter::LUR,
triple: TripleFilter {
lhs: NodeFilter::labeled("a".to_string()),
e: EdgeFilter::any(),
rhs: NodeFilter::labeled("b".to_string()),
},
};
let matcher2: PathMatch<StringGraphTypes> = PathMatch { binders, spec };

let pattern_match = PathPatternMatch::Concat(vec![
PathPatternMatch::Match(matcher1),
PathPatternMatch::Match(matcher2),
]);

test_graph(
pattern_match,
"<< { 'x': 2, 'z1': 1.2, 'y1': 1, 'z2': 1.2, 'y2': 2 }, \
{ 'x': 2, 'z1': 2.3, 'y1': 3, 'z2': 2.3, 'y2': 2 } >>",
)
}
#[test]
fn cycle() {
let fresh = FreshBinder::default();

// Query: (graph MATCH (x1) - (x2) - (x1))
let binders = (
BindSpec("x1".to_string()),
BindSpec(fresh.edge()),
BindSpec("x2".to_string()),
);
let spec = StepFilter {
dir: DirectionFilter::LUR,
triple: TripleFilter {
lhs: NodeFilter::any(),
e: EdgeFilter::any(),
rhs: NodeFilter::any(),
},
};
let matcher1: PathMatch<StringGraphTypes> = PathMatch { binders, spec };

let binders = (
BindSpec("x2".to_string()),
BindSpec(fresh.edge()),
BindSpec("x1".to_string()),
);
let spec = StepFilter {
dir: DirectionFilter::LUR,
triple: TripleFilter {
lhs: NodeFilter::any(),
e: EdgeFilter::any(),
rhs: NodeFilter::any(),
},
};
let matcher2: PathMatch<StringGraphTypes> = PathMatch { binders, spec };

let pattern_match = PathPatternMatch::Concat(vec![
PathPatternMatch::Match(matcher1),
PathPatternMatch::Match(matcher2),
]);
test_graph(
pattern_match,
"<< { 'x1': 3, 'x2': 3 }, \
{ 'x1': 1, 'x2': 2 }, \
{ 'x1': 2, 'x2': 1 }, \
{ 'x1': 2, 'x2': 3 }, \
{ 'x1': 3, 'x2': 2 } >>",
)
}
}
3 changes: 3 additions & 0 deletions partiql-eval/src/eval/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ mod path;
pub(crate) use path::*;
mod pattern_match;
pub(crate) use pattern_match::*;

mod graph_match;
pub(crate) use graph_match::*;
mod functions;
mod operators;

Expand Down
2 changes: 1 addition & 1 deletion partiql-eval/src/eval/graph/bind_name.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering::Relaxed;

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

pub trait BindNameExt {
Expand Down
2 changes: 1 addition & 1 deletion partiql-eval/src/eval/graph/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::eval::graph::plan::{
DirectionFilter, GraphPlanConvert, NodeFilter, StepFilter, TripleFilter,
};
use crate::eval::graph::result::Triple;
use crate::eval::graph::string_graph::types::StringGraphTypes;
use crate::eval::graph::string_graph::StringGraphTypes;
use crate::eval::graph::types::GraphTypes;
use partiql_value::Value;

Expand Down
Loading
Loading