From f59d3d161a4b3bf9608a49f244f524be0eb436cd Mon Sep 17 00:00:00 2001 From: Josh Pschorr Date: Thu, 20 Mar 2025 18:49:26 -0700 Subject: [PATCH 1/3] Refactor GPML evaluation --- partiql-eval/Cargo.toml | 4 +- partiql-eval/src/eval/expr/graph_match.rs | 343 ++++++++++++++++++ partiql-eval/src/eval/expr/mod.rs | 3 + partiql-eval/src/eval/graph/bind_name.rs | 2 +- partiql-eval/src/eval/graph/engine.rs | 2 +- partiql-eval/src/eval/graph/evaluator.rs | 8 +- partiql-eval/src/eval/graph/mod.rs | 4 +- partiql-eval/src/eval/graph/plan.rs | 3 +- .../src/eval/graph/simple_graph/engine.rs | 8 +- .../src/eval/graph/simple_graph/types.rs | 11 +- .../types.rs => string_graph.rs} | 10 +- .../src/eval/graph/string_graph/mod.rs | 1 - partiql-eval/src/eval/graph/types.rs | 28 +- partiql-eval/src/eval/mod.rs | 50 +-- partiql-eval/src/lib.rs | 297 --------------- partiql-eval/src/plan.rs | 16 +- partiql-eval/src/test_value.rs | 2 +- partiql-logical/Cargo.toml | 4 +- partiql-logical/src/graph.rs | 154 ++++++++ partiql-logical/src/lib.rs | 15 + partiql-types/src/lib.rs | 29 +- partiql-value/src/datum.rs | 4 +- 22 files changed, 591 insertions(+), 407 deletions(-) create mode 100644 partiql-eval/src/eval/expr/graph_match.rs rename partiql-eval/src/eval/graph/{string_graph/types.rs => string_graph.rs} (51%) delete mode 100644 partiql-eval/src/eval/graph/string_graph/mod.rs create mode 100644 partiql-logical/src/graph.rs diff --git a/partiql-eval/Cargo.toml b/partiql-eval/Cargo.toml index 796ac33c..91c30d32 100644 --- a/partiql-eval/Cargo.toml +++ b/partiql-eval/Cargo.toml @@ -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]] diff --git a/partiql-eval/src/eval/expr/graph_match.rs b/partiql-eval/src/eval/expr/graph_match.rs new file mode 100644 index 00000000..2225175b --- /dev/null +++ b/partiql-eval/src/eval/expr/graph_match.rs @@ -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, +} + +impl EvalGraphMatch { + pub(crate) fn new(pattern: PathPatternMatch) -> Self { + EvalGraphMatch { pattern } + } +} + +impl BindEvalExpr for EvalGraphMatch { + fn bind( + self, + args: Vec>, + ) -> Result, 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 { + let mut bindings: MapBindings = 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 { + Box::new(EvalGlobalVarRef { + name: BindingsName::CaseInsensitive("graph".to_string().into()), + }) + } + + #[track_caller] + fn test_graph(matcher: PathPatternMatch, expected: &'static str) { + let mut eval = EvalGraphMatch::new(matcher) + .bind::(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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 } >>", + ) + } +} diff --git a/partiql-eval/src/eval/expr/mod.rs b/partiql-eval/src/eval/expr/mod.rs index c277aca4..e280874b 100644 --- a/partiql-eval/src/eval/expr/mod.rs +++ b/partiql-eval/src/eval/expr/mod.rs @@ -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; diff --git a/partiql-eval/src/eval/graph/bind_name.rs b/partiql-eval/src/eval/graph/bind_name.rs index 8d05a9fe..0efa47b1 100644 --- a/partiql-eval/src/eval/graph/bind_name.rs +++ b/partiql-eval/src/eval/graph/bind_name.rs @@ -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 { diff --git a/partiql-eval/src/eval/graph/engine.rs b/partiql-eval/src/eval/graph/engine.rs index 67a19de3..4b61bd1a 100644 --- a/partiql-eval/src/eval/graph/engine.rs +++ b/partiql-eval/src/eval/graph/engine.rs @@ -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; diff --git a/partiql-eval/src/eval/graph/evaluator.rs b/partiql-eval/src/eval/graph/evaluator.rs index 8506b11d..ebbfd0dc 100644 --- a/partiql-eval/src/eval/graph/evaluator.rs +++ b/partiql-eval/src/eval/graph/evaluator.rs @@ -1,14 +1,16 @@ use crate::eval::graph::bind_name::BindNameExt; use crate::eval::graph::engine::GraphEngine; -use crate::eval::graph::plan::{BindSpec, NodeMatch, PathMatch, PathPatternMatch}; use crate::eval::graph::result::{ GraphElement, NodeBinding, PathBinding, PathPatternBinding, PathPatternNodes, }; -use crate::eval::graph::string_graph::types::StringGraphTypes; -use crate::eval::graph::types::GraphTypes; + use fxhash::FxBuildHasher; use indexmap::IndexMap; use itertools::Itertools; + +use crate::eval::graph::plan::{BindSpec, NodeMatch, PathMatch, PathPatternMatch}; +use crate::eval::graph::string_graph::StringGraphTypes; +use crate::eval::graph::types::GraphTypes; use partiql_value::{Bag, Tuple, Value}; use std::marker::PhantomData; diff --git a/partiql-eval/src/eval/graph/mod.rs b/partiql-eval/src/eval/graph/mod.rs index 49db5ba6..1fc838a7 100644 --- a/partiql-eval/src/eval/graph/mod.rs +++ b/partiql-eval/src/eval/graph/mod.rs @@ -4,5 +4,5 @@ pub(crate) mod evaluator; pub(crate) mod plan; pub(crate) mod result; pub(crate) mod simple_graph; -pub(crate) mod string_graph; -pub(crate) mod types; +pub mod string_graph; +pub mod types; diff --git a/partiql-eval/src/eval/graph/plan.rs b/partiql-eval/src/eval/graph/plan.rs index 4c030297..47bcb2c6 100644 --- a/partiql-eval/src/eval/graph/plan.rs +++ b/partiql-eval/src/eval/graph/plan.rs @@ -1,8 +1,7 @@ +use crate::eval::graph::types::GraphTypes; use std::fmt::Debug; use std::hash::Hash; -use crate::eval::graph::types::GraphTypes; - /// A plan specification for an edge's direction filtering. #[allow(dead_code)] // TODO remove once graph planning is implemented #[allow(clippy::upper_case_acronyms)] diff --git a/partiql-eval/src/eval/graph/simple_graph/engine.rs b/partiql-eval/src/eval/graph/simple_graph/engine.rs index 5a287b0e..ebe359b2 100644 --- a/partiql-eval/src/eval/graph/simple_graph/engine.rs +++ b/partiql-eval/src/eval/graph/simple_graph/engine.rs @@ -1,11 +1,11 @@ use crate::eval::graph::engine::{GraphAccess, GraphEngine, TripleScan}; -use crate::eval::graph::plan::GraphPlanConvert; +use crate::eval::graph::result::Triple; + use crate::eval::graph::plan::{ - BindSpec, EdgeFilter, LabelFilter, NodeFilter, TripleFilter, ValueFilter, + BindSpec, EdgeFilter, GraphPlanConvert, LabelFilter, NodeFilter, TripleFilter, ValueFilter, }; -use crate::eval::graph::result::Triple; use crate::eval::graph::simple_graph::types::SimpleGraphTypes; -use crate::eval::graph::string_graph::types::StringGraphTypes; +use crate::eval::graph::string_graph::StringGraphTypes; use crate::eval::graph::types::GraphTypes; use delegate::delegate; use lasso::Rodeo; diff --git a/partiql-eval/src/eval/graph/simple_graph/types.rs b/partiql-eval/src/eval/graph/simple_graph/types.rs index 61fb78d5..d0e67655 100644 --- a/partiql-eval/src/eval/graph/simple_graph/types.rs +++ b/partiql-eval/src/eval/graph/simple_graph/types.rs @@ -1,5 +1,6 @@ -use crate::eval::graph::types::{BinderTy, EdgeIdTy, GraphLabelTy, GraphTypes, NodeIdTy}; use lasso::Spur; + +use crate::eval::graph::types::GraphTypes; use partiql_value::{GEdgeId, GLabelId, GNodeId}; #[derive(Debug, Clone, Eq, PartialEq, Hash)] @@ -11,11 +12,3 @@ impl GraphTypes for SimpleGraphTypes { type NodeId = GNodeId; type EdgeId = GEdgeId; } - -impl GraphLabelTy for GLabelId {} - -impl NodeIdTy for GNodeId {} - -impl EdgeIdTy for GEdgeId {} - -impl BinderTy for Spur {} diff --git a/partiql-eval/src/eval/graph/string_graph/types.rs b/partiql-eval/src/eval/graph/string_graph.rs similarity index 51% rename from partiql-eval/src/eval/graph/string_graph/types.rs rename to partiql-eval/src/eval/graph/string_graph.rs index a46d8a7d..8092ad0c 100644 --- a/partiql-eval/src/eval/graph/string_graph/types.rs +++ b/partiql-eval/src/eval/graph/string_graph.rs @@ -1,4 +1,4 @@ -use crate::eval::graph::types::{BinderTy, EdgeIdTy, GraphLabelTy, GraphTypes, NodeIdTy}; +use crate::eval::graph::types::GraphTypes; #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct StringGraphTypes; @@ -9,11 +9,3 @@ impl GraphTypes for StringGraphTypes { type NodeId = (); type EdgeId = (); } - -impl GraphLabelTy for String {} - -impl NodeIdTy for () {} - -impl EdgeIdTy for () {} - -impl BinderTy for String {} diff --git a/partiql-eval/src/eval/graph/string_graph/mod.rs b/partiql-eval/src/eval/graph/string_graph/mod.rs deleted file mode 100644 index cd408564..00000000 --- a/partiql-eval/src/eval/graph/string_graph/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod types; diff --git a/partiql-eval/src/eval/graph/types.rs b/partiql-eval/src/eval/graph/types.rs index 65a7c710..fa4091c4 100644 --- a/partiql-eval/src/eval/graph/types.rs +++ b/partiql-eval/src/eval/graph/types.rs @@ -1,26 +1,14 @@ use std::fmt::Debug; use std::hash::Hash; -#[allow(dead_code)] // TODO remove once graph planning is implemented -/// A Label for a graph. -pub trait GraphLabelTy: Debug + Clone + Eq + Hash {} - -#[allow(dead_code)] // TODO remove once graph planning is implemented -/// A Binder (name in a graph pattern) for a graph. -pub trait BinderTy: Debug + Clone + Eq + Hash {} - -#[allow(dead_code)] // TODO remove once graph planning is implemented -/// A Node for a graph. -pub trait NodeIdTy: Debug + Clone + Eq + Hash {} - -#[allow(dead_code)] // TODO remove once graph planning is implemented -/// An Edge for a graph. -pub trait EdgeIdTy: Debug + Clone + Eq + Hash {} - /// The collected types for a graph. pub trait GraphTypes: 'static + Sized + Debug + Clone + Eq + Hash { - type Binder: BinderTy; - type Label: GraphLabelTy; - type NodeId: NodeIdTy; - type EdgeId: EdgeIdTy; + /// A Binder (name in a graph pattern) for a graph. + type Binder: Debug + Clone + Eq + Hash; + /// A Label for a graph. + type Label: Debug + Clone + Eq + Hash; + /// A Node for a graph. + type NodeId: Debug + Clone + Eq + Hash; + /// An Edge for a graph. + type EdgeId: Debug + Clone + Eq + Hash; } diff --git a/partiql-eval/src/eval/mod.rs b/partiql-eval/src/eval/mod.rs index 7109f7fc..097eb875 100644 --- a/partiql-eval/src/eval/mod.rs +++ b/partiql-eval/src/eval/mod.rs @@ -11,7 +11,7 @@ use petgraph::dot::Dot; use petgraph::prelude::StableGraph; use petgraph::{Directed, Outgoing}; -use partiql_value::{Graph, Tuple, Value}; +use partiql_value::Value; use crate::env::basic::{MapBindings, NestedBindings}; @@ -25,11 +25,6 @@ use unicase::UniCase; use crate::eval::evaluable::{EvalType, Evaluable}; use crate::plan::EvaluationMode; -use crate::eval::expr::EvalExpr; -use crate::eval::graph::plan::PathPatternMatch; -use crate::eval::graph::simple_graph::engine::SimpleGraphEngine; -use crate::eval::graph::string_graph::types::StringGraphTypes; -use graph::evaluator::GraphEvaluator; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -263,46 +258,3 @@ impl<'a> EvalContext<'a> for NestedContext<'a, '_> { } } } - -#[allow(dead_code)] // TODO remove once graph planning is implemented -#[derive(Debug)] -pub(crate) struct EvalGraphMatch { - pub(crate) expr: Box, - pub(crate) matcher: PathPatternMatch, - pub(crate) input: Option, -} - -impl EvalGraphMatch { - #[allow(dead_code)] // TODO remove once graph planning is implemented - pub(crate) fn new( - expr: Box, - matcher: PathPatternMatch, - ) -> Self { - EvalGraphMatch { - expr, - matcher, - input: None, - } - } -} - -impl Evaluable for EvalGraphMatch { - fn evaluate<'a, 'c>(&mut self, ctx: &'c dyn EvalContext<'c>) -> Value { - let graph = match self.expr.evaluate(&Tuple::new(), ctx).into_owned() { - Value::Graph(g) => *g, - _ => todo!("Error; not a graph"), - }; - - match graph { - Graph::Simple(g) => { - let engine = SimpleGraphEngine::new(g); - let ge = GraphEvaluator::new(engine); - ge.eval(&self.matcher) - } - } - } - - fn update_input(&mut self, input: Value, _branch_num: u8, _ctx: &dyn EvalContext<'_>) { - self.input = Some(input); - } -} diff --git a/partiql-eval/src/lib.rs b/partiql-eval/src/lib.rs index d38a52e0..d83857ad 100644 --- a/partiql-eval/src/lib.rs +++ b/partiql-eval/src/lib.rs @@ -2435,301 +2435,4 @@ mod tests { assert_eq!(Value::Bag(Box::new(expected)), res); } } - - mod graph { - use crate::env::basic::MapBindings; - use crate::eval::evaluable::Evaluable; - use crate::eval::expr::EvalGlobalVarRef; - 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::types::StringGraphTypes; - use crate::eval::{BasicContext, EvalGraphMatch}; - use crate::test_value::TestValue; - use partiql_catalog::context::SystemContext; - use partiql_common::pretty::ToPretty; - use partiql_value::{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 { - let mut bindings: MapBindings = MapBindings::default(); - bindings.insert("g3aba", 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 { - Box::new(EvalGlobalVarRef { - name: BindingsName::CaseInsensitive("g3aba".to_string().into()), - }) - } - - #[track_caller] - fn test_graph(matcher: PathPatternMatch, expected: &'static str) { - let mut eval = EvalGraphMatch::new(graph_reference(), matcher); - - let ctx = context(); - let res = eval.evaluate(&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)); - } - - #[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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 } >>", - ) - } - } } diff --git a/partiql-eval/src/plan.rs b/partiql-eval/src/plan.rs index fe908535..f29b9c82 100644 --- a/partiql-eval/src/plan.rs +++ b/partiql-eval/src/plan.rs @@ -1,9 +1,10 @@ use itertools::{Either, Itertools}; use partiql_logical as logical; use partiql_logical::{ - AggFunc, BagOperator, BinaryOp, BindingsOp, CallName, GroupingStrategy, IsTypeExpr, JoinKind, - Lit, LogicalPlan, OpId, PathComponent, Pattern, PatternMatchExpr, SearchedCase, SetQuantifier, - SortSpecNullOrder, SortSpecOrder, Type, UnaryOp, ValueExpr, VarRefType, + AggFunc, BagOperator, BinaryOp, BindingsOp, CallName, GraphMatchExpr, GroupingStrategy, + IsTypeExpr, JoinKind, Lit, LogicalPlan, OpId, PathComponent, Pattern, PatternMatchExpr, + SearchedCase, SetQuantifier, SortSpecNullOrder, SortSpecOrder, Type, UnaryOp, ValueExpr, + VarRefType, }; use petgraph::prelude::StableGraph; use std::collections::HashMap; @@ -18,7 +19,7 @@ use crate::eval::evaluable::{ use crate::eval::expr::{ BindError, BindEvalExpr, EvalBagExpr, EvalBetweenExpr, EvalCollFn, EvalDynamicLookup, EvalExpr, EvalExtractFn, EvalFnAbs, EvalFnBaseTableExpr, EvalFnCardinality, EvalFnExists, EvalFnOverlay, - EvalFnPosition, EvalFnSubstring, EvalIsTypeExpr, EvalLikeMatch, + EvalFnPosition, EvalFnSubstring, EvalGraphMatch, EvalIsTypeExpr, EvalLikeMatch, EvalLikeNonStringNonLiteralMatch, EvalListExpr, EvalLitExpr, EvalOpBinary, EvalOpUnary, EvalPath, EvalSearchedCaseExpr, EvalStringFn, EvalTrimFn, EvalTupleExpr, EvalVarRef, }; @@ -493,6 +494,13 @@ impl<'c> EvaluatorPlanner<'c> { ("pattern expr", expr) } + ValueExpr::GraphMatch(GraphMatchExpr { value, pattern }) => { + //let pattern = plan_graph_match(pattern); + let pattern = todo!(); + + let expr = EvalGraphMatch::new(pattern).bind::<{ STRICT }>(plan_args(&[value])); + ("graphmatch expr", expr) + } ValueExpr::SubQueryExpr(expr) => ( "subquery", Ok(Box::new(EvalSubQueryExpr::new( diff --git a/partiql-eval/src/test_value.rs b/partiql-eval/src/test_value.rs index 821c070a..af0c48d7 100644 --- a/partiql-eval/src/test_value.rs +++ b/partiql-eval/src/test_value.rs @@ -55,7 +55,7 @@ fn parse_test_value_str(contents: &str) -> Value { } #[cfg(test)] -pub fn parse_partiql_value_str(contents: &str) -> Value { +pub(crate) fn parse_partiql_value_str(contents: &str) -> Value { use crate::env::basic::MapBindings; use crate::eval::BasicContext; use crate::plan::{EvaluationMode, EvaluatorPlanner}; diff --git a/partiql-logical/Cargo.toml b/partiql-logical/Cargo.toml index 03f79b87..cabeff0e 100644 --- a/partiql-logical/Cargo.toml +++ b/partiql-logical/Cargo.toml @@ -38,5 +38,7 @@ serde = { version = "1", features = ["derive"], optional = true } default = [] serde = [ "dep:serde", - "ordered-float/serde" + "partiql-common/serde", + "partiql-value/serde", + "ordered-float/serde", ] diff --git a/partiql-logical/src/graph.rs b/partiql-logical/src/graph.rs new file mode 100644 index 00000000..16fcb99b --- /dev/null +++ b/partiql-logical/src/graph.rs @@ -0,0 +1,154 @@ +use std::fmt::Debug; +use std::hash::Hash; + +/// A plan specification for an edge's direction filtering. +#[allow(dead_code)] // TODO remove once graph planning is implemented +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug, Clone, Copy)] +pub enum DirectionFilter { + L, // <- + U, // ~ + R, // -> + LU, // <~ + UR, // ~> + LR, // <-> + LUR, // - +} + +/// A plan specification for bind names. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct BindSpec(pub String); + +/// A plan specification for label filtering. +#[derive(Debug, Clone, Default)] +pub enum LabelFilter { + #[default] + Always, + Named(String), + Never, +} + +/// A plan specification for value filtering. +#[derive(Debug, Clone, Copy, Default)] +pub enum ValueFilter { + #[default] + Always, + // TODO other variant for, e.g., `WHERE` filters +} + +/// A plan specification for node label & value filtering. +#[derive(Debug, Clone)] +pub struct NodeFilter { + pub label: LabelFilter, + pub filter: ValueFilter, +} + +/// A plan specification for edge label & value filtering. +#[derive(Debug, Clone)] +pub struct EdgeFilter { + pub label: LabelFilter, + pub filter: ValueFilter, +} + +/// A plan specification for triple (node, edge, node) matching. +#[derive(Debug, Clone)] +pub struct TripleFilter { + pub lhs: NodeFilter, + pub e: EdgeFilter, + pub rhs: NodeFilter, +} + +/// A plan specification for 'step' (triple + edge direction) matching. +#[derive(Debug, Clone)] +pub struct StepFilter { + pub dir: DirectionFilter, + pub triple: TripleFilter, +} + +/// A plan specification for 'path patterns' (i.e., sequences of 'node edge node's) matching. +#[allow(dead_code)] // TODO remove once graph planning is implemented +#[derive(Debug, Clone)] +pub struct PathPatternFilter { + pub head: NodeFilter, + pub tail: Vec<(DirectionFilter, EdgeFilter, NodeFilter)>, +} + +/// A plan specification for node matching. +#[derive(Debug, Clone)] +pub struct NodeMatch { + pub binder: BindSpec, + pub spec: NodeFilter, +} + +/// A plan specification for edge matching. +#[allow(dead_code)] // TODO remove once graph planning is implemented +#[derive(Debug, Clone)] +pub struct EdgeMatch { + pub binder: BindSpec, + pub spec: EdgeFilter, +} + +/// A plan specification for path (i.e., node, edge, node) matching. +#[derive(Debug, Clone)] +pub struct PathMatch { + pub binders: (BindSpec, BindSpec, BindSpec), + pub spec: StepFilter, +} + +/// A plan specification for path patterns (i.e., sequences of [`PathMatch`]s) matching. +#[allow(dead_code)] // TODO remove once graph planning is implemented +#[derive(Debug, Clone)] +pub enum PathPatternMatch { + Node(NodeMatch), + Match(PathMatch), + Concat(Vec), +} + +impl From for PathPatternMatch { + fn from(value: PathMatch) -> Self { + Self::Match(value) + } +} + +impl From for PathPatternMatch { + fn from(value: NodeMatch) -> Self { + Self::Node(value) + } +} + +#[allow(dead_code)] // TODO remove once graph planning is implemented +pub trait ElementFilterBuilder { + fn any() -> Self; + fn labeled(label: String) -> Self; +} + +impl ElementFilterBuilder for NodeFilter { + fn any() -> Self { + Self { + label: LabelFilter::Always, + filter: ValueFilter::Always, + } + } + + fn labeled(label: String) -> Self { + Self { + label: LabelFilter::Named(label), + filter: ValueFilter::Always, + } + } +} + +impl ElementFilterBuilder for EdgeFilter { + fn any() -> Self { + Self { + label: LabelFilter::Always, + filter: ValueFilter::Always, + } + } + fn labeled(label: String) -> Self { + Self { + label: LabelFilter::Named(label), + filter: ValueFilter::Always, + } + } +} diff --git a/partiql-logical/src/lib.rs b/partiql-logical/src/lib.rs index 2dcc614c..d122a1fb 100644 --- a/partiql-logical/src/lib.rs +++ b/partiql-logical/src/lib.rs @@ -15,6 +15,9 @@ mod util; +mod graph; +pub use graph::*; + use ordered_float::OrderedFloat; use partiql_common::catalog::ObjectId; /// # Examples @@ -443,6 +446,7 @@ pub enum ValueExpr { NullIfExpr(NullIfExpr), CoalesceExpr(CoalesceExpr), Call(CallExpr), + GraphMatch(GraphMatchExpr), } /// Represents a `PartiQL` literal value. @@ -600,6 +604,17 @@ pub struct LikeNonStringNonLiteralMatch { pub escape: Box, } +/// Represents a `PartiQL` GPML expression, e.g. `graph MATCH (node1:NodeLabel) -> (node2:OtherLabel)`. +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct GraphMatchExpr { + pub value: Box, + pub pattern: GraphMatchPattern, +} +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct GraphMatchPattern {} + /// Represents a sub-query expression, e.g. `SELECT v.a*2 AS u FROM t AS v` in /// `SELECT t.a, s FROM data AS t, (SELECT v.a*2 AS u FROM t AS v) AS s` #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/partiql-types/src/lib.rs b/partiql-types/src/lib.rs index 287a8f34..15cb7740 100644 --- a/partiql-types/src/lib.rs +++ b/partiql-types/src/lib.rs @@ -146,6 +146,13 @@ macro_rules! type_struct { }}; } +#[macro_export] +macro_rules! type_graph { + ($bld:expr) => { + $bld.new_graph() + }; +} + #[macro_export] macro_rules! struct_fields { ($(($x:expr, $y:expr)),+ $(,)?) => ( @@ -259,6 +266,18 @@ impl PartiqlShape { ) } + #[inline] + pub fn is_graph(&self) -> bool { + matches!( + *self, + PartiqlShape::Static(StaticType { + ty: Static::Graph(), + nullable: true, + .. + }) + ) + } + #[inline] pub fn is_bag(&self) -> bool { matches!( @@ -433,6 +452,11 @@ impl ShapeBuilder { self.new_static(Static::Struct(s)) } + #[inline] + pub fn new_graph(&mut self) -> PartiqlShape { + self.new_static(Static::Graph()) + } + #[inline] pub fn new_struct_of_dyn(&mut self) -> PartiqlShape { self.new_struct(StructType::new_any()) @@ -739,7 +763,8 @@ pub enum Static { Struct(StructType), Bag(BagType), Array(ArrayType), - // TODO GPML + + Graph(/* TODO? */), // TODO Add BitString, ByteString, Blob, Clob, and Graph types } @@ -768,6 +793,7 @@ impl Static { Static::Struct(_) => StaticCategory::Tuple(), Static::Bag(b) => StaticCategory::Sequence(b.element_type()), Static::Array(b) => StaticCategory::Sequence(b.element_type()), + Static::Graph() => StaticCategory::Graph(), _ => StaticCategory::Scalar(self), } } @@ -800,6 +826,7 @@ impl Display for Static { Static::Struct(inner) => std::fmt::Display::fmt(inner, f), Static::Bag(inner) => std::fmt::Display::fmt(inner, f), Static::Array(inner) => std::fmt::Display::fmt(inner, f), + Static::Graph() => write!(f, "Graph"), } } } diff --git a/partiql-value/src/datum.rs b/partiql-value/src/datum.rs index 080eb604..78692fee 100644 --- a/partiql-value/src/datum.rs +++ b/partiql-value/src/datum.rs @@ -120,7 +120,7 @@ pub enum DatumValueOwned { #[derive(Debug)] pub enum DatumGraphOwned { - Graph(Graph), + Graph(Box), } impl<'a> DatumCategory<'a> for Value { @@ -132,6 +132,7 @@ impl<'a> DatumCategory<'a> for Value { Value::Bag(bag) => DatumCategoryRef::Sequence(DatumSeqRef::Bag(bag)), Value::Tuple(tuple) => DatumCategoryRef::Tuple(DatumTupleRef::Tuple(tuple.as_ref())), Value::Variant(doc) => doc.category(), + Value::Graph(graph) => DatumCategoryRef::Graph(DatumGraphRef::Graph(graph.as_ref())), val => DatumCategoryRef::Scalar(DatumValueRef::Value(val)), } } @@ -144,6 +145,7 @@ impl<'a> DatumCategory<'a> for Value { Value::Bag(bag) => DatumCategoryOwned::Sequence(DatumSeqOwned::Bag(bag)), Value::Tuple(tuple) => DatumCategoryOwned::Tuple(DatumTupleOwned::Tuple(tuple)), Value::Variant(doc) => doc.into_category(), + Value::Graph(graph) => DatumCategoryOwned::Graph(DatumGraphOwned::Graph(graph)), val => DatumCategoryOwned::Scalar(DatumValueOwned::Value(val)), } } From d3f90a8aa6e91010103b1ceab5ce0b0b26cdea49 Mon Sep 17 00:00:00 2001 From: Josh Pschorr Date: Fri, 21 Mar 2025 13:11:26 -0700 Subject: [PATCH 2/3] Add GPML logical plan and plan -> evaluator. --- partiql-eval/src/eval/expr/graph_match.rs | 2 +- partiql-eval/src/plan.rs | 154 +++++++++++++++++++--- partiql-logical/src/graph.rs | 95 ++++--------- partiql-logical/src/lib.rs | 2 +- 4 files changed, 166 insertions(+), 87 deletions(-) diff --git a/partiql-eval/src/eval/expr/graph_match.rs b/partiql-eval/src/eval/expr/graph_match.rs index 2225175b..82e7f95b 100644 --- a/partiql-eval/src/eval/expr/graph_match.rs +++ b/partiql-eval/src/eval/expr/graph_match.rs @@ -103,7 +103,7 @@ mod tests { #[track_caller] fn test_graph(matcher: PathPatternMatch, expected: &'static str) { - let mut eval = EvalGraphMatch::new(matcher) + let eval = EvalGraphMatch::new(matcher) .bind::(vec![graph_reference()]) .expect("graph match bind"); diff --git a/partiql-eval/src/plan.rs b/partiql-eval/src/plan.rs index f29b9c82..22d70b85 100644 --- a/partiql-eval/src/plan.rs +++ b/partiql-eval/src/plan.rs @@ -1,14 +1,3 @@ -use itertools::{Either, Itertools}; -use partiql_logical as logical; -use partiql_logical::{ - AggFunc, BagOperator, BinaryOp, BindingsOp, CallName, GraphMatchExpr, GroupingStrategy, - IsTypeExpr, JoinKind, Lit, LogicalPlan, OpId, PathComponent, Pattern, PatternMatchExpr, - SearchedCase, SetQuantifier, SortSpecNullOrder, SortSpecOrder, Type, UnaryOp, ValueExpr, - VarRefType, -}; -use petgraph::prelude::StableGraph; -use std::collections::HashMap; - use crate::error::{ErrorNode, PlanErr, PlanningError}; use crate::eval; use crate::eval::evaluable::{ @@ -23,11 +12,22 @@ use crate::eval::expr::{ EvalLikeNonStringNonLiteralMatch, EvalListExpr, EvalLitExpr, EvalOpBinary, EvalOpUnary, EvalPath, EvalSearchedCaseExpr, EvalStringFn, EvalTrimFn, EvalTupleExpr, EvalVarRef, }; +use crate::eval::graph::string_graph::StringGraphTypes; use crate::eval::EvalPlan; +use itertools::{Either, Itertools}; use partiql_catalog::catalog::{Catalog, FunctionEntryFunction}; use partiql_extension_ion::boxed_ion::BoxedIonType; +use partiql_logical as logical; +use partiql_logical::{ + AggFunc, BagOperator, BinaryOp, BindingsOp, CallName, GraphMatchExpr, GroupingStrategy, + IsTypeExpr, JoinKind, Lit, LogicalPlan, OpId, PathComponent, Pattern, PatternMatchExpr, + SearchedCase, SetQuantifier, SortSpecNullOrder, SortSpecOrder, Type, UnaryOp, ValueExpr, + VarRefType, +}; use partiql_value::boxed_variant::DynBoxedVariantTypeFactory; use partiql_value::{Bag, List, Tuple, Value, Variant}; +use petgraph::prelude::StableGraph; +use std::collections::HashMap; #[macro_export] macro_rules! correct_num_args_or_err { @@ -494,13 +494,15 @@ impl<'c> EvaluatorPlanner<'c> { ("pattern expr", expr) } - ValueExpr::GraphMatch(GraphMatchExpr { value, pattern }) => { - //let pattern = plan_graph_match(pattern); - let pattern = todo!(); - - let expr = EvalGraphMatch::new(pattern).bind::<{ STRICT }>(plan_args(&[value])); - ("graphmatch expr", expr) - } + ValueExpr::GraphMatch(GraphMatchExpr { value, pattern }) => ( + "graphmatch expr", + match plan_graph_plan(pattern) { + Ok(pattern) => { + EvalGraphMatch::new(pattern).bind::<{ STRICT }>(plan_args(&[value])) + } + Err(e) => Ok(self.err(e) as Box), + }, + ), ValueExpr::SubQueryExpr(expr) => ( "subquery", Ok(Box::new(EvalSubQueryExpr::new( @@ -791,6 +793,122 @@ impl<'c> EvaluatorPlanner<'c> { } } +fn plan_graph_plan( + pattern: &partiql_logical::PathPatternMatch, +) -> Result, PlanningError> { + use eval::graph::plan as physical; + use partiql_logical as logical; + + fn plan_bind_spec( + pattern: &logical::BindSpec, + ) -> Result, PlanningError> { + Ok(physical::BindSpec(pattern.0.clone())) + } + + fn plan_label_filter( + pattern: &logical::LabelFilter, + ) -> Result, PlanningError> { + Ok(match pattern { + logical::LabelFilter::Always => physical::LabelFilter::Always, + logical::LabelFilter::Never => physical::LabelFilter::Never, + logical::LabelFilter::Named(n) => physical::LabelFilter::Named(n.clone()), + }) + } + + fn plan_value_filter( + pattern: &logical::ValueFilter, + ) -> Result { + Ok(match pattern { + logical::ValueFilter::Always => physical::ValueFilter::Always, + }) + } + + fn plan_node_filter( + pattern: &logical::NodeFilter, + ) -> Result, PlanningError> { + Ok(physical::NodeFilter { + label: plan_label_filter(&pattern.label)?, + filter: plan_value_filter(&pattern.filter)?, + }) + } + + fn plan_edge_filter( + pattern: &logical::EdgeFilter, + ) -> Result, PlanningError> { + Ok(physical::EdgeFilter { + label: plan_label_filter(&pattern.label)?, + filter: plan_value_filter(&pattern.filter)?, + }) + } + + fn plan_step_filter( + pattern: &logical::StepFilter, + ) -> Result, PlanningError> { + let dir = match pattern.dir { + logical::DirectionFilter::L => physical::DirectionFilter::L, + logical::DirectionFilter::R => physical::DirectionFilter::R, + logical::DirectionFilter::U => physical::DirectionFilter::U, + logical::DirectionFilter::LU => physical::DirectionFilter::LU, + logical::DirectionFilter::UR => physical::DirectionFilter::UR, + logical::DirectionFilter::LR => physical::DirectionFilter::LR, + logical::DirectionFilter::LUR => physical::DirectionFilter::LUR, + }; + Ok(physical::StepFilter { + dir, + triple: plan_triple_filter(&pattern.triple)?, + }) + } + + fn plan_triple_filter( + pattern: &logical::TripleFilter, + ) -> Result, PlanningError> { + Ok(physical::TripleFilter { + lhs: plan_node_filter(&pattern.lhs)?, + e: plan_edge_filter(&pattern.e)?, + rhs: plan_node_filter(&pattern.rhs)?, + }) + } + + fn plan_node_match( + pattern: &logical::NodeMatch, + ) -> Result, PlanningError> { + Ok(physical::NodeMatch { + binder: plan_bind_spec(&pattern.binder)?, + spec: plan_node_filter(&pattern.spec)?, + }) + } + + fn plan_path_match( + pattern: &logical::PathMatch, + ) -> Result, PlanningError> { + let (l, m, r) = &pattern.binders; + let binders = (plan_bind_spec(l)?, plan_bind_spec(m)?, plan_bind_spec(r)?); + Ok(physical::PathMatch { + binders, + spec: plan_step_filter(&pattern.spec)?, + }) + } + + fn plan_path_pattern_match( + pattern: &logical::PathPatternMatch, + ) -> Result, PlanningError> { + Ok(match pattern { + logical::PathPatternMatch::Node(n) => { + physical::PathPatternMatch::Node(plan_node_match(n)?) + } + logical::PathPatternMatch::Match(m) => { + physical::PathPatternMatch::Match(plan_path_match(m)?) + } + logical::PathPatternMatch::Concat(ms) => { + let ms: Result, _> = ms.iter().map(plan_path_pattern_match).collect(); + physical::PathPatternMatch::Concat(ms?) + } + }) + } + + plan_path_pattern_match(pattern) +} + fn plan_lit(lit: &Lit) -> Result { let lit_to_val = |lit| plan_lit(lit); Ok(match lit { diff --git a/partiql-logical/src/graph.rs b/partiql-logical/src/graph.rs index 16fcb99b..f0603d31 100644 --- a/partiql-logical/src/graph.rs +++ b/partiql-logical/src/graph.rs @@ -1,10 +1,11 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; use std::fmt::Debug; -use std::hash::Hash; /// A plan specification for an edge's direction filtering. -#[allow(dead_code)] // TODO remove once graph planning is implemented #[allow(clippy::upper_case_acronyms)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum DirectionFilter { L, // <- U, // ~ @@ -16,11 +17,13 @@ pub enum DirectionFilter { } /// A plan specification for bind names. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct BindSpec(pub String); /// A plan specification for label filtering. -#[derive(Debug, Clone, Default)] +#[derive(Default, Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum LabelFilter { #[default] Always, @@ -29,7 +32,8 @@ pub enum LabelFilter { } /// A plan specification for value filtering. -#[derive(Debug, Clone, Copy, Default)] +#[derive(Default, Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ValueFilter { #[default] Always, @@ -37,21 +41,24 @@ pub enum ValueFilter { } /// A plan specification for node label & value filtering. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct NodeFilter { pub label: LabelFilter, pub filter: ValueFilter, } /// A plan specification for edge label & value filtering. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct EdgeFilter { pub label: LabelFilter, pub filter: ValueFilter, } /// A plan specification for triple (node, edge, node) matching. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TripleFilter { pub lhs: NodeFilter, pub e: EdgeFilter, @@ -59,96 +66,50 @@ pub struct TripleFilter { } /// A plan specification for 'step' (triple + edge direction) matching. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct StepFilter { pub dir: DirectionFilter, pub triple: TripleFilter, } /// A plan specification for 'path patterns' (i.e., sequences of 'node edge node's) matching. -#[allow(dead_code)] // TODO remove once graph planning is implemented -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PathPatternFilter { pub head: NodeFilter, pub tail: Vec<(DirectionFilter, EdgeFilter, NodeFilter)>, } /// A plan specification for node matching. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct NodeMatch { pub binder: BindSpec, pub spec: NodeFilter, } /// A plan specification for edge matching. -#[allow(dead_code)] // TODO remove once graph planning is implemented -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct EdgeMatch { pub binder: BindSpec, pub spec: EdgeFilter, } /// A plan specification for path (i.e., node, edge, node) matching. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PathMatch { pub binders: (BindSpec, BindSpec, BindSpec), pub spec: StepFilter, } /// A plan specification for path patterns (i.e., sequences of [`PathMatch`]s) matching. -#[allow(dead_code)] // TODO remove once graph planning is implemented -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum PathPatternMatch { Node(NodeMatch), Match(PathMatch), Concat(Vec), } - -impl From for PathPatternMatch { - fn from(value: PathMatch) -> Self { - Self::Match(value) - } -} - -impl From for PathPatternMatch { - fn from(value: NodeMatch) -> Self { - Self::Node(value) - } -} - -#[allow(dead_code)] // TODO remove once graph planning is implemented -pub trait ElementFilterBuilder { - fn any() -> Self; - fn labeled(label: String) -> Self; -} - -impl ElementFilterBuilder for NodeFilter { - fn any() -> Self { - Self { - label: LabelFilter::Always, - filter: ValueFilter::Always, - } - } - - fn labeled(label: String) -> Self { - Self { - label: LabelFilter::Named(label), - filter: ValueFilter::Always, - } - } -} - -impl ElementFilterBuilder for EdgeFilter { - fn any() -> Self { - Self { - label: LabelFilter::Always, - filter: ValueFilter::Always, - } - } - fn labeled(label: String) -> Self { - Self { - label: LabelFilter::Named(label), - filter: ValueFilter::Always, - } - } -} diff --git a/partiql-logical/src/lib.rs b/partiql-logical/src/lib.rs index d122a1fb..3e44bae7 100644 --- a/partiql-logical/src/lib.rs +++ b/partiql-logical/src/lib.rs @@ -609,7 +609,7 @@ pub struct LikeNonStringNonLiteralMatch { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct GraphMatchExpr { pub value: Box, - pub pattern: GraphMatchPattern, + pub pattern: PathPatternMatch, } #[derive(Debug, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] From 2bf9bbfd1c2ce759a667879cbdbd0a689193ce65 Mon Sep 17 00:00:00 2001 From: Josh Pschorr Date: Wed, 26 Mar 2025 09:16:34 -0700 Subject: [PATCH 3/3] Update partiql-types/src/lib.rs --- partiql-types/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/partiql-types/src/lib.rs b/partiql-types/src/lib.rs index 15cb7740..76baf8a9 100644 --- a/partiql-types/src/lib.rs +++ b/partiql-types/src/lib.rs @@ -764,7 +764,9 @@ pub enum Static { Bag(BagType), Array(ArrayType), - Graph(/* TODO? */), + Graph( + /* TODO: https://github.com/partiql/partiql-lang/blob/main/RFCs/0025-graph-data-model.md */ + ), // TODO Add BitString, ByteString, Blob, Clob, and Graph types }