Skip to main content

linkrs_query/planning/statements/pattern_planner/
node.rs

1//! Node pattern planning.
2
3use std::sync::Arc;
4
5use crate::metadata::MetadataContext;
6use crate::parser::ast::pattern::NodePattern;
7use crate::planning::plan::core::nodes::base::plan_node_traits::PlanNode;
8use crate::planning::plan::core::nodes::operation::filter_node::FilterNode;
9use crate::planning::plan::core::nodes::ScanVerticesNode;
10use crate::planning::plan::SubPlan;
11use crate::planning::planner::PlannerError;
12use crate::planning::statements::{expression_helpers, index_scan_planner};
13use linkrs_core::types::expr::expression_context::ExpressionAnalysisContext;
14use linkrs_core::types::expr::ContextualExpression;
15
16use super::logical_mirror::{logical_filter, logical_scan_vertices};
17
18pub fn plan_pattern_node(
19    node: &NodePattern,
20    space_id: u64,
21    space_name: &str,
22    enable_index_optimization: bool,
23    metadata_context: &Option<MetadataContext>,
24    expr_context: &Option<Arc<ExpressionAnalysisContext>>,
25    where_expression: Option<&ContextualExpression>,
26) -> Result<SubPlan, PlannerError> {
27    let var_name = node.variable.clone().unwrap_or_else(|| "n".to_string());
28
29    if enable_index_optimization {
30        if let Some(index_plan) = index_scan_planner::try_create_index_scan_plan(
31            node,
32            space_id,
33            space_name,
34            &var_name,
35            enable_index_optimization,
36            metadata_context.as_ref(),
37            where_expression,
38        )? {
39            // Index scans are a physical choice; the logical mirror is a
40            // tagged vertex scan (matching the physical→logical converter).
41            let logical_root = logical_scan_vertices(
42                space_id,
43                space_name,
44                node.labels.first().map(|s| s.as_str()),
45                &var_name,
46            );
47            return Ok(SubPlan {
48                root: index_plan.root,
49                tail: index_plan.tail,
50                logical_root: Some(logical_root),
51            });
52        }
53    }
54
55    // Single label execution scans the first label only; the full label
56    // filter below guarantees distinct multi label patterns match nothing.
57    let mut scan_node = ScanVerticesNode::new(space_id, space_name);
58    scan_node.set_col_names(vec![var_name.clone()]);
59    scan_node.set_output_var(var_name.clone());
60    if let Some(label) = node.labels.first() {
61        scan_node.set_tag(label);
62    }
63    let scan_root = scan_node.into_enum();
64    let mut logical_root = logical_scan_vertices(
65        space_id,
66        space_name,
67        node.labels.first().map(|s| s.as_str()),
68        &var_name,
69    );
70    let mut plan = SubPlan {
71        root: Some(scan_root.clone()),
72        tail: Some(scan_root),
73        logical_root: Some(logical_root.clone()),
74    };
75
76    if !node.labels.is_empty() {
77        let expr_ctx = expr_context.as_ref().expect("expr_context should be set");
78        let label_filter = expression_helpers::build_label_filter_expression(
79            &node.variable,
80            &node.labels,
81            expr_ctx,
82        );
83        let root_node = plan.root.as_ref().expect("The root of plan should exist");
84        let filter_node = FilterNode::new(root_node.clone(), label_filter.clone())
85            .map_err(|e| PlannerError::PlanGenerationFailed(e.to_string()))?;
86        logical_root = logical_filter(logical_root, label_filter);
87        plan = SubPlan {
88            root: Some(filter_node.into_enum()),
89            tail: plan.tail,
90            logical_root: Some(logical_root.clone()),
91        };
92    }
93
94    if let Some(ref props) = node.properties {
95        let filter_expr = if let Some(ref expr_ctx) = expr_context {
96            expression_helpers::convert_properties_to_filter(&var_name, props, expr_ctx)
97        } else {
98            None
99        };
100
101        let filter_expr = match filter_expr {
102            Some(expr) => expr,
103            None => props.clone(),
104        };
105
106        let filter_node = FilterNode::new(
107            plan.root
108                .as_ref()
109                .expect("The root of plan should exist")
110                .clone(),
111            filter_expr.clone(),
112        )
113        .map_err(|e| PlannerError::PlanGenerationFailed(e.to_string()))?;
114        logical_root = logical_filter(logical_root, filter_expr);
115        plan = SubPlan {
116            root: Some(filter_node.into_enum()),
117            tail: plan.tail,
118            logical_root: Some(logical_root.clone()),
119        };
120    }
121
122    if !node.predicates.is_empty() {
123        for pred in &node.predicates {
124            let filter_node = FilterNode::new(
125                plan.root
126                    .as_ref()
127                    .expect("The root of plan should exist")
128                    .clone(),
129                pred.clone(),
130            )
131            .map_err(|e| PlannerError::PlanGenerationFailed(e.to_string()))?;
132            logical_root = logical_filter(logical_root, pred.clone());
133            plan = SubPlan {
134                root: Some(filter_node.into_enum()),
135                tail: plan.tail,
136                logical_root: Some(logical_root.clone()),
137            };
138        }
139    }
140
141    Ok(plan)
142}
143pub fn plan_node_pattern(space_id: u64, space_name: &str) -> Result<SubPlan, PlannerError> {
144    let scan_node = ScanVerticesNode::new(space_id, space_name);
145    let scan_root = scan_node.into_enum();
146    let logical_root = logical_scan_vertices(space_id, space_name, None, "n");
147    Ok(SubPlan {
148        root: Some(scan_root.clone()),
149        tail: Some(scan_root),
150        logical_root: Some(logical_root),
151    })
152}