use crate::planning::plan::PlanNodeEnum;
macro_rules! define_matcher {
($($method:ident => $name:expr),+ $(,)?) => {
$(
pub fn $method() -> Self {
Self::new_with_name($name)
}
)+
};
}
#[derive(Debug, Clone, Default)]
pub struct Pattern {
pub node: Option<MatchNode>,
pub dependencies: Vec<Pattern>,
}
impl Pattern {
pub fn new() -> Self {
Self::default()
}
pub fn with_node(node: MatchNode) -> Self {
Self {
node: Some(node),
dependencies: Vec::new(),
}
}
pub fn new_with_name(name: &'static str) -> Self {
Self::with_node(MatchNode::Single(name))
}
pub fn multi(node_names: Vec<&'static str>) -> Self {
Self::with_node(MatchNode::Multi(node_names))
}
pub fn with_dependency(mut self, dependency: Pattern) -> Self {
self.dependencies.push(dependency);
self
}
pub fn with_dependency_name(mut self, name: &'static str) -> Self {
self.dependencies.push(Self::new_with_name(name));
self
}
pub fn add_dependency(&mut self, dependency: Pattern) {
self.dependencies.push(dependency);
}
pub fn matches(&self, plan_node: &PlanNodeEnum) -> bool {
if let Some(ref node) = self.node {
if !node.matches(plan_node.name()) {
return false;
}
}
if self.dependencies.is_empty() {
return true;
}
let dep_names: Vec<&str> = self
.dependencies
.iter()
.filter_map(|d| d.node.as_ref())
.filter_map(|n| n.as_single())
.collect();
if dep_names.is_empty() {
return true;
}
let deps = plan_node.dependencies_ref();
for dep_pattern in &self.dependencies {
let dep_matches = deps.iter().any(|input| dep_pattern.matches(input));
if !dep_matches {
return false;
}
}
true
}
define_matcher! {
with_project_matcher => "Project",
with_filter_matcher => "Filter",
with_scan_vertices_matcher => "ScanVertices",
with_get_vertices_matcher => "GetVertices",
with_limit_matcher => "Limit",
with_sort_matcher => "Sort",
with_aggregate_matcher => "Aggregate",
with_dedup_matcher => "Dedup",
with_get_neighbors_matcher => "GetNeighbors",
with_traverse_matcher => "Traverse",
}
pub fn with_join_matcher() -> Self {
Self::multi(vec![
"InnerJoin",
"LeftJoin",
"InnerJoin",
"LeftJoin",
"CrossJoin",
"FullOuterJoin",
])
}
}
#[derive(Debug, Clone)]
pub enum MatchNode {
Single(&'static str),
Multi(Vec<&'static str>),
Any,
}
impl MatchNode {
pub fn matches(&self, node_name: &str) -> bool {
match self {
MatchNode::Single(name) => *name == node_name,
MatchNode::Multi(names) => names.contains(&node_name),
MatchNode::Any => true,
}
}
pub fn as_single(&self) -> Option<&'static str> {
match self {
MatchNode::Single(name) => Some(name),
_ => None,
}
}
pub fn as_multi(&self) -> Option<&Vec<&'static str>> {
match self {
MatchNode::Multi(names) => Some(names),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub enum PlanNodeMatcher {
MatchNode(&'static str),
Not(Box<PlanNodeMatcher>),
And(Vec<PlanNodeMatcher>),
Or(Vec<PlanNodeMatcher>),
}
impl PlanNodeMatcher {
pub fn matches(&self, plan_node: &PlanNodeEnum) -> bool {
match self {
PlanNodeMatcher::MatchNode(name) => plan_node.name() == *name,
PlanNodeMatcher::Not(matcher) => !matcher.matches(plan_node),
PlanNodeMatcher::And(matchers) => matchers.iter().all(|m| m.matches(plan_node)),
PlanNodeMatcher::Or(matchers) => matchers.iter().any(|m| m.matches(plan_node)),
}
}
pub fn and(self, other: PlanNodeMatcher) -> Self {
PlanNodeMatcher::And(vec![self, other])
}
pub fn or(self, other: PlanNodeMatcher) -> Self {
PlanNodeMatcher::Or(vec![self, other])
}
}
pub trait PatternBuilder {
fn build(&self) -> Pattern;
}
pub trait NodeVisitor {
fn visit(&mut self, node: &PlanNodeEnum) -> bool;
}
#[derive(Debug, Default)]
pub struct NodeVisitorRecorder {
pub nodes: Vec<PlanNodeEnum>,
}
impl NodeVisitorRecorder {
pub fn new() -> Self {
Self::default()
}
pub fn record(&mut self, node: &PlanNodeEnum) {
self.nodes.push(node.clone());
}
}
impl NodeVisitor for NodeVisitorRecorder {
fn visit(&mut self, node: &PlanNodeEnum) -> bool {
self.record(node);
true
}
}
#[derive(Debug)]
pub struct NodeVisitorFinder {
pub target_name: String,
pub found_node: Option<PlanNodeEnum>,
}
impl NodeVisitorFinder {
pub fn new(target_name: &str) -> Self {
Self {
target_name: target_name.to_string(),
found_node: None,
}
}
}
impl NodeVisitor for NodeVisitorFinder {
fn visit(&mut self, node: &PlanNodeEnum) -> bool {
if node.name() == self.target_name {
self.found_node = Some(node.clone());
return false; }
true
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::planning::plan::core::nodes::access::graph_scan_node::ScanVerticesNode;
use crate::planning::plan::core::nodes::operation::filter_node::FilterNode;
use crate::planning::plan::core::nodes::operation::project_node::ProjectNode;
use linkrs_core::types::expr::expression_context::ExpressionAnalysisContext;
use linkrs_core::types::ContextualExpression;
use linkrs_core::Expression;
use linkrs_core::Value;
use std::sync::Arc;
#[test]
fn test_pattern_matches() {
let pattern = Pattern::new_with_name("Project");
let input_node = PlanNodeEnum::ScanVertices(ScanVerticesNode::new(1, "default"));
let ctx = Arc::new(ExpressionAnalysisContext::new());
let proj_meta =
linkrs_core::types::expr::ExpressionMeta::new(Expression::Variable("a".to_string()));
let proj_id = ctx.register_expression(proj_meta);
let proj_expr = ContextualExpression::new(proj_id, ctx.clone());
let project_node = PlanNodeEnum::Project(
ProjectNode::new(
input_node.clone(),
vec![linkrs_core::YieldColumn::new(proj_expr, "a".to_string())],
)
.expect("Creating the ProjectNode should succeed"),
);
let expr_meta =
linkrs_core::types::expr::ExpressionMeta::new(Expression::Literal(Value::Bool(true)));
let id = ctx.register_expression(expr_meta);
let ctx_expr = ContextualExpression::new(id, ctx);
let filter_node = PlanNodeEnum::Filter(
FilterNode::new(input_node, ctx_expr).expect("Creating the FilterNode should succeed"),
);
assert!(pattern.matches(&project_node));
assert!(!pattern.matches(&filter_node));
}
#[test]
fn test_match_node_single() {
let matcher = MatchNode::Single("Project");
assert!(matcher.matches("Project"));
assert!(!matcher.matches("Filter"));
}
#[test]
fn test_match_node_multi() {
let matcher = MatchNode::Multi(vec!["Project", "Filter"]);
assert!(matcher.matches("Project"));
assert!(matcher.matches("Filter"));
assert!(!matcher.matches("ScanVertices"));
}
#[test]
fn test_match_node_any() {
let matcher = MatchNode::Any;
assert!(matcher.matches("Project"));
assert!(matcher.matches("Filter"));
assert!(matcher.matches("ScanVertices"));
}
#[test]
fn test_pattern_with_dependency() {
let pattern = Pattern::new_with_name("Filter").with_dependency_name("Project");
let scan = PlanNodeEnum::ScanVertices(ScanVerticesNode::new(1, "default"));
let ctx = Arc::new(ExpressionAnalysisContext::new());
let proj_meta =
linkrs_core::types::expr::ExpressionMeta::new(Expression::Variable("a".to_string()));
let proj_id = ctx.register_expression(proj_meta);
let proj_expr = ContextualExpression::new(proj_id, ctx.clone());
let project = PlanNodeEnum::Project(
ProjectNode::new(
scan.clone(),
vec![linkrs_core::YieldColumn::new(proj_expr, "a".to_string())],
)
.expect("Creating the ProjectNode should succeed"),
);
let expr_meta =
linkrs_core::types::expr::ExpressionMeta::new(Expression::Literal(Value::Bool(true)));
let id = ctx.register_expression(expr_meta);
let ctx_expr = ContextualExpression::new(id, ctx.clone());
let filter = PlanNodeEnum::Filter(
FilterNode::new(project.clone(), ctx_expr)
.expect("Creating the FilterNode should succeed"),
);
assert!(pattern.matches(&filter));
let expr_meta2 =
linkrs_core::types::expr::ExpressionMeta::new(Expression::Literal(Value::Bool(true)));
let id2 = ctx.register_expression(expr_meta2);
let ctx_expr2 = ContextualExpression::new(id2, ctx);
let filter2 = PlanNodeEnum::Filter(
FilterNode::new(scan, ctx_expr2).expect("Creating the FilterNode should succeed"),
);
assert!(!pattern.matches(&filter2));
}
}