Skip to main content

linkrs_query/planning/statements/dml/
insert_planner.rs

1//! Insert Operation Planner
2//!
3//! Query planning for INSERT VERTEX and INSERT EDGE statements
4
5use crate::parser::ast::{InsertStmt, InsertTarget, Stmt, VertexRow};
6use crate::planning::plan::core::{
7    node_id_generator::next_node_id,
8    nodes::{EdgeInsertInfo, TagInsertSpec, VertexInsertInfo},
9};
10use crate::planning::plan::logical::logical_nodes::dml::{
11    LogicalInsertEdgesNode, LogicalInsertVerticesNode,
12};
13use crate::planning::plan::logical::LogicalNodeEnum;
14use crate::planning::plan::SubPlan;
15use crate::planning::planner::{Planner, PlannerError, ValidatedStatement};
16use crate::planning::statements::clauses::exists_planner;
17use crate::QueryContext;
18use linkrs_core::types::expr::contextual::ContextualExpression;
19use std::sync::Arc;
20
21#[cfg(test)]
22use crate::parser::ast::utils::ExprFactory;
23#[cfg(test)]
24use linkrs_core::types::expr::expression_context::ExpressionAnalysisContext;
25#[cfg(test)]
26use linkrs_core::YieldColumn;
27
28/// Insert Operation Planner
29/// Responsible for converting INSERT statements into execution plans.
30#[derive(Debug, Clone)]
31pub struct InsertPlanner;
32
33impl InsertPlanner {
34    /// Create a new insertion planner.
35    pub fn new() -> Self {
36        Self
37    }
38
39    /// Check whether the statements match the insertion operations.
40    pub fn match_stmt(stmt: &Stmt) -> bool {
41        matches!(stmt, Stmt::Insert(_))
42    }
43
44    /// Extract the InsertStmt from the Stmt.
45    fn extract_insert_stmt(&self, stmt: &Stmt) -> Result<InsertStmt, PlannerError> {
46        match stmt {
47            Stmt::Insert(insert_stmt) => Ok(insert_stmt.clone()),
48            _ => Err(PlannerError::PlanGenerationFailed(
49                "statement is not an INSERT statement".to_string(),
50            )),
51        }
52    }
53
54    /// Constructing vertex insertion information.
55    ///
56    /// The parser carries exactly one tag per vertex; storage enforces a
57    /// single tag per vertex.
58    fn build_vertex_insert_info(
59        &self,
60        space_name: String,
61        tag: crate::parser::ast::TagInsertSpec,
62        values: Vec<VertexRow>,
63        if_not_exists: bool,
64    ) -> Result<VertexInsertInfo, PlannerError> {
65        let tag_spec = TagInsertSpec {
66            tag_name: tag.tag_name,
67            prop_names: tag.prop_names,
68        };
69
70        // Convert `VertexRow` to `(vid, Vec<Expression>)` pairs.
71        let converted_values: Vec<(ContextualExpression, Vec<ContextualExpression>)> = values
72            .into_iter()
73            .map(|row| (row.vid, row.values))
74            .collect();
75
76        Ok(VertexInsertInfo {
77            space_name,
78            tag: tag_spec,
79            values: converted_values,
80            if_not_exists,
81        })
82    }
83
84    /// Constructing edge insertion information
85    fn build_edge_insert_info(
86        &self,
87        space_name: String,
88        edge_name: String,
89        prop_names: Vec<String>,
90        edges: Vec<(
91            ContextualExpression,
92            ContextualExpression,
93            Option<ContextualExpression>,
94            Vec<ContextualExpression>,
95        )>,
96        if_not_exists: bool,
97    ) -> EdgeInsertInfo {
98        EdgeInsertInfo {
99            space_name,
100            edge_name,
101            prop_names,
102            edges,
103            if_not_exists,
104        }
105    }
106
107    /// Create columns for projecting the insertion results.
108    #[cfg(test)]
109    fn create_yield_columns(
110        &self,
111        count: usize,
112        expr_context: &Arc<ExpressionAnalysisContext>,
113    ) -> Vec<YieldColumn> {
114        let expr = ExprFactory::constant(
115            linkrs_core::Value::BigInt(count as i64),
116            expr_context.clone(),
117        );
118        vec![YieldColumn::new(expr, "inserted_count".to_string())]
119    }
120}
121
122impl Planner for InsertPlanner {
123    fn plan_bound(
124        &mut self,
125        ctx: &crate::planning::context::PlanContext<'_>,
126    ) -> Result<SubPlan, PlannerError> {
127        use crate::binder::bound::{BoundExpression, BoundInsertTarget};
128
129        let bound = ctx.bound;
130        let qctx = ctx.qctx.clone();
131        let validated = ctx.validated;
132        let insert = match bound {
133            crate::binder::BoundStatement::Insert(i) => i,
134            _ => {
135                return Err(PlannerError::PlanGenerationFailed(
136                    "statement is not an INSERT statement".to_string(),
137                ));
138            }
139        };
140
141        let space_name = qctx.space_name().unwrap_or_else(|| "default".to_string());
142        // Bound expressions are converted back into contextual expressions so
143        // downstream plan nodes keep the same shape as the AST path. The
144        // validated expression context is still required as the id registry.
145        let expr_ctx = validated.expr_context().clone();
146        let convert = |bound_expr: &BoundExpression| {
147            crate::binder::expr_converter::bound_expr_to_contextual(bound_expr, &expr_ctx)
148                .map_err(PlannerError::PlanGenerationFailed)
149        };
150        let check_space_id = qctx.space_id().unwrap_or(1);
151        let check_space_name = qctx.space_name().unwrap_or_else(|| "default".to_string());
152        let outer_col_names: Vec<String> = Vec::new();
153        let reject_subquery = |expr: &ContextualExpression| -> Result<(), PlannerError> {
154            if let Some(expr_meta) = expr.expression() {
155                exists_planner::check_expression_subqueries(
156                    expr_meta.inner(),
157                    &qctx,
158                    check_space_id,
159                    &check_space_name,
160                    &outer_col_names,
161                )?;
162            }
163            Ok(())
164        };
165
166        let (insert_node, _inserted_count) = match &insert.target {
167            BoundInsertTarget::Vertices { tag, values } => {
168                let mut rows = Vec::with_capacity(values.len());
169                for row in values {
170                    let vid = convert(&row.vid)?;
171                    reject_subquery(&vid)?;
172                    let mut converted = Vec::with_capacity(row.values.len());
173                    for value in &row.values {
174                        let expr = convert(value)?;
175                        reject_subquery(&expr)?;
176                        converted.push(expr);
177                    }
178                    rows.push(VertexRow {
179                        vid,
180                        values: converted,
181                    });
182                }
183                let count = rows.len();
184                let info = self.build_vertex_insert_info(
185                    space_name,
186                    tag.clone(),
187                    rows,
188                    insert.if_not_exists,
189                )?;
190                (
191                    LogicalNodeEnum::InsertVertices(LogicalInsertVerticesNode {
192                        id: next_node_id(),
193                        info,
194                        output_var: None,
195                        col_names: vec!["inserted".to_string()],
196                        column_types: vec![],
197                    }),
198                    count,
199                )
200            }
201            BoundInsertTarget::Edge {
202                edge_name,
203                prop_names,
204                edges,
205            } => {
206                let mut converted_edges = Vec::with_capacity(edges.len());
207                for (src_b, dst_b, rank_b, props_b) in edges {
208                    let src = convert(src_b)?;
209                    reject_subquery(&src)?;
210                    let dst = convert(dst_b)?;
211                    reject_subquery(&dst)?;
212                    let rank = rank_b
213                        .as_ref()
214                        .map(|rank| -> Result<ContextualExpression, PlannerError> {
215                            let expr = convert(rank)?;
216                            reject_subquery(&expr)?;
217                            Ok(expr)
218                        })
219                        .transpose()?;
220                    let mut props = Vec::with_capacity(props_b.len());
221                    for prop in props_b {
222                        let expr = convert(prop)?;
223                        reject_subquery(&expr)?;
224                        props.push(expr);
225                    }
226                    converted_edges.push((src, dst, rank, props));
227                }
228                let count = converted_edges.len();
229                let info = self.build_edge_insert_info(
230                    space_name,
231                    edge_name.clone(),
232                    prop_names.clone(),
233                    converted_edges,
234                    insert.if_not_exists,
235                );
236                (
237                    LogicalNodeEnum::InsertEdges(LogicalInsertEdgesNode {
238                        id: next_node_id(),
239                        info,
240                        output_var: None,
241                        col_names: vec!["inserted".to_string()],
242                        column_types: vec![],
243                    }),
244                    count,
245                )
246            }
247        };
248
249        Ok(SubPlan::from_logical_root(insert_node))
250    }
251
252    fn transform(
253        &mut self,
254        validated: &ValidatedStatement,
255        qctx: Arc<QueryContext>,
256    ) -> Result<SubPlan, PlannerError> {
257        // Obtain the space name
258        let space_name = qctx.space_name().unwrap_or_else(|| "default".to_string());
259
260        // Use the verification information to optimize the planning process.
261        let validation_info = &validated.validation_info;
262
263        // Check the semantic information.
264        let referenced_tags = &validation_info.semantic_info.referenced_tags;
265        if !referenced_tags.is_empty() {
266            log::debug!("INSERT references the tag: {:?}", referenced_tags);
267        }
268
269        let referenced_edges = &validation_info.semantic_info.referenced_edges;
270        if !referenced_edges.is_empty() {
271            log::debug!("INSERT referenced edge type: {:?}", referenced_edges);
272        }
273
274        // Extract the INSERT statement
275        let insert_stmt = self.extract_insert_stmt(validated.stmt())?;
276
277        // Unified entry for expression-level EXISTS / IN: subqueries in
278        // INSERT value expressions are rejected at planning time with a
279        // precise error.
280        let check_space_id = qctx.space_id().unwrap_or(1);
281        let check_space_name = qctx.space_name().unwrap_or_else(|| "default".to_string());
282        let outer_col_names: Vec<String> = Vec::new();
283        match &insert_stmt.target {
284            InsertTarget::Vertices { values, .. } => {
285                for row in values {
286                    for expr in std::iter::once(&row.vid).chain(row.values.iter()) {
287                        if let Some(expr_meta) = expr.expression() {
288                            exists_planner::check_expression_subqueries(
289                                expr_meta.inner(),
290                                &qctx,
291                                check_space_id,
292                                &check_space_name,
293                                &outer_col_names,
294                            )?;
295                        }
296                    }
297                }
298            }
299            InsertTarget::Edge { edges, .. } => {
300                for (src, dst, rank, prop_values) in edges {
301                    for expr in std::iter::once(src)
302                        .chain(std::iter::once(dst))
303                        .chain(rank.iter())
304                        .chain(prop_values.iter())
305                    {
306                        if let Some(expr_meta) = expr.expression() {
307                            exists_planner::check_expression_subqueries(
308                                expr_meta.inner(),
309                                &qctx,
310                                check_space_id,
311                                &check_space_name,
312                                &outer_col_names,
313                            )?;
314                        }
315                    }
316                }
317            }
318        }
319
320        // Create the corresponding insertion nodes based on the type of the INSERT target.
321        let (insert_node, _inserted_count) = match &insert_stmt.target {
322            InsertTarget::Vertices { tag, values } => {
323                let count = values.len();
324                let info = self.build_vertex_insert_info(
325                    space_name,
326                    tag.clone(),
327                    values.clone(),
328                    insert_stmt.if_not_exists,
329                )?;
330                (
331                    LogicalNodeEnum::InsertVertices(LogicalInsertVerticesNode {
332                        id: next_node_id(),
333                        info,
334                        output_var: None,
335                        col_names: vec!["inserted".to_string()],
336                        column_types: vec![],
337                    }),
338                    count,
339                )
340            }
341            InsertTarget::Edge {
342                edge_name,
343                prop_names,
344                edges,
345            } => {
346                let count = edges.len();
347                let info = self.build_edge_insert_info(
348                    space_name,
349                    edge_name.clone(),
350                    prop_names.clone(),
351                    edges.clone(),
352                    insert_stmt.if_not_exists,
353                );
354                (
355                    LogicalNodeEnum::InsertEdges(LogicalInsertEdgesNode {
356                        id: next_node_id(),
357                        info,
358                        output_var: None,
359                        col_names: vec!["inserted".to_string()],
360                        column_types: vec![],
361                    }),
362                    count,
363                )
364            }
365        };
366
367        // InsertVertices/InsertEdges is the root node; the executor returns
368        // the count summary directly, so no projection node is needed.
369        Ok(SubPlan::from_logical_root(insert_node))
370    }
371
372    fn match_planner(&self, stmt: &Stmt) -> bool {
373        Self::match_stmt(stmt)
374    }
375}
376
377impl Default for InsertPlanner {
378    fn default() -> Self {
379        Self::new()
380    }
381}
382
383#[cfg(test)]
384#[allow(clippy::arc_with_non_send_sync)]
385mod tests {
386    use super::*;
387    use crate::binder::validation::ValidationInfo;
388    use crate::parser::ast::utils::ExprFactory;
389    use crate::parser::ast::{Ast, Span, Stmt};
390    use crate::parser::ast::{InsertStmt, InsertTarget, TagInsertSpec, VertexRow};
391    use crate::planning::planner::{Planner, ValidatedStatement};
392    use crate::QueryContext;
393    use linkrs_core::types::expr::contextual::ContextualExpression;
394    use linkrs_core::types::expr::expression_context::ExpressionAnalysisContext;
395    use linkrs_core::Value;
396    use std::sync::Arc;
397
398    fn create_test_span() -> Span {
399        use linkrs_core::types::span::Position;
400        Span::new(Position::new(1, 1), Position::new(1, 1))
401    }
402
403    fn create_test_stmt_with_insert(target: InsertTarget) -> Arc<Ast> {
404        let insert_stmt = InsertStmt {
405            span: create_test_span(),
406            target,
407            if_not_exists: false,
408        };
409        let ctx = Arc::new(ExpressionAnalysisContext::new());
410        Arc::new(Ast::new(Stmt::Insert(insert_stmt), ctx))
411    }
412
413    fn create_test_qctx() -> Arc<QueryContext> {
414        Arc::new(QueryContext::default())
415    }
416
417    // Auxiliary function: Creating constant expressions
418    fn lit(val: Value) -> ContextualExpression {
419        let ctx = Arc::new(ExpressionAnalysisContext::new());
420        ExprFactory::constant(val, ctx)
421    }
422
423    #[test]
424    fn test_insert_planner_new() {
425        let planner = InsertPlanner::new();
426        let ast = create_test_stmt_with_insert(InsertTarget::Vertices {
427            tag: TagInsertSpec {
428                tag_name: "person".to_string(),
429                prop_names: vec!["name".to_string(), "age".to_string()],
430                is_default_props: false,
431            },
432            values: vec![VertexRow {
433                vid: lit(Value::Int(1)),
434                values: vec![lit(Value::string("Alice")), lit(Value::Int(30))],
435            }],
436        });
437        assert!(planner.match_planner(&ast.stmt));
438    }
439
440    #[test]
441    fn test_match_stmt_with_insert() {
442        let ast = create_test_stmt_with_insert(InsertTarget::Vertices {
443            tag: TagInsertSpec {
444                tag_name: "person".to_string(),
445                prop_names: vec![],
446                is_default_props: true,
447            },
448            values: vec![],
449        });
450        assert!(InsertPlanner::match_stmt(&ast.stmt));
451    }
452
453    #[test]
454    fn test_match_stmt_without_insert() {
455        let stmt = Stmt::Use(crate::parser::ast::UseStmt {
456            span: create_test_span(),
457            space: "test_space".to_string(),
458        });
459        assert!(!InsertPlanner::match_stmt(&stmt));
460    }
461
462    #[test]
463    fn test_extract_insert_stmt_success() {
464        let planner = InsertPlanner::new();
465        let target = InsertTarget::Vertices {
466            tag: TagInsertSpec {
467                tag_name: "person".to_string(),
468                prop_names: vec!["name".to_string()],
469                is_default_props: false,
470            },
471            values: vec![],
472        };
473        let stmt = create_test_stmt_with_insert(target.clone());
474        let result = planner
475            .extract_insert_stmt(&stmt.as_ref().stmt)
476            .expect("Failed to extract insert statement");
477        assert_eq!(result.target, target);
478    }
479
480    #[test]
481    fn test_extract_insert_stmt_failure() {
482        let planner = InsertPlanner::new();
483        let stmt = Stmt::Use(crate::parser::ast::UseStmt {
484            span: create_test_span(),
485            space: "test_space".to_string(),
486        });
487        let result = planner.extract_insert_stmt(&stmt);
488        assert!(result.is_err());
489        assert!(result
490            .unwrap_err()
491            .to_string()
492            .contains("not an INSERT statement"));
493    }
494
495    #[test]
496    fn test_build_vertex_insert_info() {
497        let planner = InsertPlanner::new();
498        let info = planner
499            .build_vertex_insert_info(
500                "test_space".to_string(),
501                TagInsertSpec {
502                    tag_name: "person".to_string(),
503                    prop_names: vec!["name".to_string(), "age".to_string()],
504                    is_default_props: false,
505                },
506                vec![VertexRow {
507                    vid: lit(Value::Int(1)),
508                    values: vec![lit(Value::string("Alice")), lit(Value::Int(30))],
509                }],
510                false,
511            )
512            .expect("Failed to build vertex insert info");
513        assert_eq!(info.space_name, "test_space");
514        assert_eq!(info.tag.tag_name, "person");
515        assert_eq!(info.tag.prop_names.len(), 2);
516        assert_eq!(info.values.len(), 1);
517    }
518
519    #[test]
520    fn test_build_edge_insert_info() {
521        let planner = InsertPlanner::new();
522        let info = planner.build_edge_insert_info(
523            "test_space".to_string(),
524            "follow".to_string(),
525            vec!["since".to_string()],
526            vec![(
527                lit(Value::Int(1)),
528                lit(Value::Int(2)),
529                Some(lit(Value::Int(0))),
530                vec![lit(Value::string("2023"))],
531            )],
532            false,
533        );
534        assert_eq!(info.space_name, "test_space");
535        assert_eq!(info.edge_name, "follow");
536        assert_eq!(info.prop_names.len(), 1);
537        assert_eq!(info.edges.len(), 1);
538    }
539
540    #[test]
541    fn test_create_yield_columns() {
542        let planner = InsertPlanner::new();
543        let expr_ctx = Arc::new(ExpressionAnalysisContext::new());
544        let columns = planner.create_yield_columns(5, &expr_ctx);
545        assert_eq!(columns.len(), 1);
546        assert_eq!(columns[0].alias, "inserted_count");
547    }
548
549    #[test]
550    fn test_transform_insert_vertices() {
551        let mut planner = InsertPlanner::new();
552        let target = InsertTarget::Vertices {
553            tag: TagInsertSpec {
554                tag_name: "person".to_string(),
555                prop_names: vec!["name".to_string()],
556                is_default_props: false,
557            },
558            values: vec![
559                VertexRow {
560                    vid: lit(Value::Int(1)),
561                    values: vec![lit(Value::string("Alice"))],
562                },
563                VertexRow {
564                    vid: lit(Value::Int(2)),
565                    values: vec![lit(Value::string("Bob"))],
566                },
567            ],
568        };
569        let ast = create_test_stmt_with_insert(target);
570        let qctx = create_test_qctx();
571
572        // Create a verified statement.
573        let validation_info = ValidationInfo::new();
574        let validated = ValidatedStatement::new(ast, validation_info);
575
576        let result = planner.transform(&validated, qctx);
577        assert!(result.is_ok());
578        let sub_plan = result.expect("Failed to transform insert statement");
579        assert!(sub_plan.root.is_some());
580    }
581
582    #[test]
583    fn test_transform_insert_edge() {
584        let mut planner = InsertPlanner::new();
585        let target = InsertTarget::Edge {
586            edge_name: "follow".to_string(),
587            prop_names: vec!["since".to_string()],
588            edges: vec![(
589                lit(Value::Int(1)),
590                lit(Value::Int(2)),
591                Some(lit(Value::Int(0))),
592                vec![lit(Value::string("2023"))],
593            )],
594        };
595        let ast = create_test_stmt_with_insert(target);
596        let qctx = create_test_qctx();
597
598        // Create a verified statement.
599        let validation_info = ValidationInfo::new();
600        let validated = ValidatedStatement::new(ast, validation_info);
601
602        let result = planner.transform(&validated, qctx);
603        assert!(result.is_ok());
604        let sub_plan = result.expect("Failed to transform insert statement");
605        assert!(sub_plan.root.is_some());
606    }
607
608    #[test]
609    fn test_transform_without_insert_stmt() {
610        let mut planner = InsertPlanner::new();
611        let stmt = Stmt::Use(crate::parser::ast::UseStmt {
612            span: create_test_span(),
613            space: "test_space".to_string(),
614        });
615        let qctx = create_test_qctx();
616        let ctx = Arc::new(ExpressionAnalysisContext::new());
617        let ast = Arc::new(Ast::new(stmt, ctx));
618
619        // Create a verified statement.
620        let validation_info = ValidationInfo::new();
621        let validated = ValidatedStatement::new(ast, validation_info);
622
623        let result = planner.transform(&validated, qctx);
624        assert!(result.is_err());
625    }
626
627    #[test]
628    fn test_default_impl() {
629        let planner: InsertPlanner = Default::default();
630        let ast = create_test_stmt_with_insert(InsertTarget::Vertices {
631            tag: TagInsertSpec {
632                tag_name: "test".to_string(),
633                prop_names: vec![],
634                is_default_props: true,
635            },
636            values: vec![],
637        });
638        assert!(planner.match_planner(&ast.stmt));
639    }
640}