Skip to content

Add lowering from AST to logical plan for graph MATCH #551

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/ci_build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ jobs:
# to format and use the output.
- name: Cargo Test of the conformance tests (can fail) and save to json file
continue-on-error: true
run: cargo test --verbose --package partiql-conformance-tests --features "conformance_test" --release -- -Z unstable-options --format json > ${{ env.CARGO_TEST_RESULT_NAME }}
run: cargo test --verbose --package partiql-conformance-tests --features "conformance_test, experimental" --release -- -Z unstable-options --format json > ${{ env.CARGO_TEST_RESULT_NAME }}
# Create a conformance report from the `cargo test` json file
- run: cargo run --features report_tool --bin generate_cts_report ${{ env.CARGO_TEST_RESULT_NAME }} ${GITHUB_SHA} ${{ env.CONFORMANCE_REPORT_NAME }}
# Upload conformance report for comparison with future runs
Expand Down
1 change: 1 addition & 0 deletions partiql-ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,7 @@ pub struct GraphMatchEdge {
#[derive(Visit, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GraphMatchPattern {
/// an optional restrictor for the entire pattern match
#[visit(skip)]
pub restrictor: Option<GraphMatchRestrictor>,
/// an optional quantifier for the entire pattern match
Expand Down
56 changes: 52 additions & 4 deletions partiql-eval/src/eval/expr/graph_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,67 @@ impl BindEvalExpr for EvalGraphMatch {
#[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,
BindSpec, DirectionFilter, EdgeFilter, LabelFilter, NodeFilter, NodeMatch, PathMatch,
PathPatternMatch, StepFilter, TripleFilter, ValueFilter,
};
use crate::eval::graph::string_graph::StringGraphTypes;
use crate::eval::graph::types::GraphTypes;
use crate::eval::{BasicContext, MapBindings};
use crate::test_value::TestValue;
use partiql_catalog::context::SystemContext;
use partiql_common::pretty::ToPretty;

use partiql_logical::graph::bind_name::FreshBinder;
use partiql_value::{tuple, BindingsName, DateTime, Value};

impl<GT: GraphTypes> From<PathMatch<GT>> for PathPatternMatch<GT> {
fn from(value: PathMatch<GT>) -> Self {
Self::Match(value)
}
}

impl<GT: GraphTypes> From<NodeMatch<GT>> for PathPatternMatch<GT> {
fn from(value: NodeMatch<GT>) -> Self {
Self::Node(value)
}
}

pub trait ElementFilterBuilder<GT: GraphTypes> {
fn any() -> Self;
fn labeled(label: GT::Label) -> Self;
}

impl<GT: GraphTypes> ElementFilterBuilder<GT> for NodeFilter<GT> {
fn any() -> Self {
Self {
label: LabelFilter::Always,
filter: ValueFilter::Always,
}
}

fn labeled(label: GT::Label) -> Self {
Self {
label: LabelFilter::Named(label),
filter: ValueFilter::Always,
}
}
}

impl<GT: GraphTypes> ElementFilterBuilder<GT> for EdgeFilter<GT> {
fn any() -> Self {
Self {
label: LabelFilter::Always,
filter: ValueFilter::Always,
}
}
fn labeled(label: GT::Label) -> Self {
Self {
label: LabelFilter::Named(label),
filter: ValueFilter::Always,
}
}
}

/*
A simple 3-node, 3-edge graph which is intended to be able to be exactly matched by:
```(graph MATCH
Expand Down
19 changes: 16 additions & 3 deletions partiql-eval/src/eval/graph/evaluator.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::eval::graph::bind_name::BindNameExt;
use crate::eval::graph::engine::GraphEngine;
use crate::eval::graph::result::{
GraphElement, NodeBinding, PathBinding, PathPatternBinding, PathPatternNodes,
GraphElement, NodeBinding, PathBinding, PathPatternBinding, PathPatternNodes, Triple,
};
use partiql_logical::graph::bind_name::BindNameExt;

use fxhash::FxBuildHasher;
use indexmap::IndexMap;
Expand Down Expand Up @@ -92,7 +92,20 @@ impl<GT: GraphTypes, G: GraphEngine<GT>> GraphEvaluator<GT, G> {
fn eval_path_pattern(&self, matcher: PathPatternMatch<GT>) -> PathPatternBinding<GT> {
match matcher {
PathPatternMatch::Node(n) => self.eval_node(n).into(),
PathPatternMatch::Match(m) => self.eval_path(m).into(),
PathPatternMatch::Match(m) => {
let PathBinding {
matcher,
mut bindings,
} = self.eval_path(m);

// if edge is cyclic, filter triples
let (n1, _, n2) = &matcher.binders;
if n1 == n2 {
bindings.retain(|Triple { lhs, e: _, rhs }| lhs == rhs)
}

PathBinding { matcher, bindings }.into()
}
PathPatternMatch::Concat(ms) => ms
.into_iter()
.map(|p| self.eval_path_pattern(p))
Expand Down
1 change: 0 additions & 1 deletion partiql-eval/src/eval/graph/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub(crate) mod bind_name;
pub(crate) mod engine;
pub(crate) mod evaluator;
pub(crate) mod plan;
Expand Down
74 changes: 0 additions & 74 deletions partiql-eval/src/eval/graph/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ 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 {
Expand Down Expand Up @@ -66,29 +65,13 @@ pub struct StepFilter<GT: GraphTypes> {
pub triple: TripleFilter<GT>,
}

/// 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<GT: GraphTypes> {
pub head: NodeFilter<GT>,
pub tail: Vec<(DirectionFilter, EdgeFilter<GT>, NodeFilter<GT>)>,
}

/// A plan specification for node matching.
#[derive(Debug, Clone)]
pub struct NodeMatch<GT: GraphTypes> {
pub binder: BindSpec<GT>,
pub spec: NodeFilter<GT>,
}

/// A plan specification for edge matching.
#[allow(dead_code)] // TODO remove once graph planning is implemented
#[derive(Debug, Clone)]
pub struct EdgeMatch<GT: GraphTypes> {
pub binder: BindSpec<GT>,
pub spec: EdgeFilter<GT>,
}

/// A plan specification for path (i.e., node, edge, node) matching.
#[derive(Debug, Clone)]
pub struct PathMatch<GT: GraphTypes> {
Expand All @@ -97,63 +80,13 @@ pub struct PathMatch<GT: GraphTypes> {
}

/// 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<GT: GraphTypes> {
Node(NodeMatch<GT>),
Match(PathMatch<GT>),
Concat(Vec<PathPatternMatch<GT>>),
}

impl<GT: GraphTypes> From<PathMatch<GT>> for PathPatternMatch<GT> {
fn from(value: PathMatch<GT>) -> Self {
Self::Match(value)
}
}

impl<GT: GraphTypes> From<NodeMatch<GT>> for PathPatternMatch<GT> {
fn from(value: NodeMatch<GT>) -> Self {
Self::Node(value)
}
}

#[allow(dead_code)] // TODO remove once graph planning is implemented
pub trait ElementFilterBuilder<GT: GraphTypes> {
fn any() -> Self;
fn labeled(label: GT::Label) -> Self;
}

impl<GT: GraphTypes> ElementFilterBuilder<GT> for NodeFilter<GT> {
fn any() -> Self {
Self {
label: LabelFilter::Always,
filter: ValueFilter::Always,
}
}

fn labeled(label: GT::Label) -> Self {
Self {
label: LabelFilter::Named(label),
filter: ValueFilter::Always,
}
}
}

impl<GT: GraphTypes> ElementFilterBuilder<GT> for EdgeFilter<GT> {
fn any() -> Self {
Self {
label: LabelFilter::Always,
filter: ValueFilter::Always,
}
}
fn labeled(label: GT::Label) -> Self {
Self {
label: LabelFilter::Named(label),
filter: ValueFilter::Always,
}
}
}

/// A trait for converting between plans parameterized by different [`GraphTypes`]
pub trait GraphPlanConvert<In: GraphTypes, Out: GraphTypes>: Debug {
fn convert_pathpattern_match(&self, matcher: &PathPatternMatch<In>) -> PathPatternMatch<Out> {
Expand Down Expand Up @@ -211,13 +144,6 @@ pub trait GraphPlanConvert<In: GraphTypes, Out: GraphTypes>: Debug {
}
}

#[allow(dead_code)] // TODO remove once graph planning is implemented
fn convert_edge_match(&self, edge: &EdgeMatch<In>) -> EdgeMatch<Out> {
EdgeMatch {
binder: self.convert_binder(&edge.binder),
spec: self.convert_edge_filter(&edge.spec),
}
}
fn convert_label_filter(&self, node: &LabelFilter<In>) -> LabelFilter<Out>;
fn convert_binder(&self, binder: &BindSpec<In>) -> BindSpec<Out>;
}
50 changes: 25 additions & 25 deletions partiql-eval/src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -794,37 +794,37 @@ impl<'c> EvaluatorPlanner<'c> {
}

fn plan_graph_plan(
pattern: &partiql_logical::PathPatternMatch,
pattern: &logical::graph::PathPatternMatch,
) -> Result<eval::graph::plan::PathPatternMatch<StringGraphTypes>, PlanningError> {
use eval::graph::plan as physical;
use partiql_logical as logical;

fn plan_bind_spec(
pattern: &logical::BindSpec,
pattern: &logical::graph::BindSpec,
) -> Result<physical::BindSpec<StringGraphTypes>, PlanningError> {
Ok(physical::BindSpec(pattern.0.clone()))
}

fn plan_label_filter(
pattern: &logical::LabelFilter,
pattern: &logical::graph::LabelFilter,
) -> Result<physical::LabelFilter<StringGraphTypes>, 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()),
logical::graph::LabelFilter::Always => physical::LabelFilter::Always,
logical::graph::LabelFilter::Never => physical::LabelFilter::Never,
logical::graph::LabelFilter::Named(n) => physical::LabelFilter::Named(n.clone()),
})
}

fn plan_value_filter(
pattern: &logical::ValueFilter,
pattern: &logical::graph::ValueFilter,
) -> Result<physical::ValueFilter, PlanningError> {
Ok(match pattern {
logical::ValueFilter::Always => physical::ValueFilter::Always,
logical::graph::ValueFilter::Always => physical::ValueFilter::Always,
})
}

fn plan_node_filter(
pattern: &logical::NodeFilter,
pattern: &logical::graph::NodeFilter,
) -> Result<physical::NodeFilter<StringGraphTypes>, PlanningError> {
Ok(physical::NodeFilter {
label: plan_label_filter(&pattern.label)?,
Expand All @@ -833,7 +833,7 @@ fn plan_graph_plan(
}

fn plan_edge_filter(
pattern: &logical::EdgeFilter,
pattern: &logical::graph::EdgeFilter,
) -> Result<physical::EdgeFilter<StringGraphTypes>, PlanningError> {
Ok(physical::EdgeFilter {
label: plan_label_filter(&pattern.label)?,
Expand All @@ -842,16 +842,16 @@ fn plan_graph_plan(
}

fn plan_step_filter(
pattern: &logical::StepFilter,
pattern: &logical::graph::StepFilter,
) -> Result<physical::StepFilter<StringGraphTypes>, 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,
logical::graph::DirectionFilter::L => physical::DirectionFilter::L,
logical::graph::DirectionFilter::R => physical::DirectionFilter::R,
logical::graph::DirectionFilter::U => physical::DirectionFilter::U,
logical::graph::DirectionFilter::LU => physical::DirectionFilter::LU,
logical::graph::DirectionFilter::UR => physical::DirectionFilter::UR,
logical::graph::DirectionFilter::LR => physical::DirectionFilter::LR,
logical::graph::DirectionFilter::LUR => physical::DirectionFilter::LUR,
};
Ok(physical::StepFilter {
dir,
Expand All @@ -860,7 +860,7 @@ fn plan_graph_plan(
}

fn plan_triple_filter(
pattern: &logical::TripleFilter,
pattern: &logical::graph::TripleFilter,
) -> Result<physical::TripleFilter<StringGraphTypes>, PlanningError> {
Ok(physical::TripleFilter {
lhs: plan_node_filter(&pattern.lhs)?,
Expand All @@ -870,7 +870,7 @@ fn plan_graph_plan(
}

fn plan_node_match(
pattern: &logical::NodeMatch,
pattern: &logical::graph::NodeMatch,
) -> Result<physical::NodeMatch<StringGraphTypes>, PlanningError> {
Ok(physical::NodeMatch {
binder: plan_bind_spec(&pattern.binder)?,
Expand All @@ -879,7 +879,7 @@ fn plan_graph_plan(
}

fn plan_path_match(
pattern: &logical::PathMatch,
pattern: &logical::graph::PathMatch,
) -> Result<physical::PathMatch<StringGraphTypes>, PlanningError> {
let (l, m, r) = &pattern.binders;
let binders = (plan_bind_spec(l)?, plan_bind_spec(m)?, plan_bind_spec(r)?);
Expand All @@ -890,16 +890,16 @@ fn plan_graph_plan(
}

fn plan_path_pattern_match(
pattern: &logical::PathPatternMatch,
pattern: &logical::graph::PathPatternMatch,
) -> Result<physical::PathPatternMatch<StringGraphTypes>, PlanningError> {
Ok(match pattern {
logical::PathPatternMatch::Node(n) => {
logical::graph::PathPatternMatch::Node(n) => {
physical::PathPatternMatch::Node(plan_node_match(n)?)
}
logical::PathPatternMatch::Match(m) => {
logical::graph::PathPatternMatch::Match(m) => {
physical::PathPatternMatch::Match(plan_path_match(m)?)
}
logical::PathPatternMatch::Concat(ms) => {
logical::graph::PathPatternMatch::Concat(ms) => {
let ms: Result<Vec<_>, _> = ms.iter().map(plan_path_pattern_match).collect();
physical::PathPatternMatch::Concat(ms?)
}
Expand Down
Loading
Loading