use crate::binder::validation::CypherClauseKind;
use crate::parser::ast::Stmt;
use crate::planning::plan::SubPlan;
use crate::planning::planner::Planner;
use crate::QueryContext;
use std::sync::Arc;
pub trait StatementPlanner: Planner {
fn statement_type(&self) -> &'static str;
fn supported_clause_kinds(&self) -> &[CypherClauseKind];
}
pub trait ClausePlanner: std::fmt::Debug {
fn clause_kind(&self) -> CypherClauseKind;
fn transform_clause(
&self,
qctx: Arc<QueryContext>,
stmt: &Stmt,
input_plan: SubPlan,
) -> Result<SubPlan, crate::planning::planner::PlannerError>;
}
#[cfg(test)]
#[allow(clippy::arc_with_non_send_sync)]
mod tests {
use super::*;
use crate::binder::validation::ValidatedStatement;
use crate::parser::ast::{Ast, Span};
use crate::planning::plan::core::nodes::StartNode;
use crate::planning::plan::core::PlanNodeEnum;
use crate::QueryRequestContext;
use linkrs_core::types::expr::expression_context::ExpressionAnalysisContext;
use std::collections::HashMap;
#[derive(Debug)]
struct MockStatementPlanner {
stmt_type: &'static str,
supported_kinds: Vec<CypherClauseKind>,
}
impl MockStatementPlanner {
fn new(stmt_type: &'static str, supported_kinds: Vec<CypherClauseKind>) -> Self {
Self {
stmt_type,
supported_kinds,
}
}
}
impl Planner for MockStatementPlanner {
fn transform(
&mut self,
_validated: &ValidatedStatement,
_qctx: Arc<QueryContext>,
) -> Result<SubPlan, crate::planning::planner::PlannerError> {
let start_node = StartNode::new();
let start_node_enum = PlanNodeEnum::Start(start_node);
Ok(SubPlan {
root: Some(start_node_enum.clone()),
tail: Some(start_node_enum),
logical_root: None,
})
}
fn match_planner(&self, _stmt: &Stmt) -> bool {
true
}
}
impl StatementPlanner for MockStatementPlanner {
fn statement_type(&self) -> &'static str {
self.stmt_type
}
fn supported_clause_kinds(&self) -> &[CypherClauseKind] {
&self.supported_kinds
}
}
#[derive(Debug)]
struct MockClausePlanner {
kind: CypherClauseKind,
}
impl MockClausePlanner {
fn new(kind: CypherClauseKind) -> Self {
Self { kind }
}
}
impl ClausePlanner for MockClausePlanner {
fn clause_kind(&self) -> CypherClauseKind {
self.kind
}
fn transform_clause(
&self,
_qctx: Arc<QueryContext>,
_stmt: &Stmt,
input_plan: SubPlan,
) -> Result<SubPlan, crate::planning::planner::PlannerError> {
Ok(input_plan)
}
}
fn create_test_qctx() -> Arc<QueryContext> {
let rctx = Arc::new(QueryRequestContext {
session_id: None,
user_name: None,
space_name: None,
query: String::new(),
parameters: HashMap::new(),
..Default::default()
});
Arc::new(QueryContext::new(rctx))
}
fn create_test_match_stmt() -> Arc<Ast> {
let stmt = Stmt::Match(crate::parser::ast::stmt::MatchStmt {
span: Span::default(),
patterns: vec![],
where_clause: None,
return_clause: None,
order_by: None,
limit: None,
skip: None,
join_hint: None,
optional: false,
delete_clause: None,
});
let ctx = Arc::new(ExpressionAnalysisContext::new());
Arc::new(Ast::new(stmt, ctx))
}
#[test]
fn test_statement_planner_statement_type() {
let planner = MockStatementPlanner::new(
"MATCH",
vec![CypherClauseKind::Match, CypherClauseKind::Where],
);
assert_eq!(planner.statement_type(), "MATCH");
}
#[test]
fn test_statement_planner_supported_clause_kinds() {
let supported_kinds = vec![CypherClauseKind::Match, CypherClauseKind::Where];
let planner = MockStatementPlanner::new("MATCH", supported_kinds.clone());
assert_eq!(planner.supported_clause_kinds(), &supported_kinds);
}
#[test]
fn test_statement_planner_transform() {
use crate::binder::validation::ValidationInfo;
let mut planner = MockStatementPlanner::new("MATCH", vec![CypherClauseKind::Match]);
let ast = create_test_match_stmt();
let qctx = create_test_qctx();
let validation_info = ValidationInfo::new();
let validated = ValidatedStatement::new(ast, validation_info);
let result = planner.transform(&validated, qctx);
assert!(result.is_ok());
let sub_plan = result.expect("transform should succeed");
assert!(sub_plan.root.is_some());
assert!(sub_plan.tail.is_some());
}
#[test]
fn test_statement_planner_match_planner() {
let planner = MockStatementPlanner::new("MATCH", vec![CypherClauseKind::Match]);
let ast = create_test_match_stmt();
assert!(planner.match_planner(&ast.stmt));
}
#[test]
fn test_clause_planner_clause_kind() {
let planner = MockClausePlanner::new(CypherClauseKind::Where);
assert_eq!(planner.clause_kind(), CypherClauseKind::Where);
}
#[test]
fn test_clause_planner_transform_clause() {
let planner = MockClausePlanner::new(CypherClauseKind::Where);
let qctx = create_test_qctx();
let ast = create_test_match_stmt();
let start_node = StartNode::new();
let start_node_enum = PlanNodeEnum::Start(start_node.clone());
let input_plan = SubPlan {
root: Some(start_node_enum.clone()),
tail: Some(start_node_enum),
logical_root: None,
};
let result = planner.transform_clause(qctx, &ast.stmt, input_plan);
assert!(result.is_ok());
}
#[test]
fn test_multiple_supported_clause_kinds() {
let supported_kinds = vec![
CypherClauseKind::Match,
CypherClauseKind::Where,
CypherClauseKind::Return,
CypherClauseKind::With,
];
let planner = MockStatementPlanner::new("MATCH", supported_kinds.clone());
assert_eq!(planner.supported_clause_kinds().len(), 4);
assert!(planner
.supported_clause_kinds()
.contains(&CypherClauseKind::Match));
assert!(planner
.supported_clause_kinds()
.contains(&CypherClauseKind::Where));
assert!(planner
.supported_clause_kinds()
.contains(&CypherClauseKind::Return));
assert!(planner
.supported_clause_kinds()
.contains(&CypherClauseKind::With));
}
#[test]
fn test_clause_planner_different_kinds() {
let where_planner = MockClausePlanner::new(CypherClauseKind::Where);
let return_planner = MockClausePlanner::new(CypherClauseKind::Return);
let with_planner = MockClausePlanner::new(CypherClauseKind::With);
assert_eq!(where_planner.clause_kind(), CypherClauseKind::Where);
assert_eq!(return_planner.clause_kind(), CypherClauseKind::Return);
assert_eq!(with_planner.clause_kind(), CypherClauseKind::With);
}
}