use std::sync::Arc;
use crate::optimizer::analysis::{
ExpressionAnalysis, ExpressionAnalyzer, FingerprintCalculator, PlanFingerprint,
ReferenceCountAnalysis, ReferenceCountAnalyzer,
};
use crate::planning::plan::core::nodes::{FilterNode, GetVerticesNode, PlanNodeEnum, ProjectNode};
use linkrs_core::types::expr::expression_context::ExpressionAnalysisContext;
use linkrs_core::types::expr::FunctionArg;
use linkrs_core::types::expr::{Expression, ExpressionMeta};
use linkrs_core::Value;
fn create_test_context(expr: Expression) -> linkrs_core::types::ContextualExpression {
let expr_ctx = Arc::new(ExpressionAnalysisContext::new());
let expr_meta = ExpressionMeta::new(expr);
let expr_id = expr_ctx.register_expression(expr_meta);
linkrs_core::types::ContextualExpression::new(expr_id, expr_ctx)
}
#[test]
fn test_expression_analyzer_integration() {
let analyzer = ExpressionAnalyzer::new();
let complex_expr = Expression::Binary {
left: Box::new(Expression::Property {
object: Box::new(Expression::Variable("n".to_string())),
property: "age".to_string(),
}),
op: linkrs_core::types::BinaryOperator::GreaterThan,
right: Box::new(Expression::Literal(Value::Int(18))),
};
let ctx_expr = create_test_context(complex_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.referenced_properties.contains(&"age".to_string()));
assert!(analysis.referenced_variables.contains(&"n".to_string()));
assert!(analysis.node_count > 0);
}
#[test]
fn test_nondeterministic_expression_analysis() {
let analyzer = ExpressionAnalyzer::new();
let nondeterministic_expr = Expression::Function {
name: "rand".to_string(),
args: vec![],
};
let ctx_expr = create_test_context(nondeterministic_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(!analysis.is_deterministic);
assert!(analysis.called_functions.contains(&"rand".to_string()));
}
#[test]
fn test_expression_analyzer_options() {
let deterministic_analyzer = ExpressionAnalyzer::deterministic_only();
let expr = Expression::Literal(Value::Int(42));
let ctx_expr = create_test_context(expr);
let analysis = deterministic_analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert_eq!(analysis.referenced_properties.len(), 0);
assert_eq!(analysis.referenced_variables.len(), 0);
let property_analyzer = ExpressionAnalyzer::property_extractor();
let prop_expr = Expression::Property {
object: Box::new(Expression::Variable("n".to_string())),
property: "name".to_string(),
};
let ctx_expr = create_test_context(prop_expr);
let prop_analysis = property_analyzer.analyze(&ctx_expr);
assert!(prop_analysis
.referenced_properties
.contains(&"name".to_string()));
}
#[test]
fn test_fingerprint_calculator_integration() {
let calculator = FingerprintCalculator::new();
let node1 = PlanNodeEnum::GetVertices(GetVerticesNode::new(1, "default", "Person"));
let node2 = PlanNodeEnum::GetVertices(GetVerticesNode::new(1, "default", "Person"));
let fp1 = calculator.calculate_fingerprint(&node1);
let fp2 = calculator.calculate_fingerprint(&node2);
assert_eq!(fp1, fp2);
let node3 = PlanNodeEnum::Start(crate::planning::plan::core::nodes::StartNode::new());
let fp3 = calculator.calculate_fingerprint(&node3);
assert_ne!(fp1, fp3);
}
#[test]
fn test_fingerprint_stability() {
let calculator = FingerprintCalculator::new();
let node = PlanNodeEnum::GetVertices(GetVerticesNode::new(1, "default", "Test"));
let fp1 = calculator.calculate_fingerprint(&node);
let fp2 = calculator.calculate_fingerprint(&node);
let fp3 = calculator.calculate_fingerprint(&node);
assert_eq!(fp1, fp2);
assert_eq!(fp2, fp3);
}
#[test]
fn test_reference_count_analyzer_integration() {
let analyzer = ReferenceCountAnalyzer::new();
let start_node = PlanNodeEnum::Start(crate::planning::plan::core::nodes::StartNode::new());
let analysis = analyzer.analyze(&start_node);
assert_eq!(analysis.repeated_count(), 0);
}
#[test]
fn test_expression_complexity_scoring() {
let analyzer = ExpressionAnalyzer::new();
let simple_expr = Expression::Literal(Value::Int(1));
let ctx_simple = create_test_context(simple_expr);
let simple_analysis = analyzer.analyze(&ctx_simple);
let complex_expr = Expression::Binary {
left: Box::new(Expression::Function {
name: "abs".to_string(),
args: vec![FunctionArg::Positional(Expression::Property {
object: Box::new(Expression::Variable("x".to_string())),
property: "value".to_string(),
})],
}),
op: linkrs_core::types::BinaryOperator::Add,
right: Box::new(Expression::Function {
name: "coalesce".to_string(),
args: vec![
FunctionArg::Positional(Expression::Property {
object: Box::new(Expression::Variable("y".to_string())),
property: "value".to_string(),
}),
FunctionArg::Positional(Expression::Literal(Value::Int(0))),
],
}),
};
let ctx_complex = create_test_context(complex_expr);
let complex_analysis = analyzer.analyze(&ctx_complex);
assert!(complex_analysis.complexity_score > simple_analysis.complexity_score);
}
#[test]
fn test_expression_aggregate_detection() {
let analyzer = ExpressionAnalyzer::new();
let aggregate_expr = Expression::Aggregate {
func: linkrs_core::types::operators::AggregateFunction::Count,
args: vec![Expression::Variable("n".to_string())],
distinct: false,
filter: None,
};
let ctx_expr = create_test_context(aggregate_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.contains_aggregate);
}
#[test]
fn test_expression_depth_and_node_count() {
let analyzer = ExpressionAnalyzer::new();
let nested_expr = Expression::Binary {
left: Box::new(Expression::Binary {
left: Box::new(Expression::Binary {
left: Box::new(Expression::Literal(Value::Int(1))),
op: linkrs_core::types::BinaryOperator::Add,
right: Box::new(Expression::Literal(Value::Int(2))),
}),
op: linkrs_core::types::BinaryOperator::Add,
right: Box::new(Expression::Literal(Value::Int(3))),
}),
op: linkrs_core::types::BinaryOperator::Add,
right: Box::new(Expression::Literal(Value::Int(4))),
};
let ctx_expr = create_test_context(nested_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.node_count > 0);
}
#[test]
fn test_multiple_function_calls() {
let analyzer = ExpressionAnalyzer::new();
let multi_func_expr = Expression::Function {
name: "coalesce".to_string(),
args: vec![
FunctionArg::Positional(Expression::Function {
name: "abs".to_string(),
args: vec![FunctionArg::Positional(Expression::Literal(Value::Int(-5)))],
}),
FunctionArg::Positional(Expression::Function {
name: "sqrt".to_string(),
args: vec![FunctionArg::Positional(Expression::Literal(Value::Int(25)))],
}),
FunctionArg::Positional(Expression::Literal(Value::Int(0))),
],
};
let ctx_expr = create_test_context(multi_func_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.called_functions.contains(&"coalesce".to_string()));
assert!(analysis.called_functions.contains(&"abs".to_string()));
assert!(analysis.called_functions.contains(&"sqrt".to_string()));
}
#[test]
fn test_expression_analyzer_with_case_expression() {
let analyzer = ExpressionAnalyzer::new();
let case_expr = Expression::Case {
test_expr: None,
conditions: vec![
(
Expression::Binary {
left: Box::new(Expression::Variable("x".to_string())),
op: linkrs_core::types::BinaryOperator::GreaterThan,
right: Box::new(Expression::Literal(Value::Int(10))),
},
Expression::Literal(Value::string("large")),
),
(
Expression::Binary {
left: Box::new(Expression::Variable("x".to_string())),
op: linkrs_core::types::BinaryOperator::GreaterThan,
right: Box::new(Expression::Literal(Value::Int(5))),
},
Expression::Literal(Value::string("medium")),
),
],
default: Some(Box::new(Expression::Literal(Value::string("small")))),
};
let ctx_expr = create_test_context(case_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.node_count > 0);
}
#[test]
fn test_plan_fingerprint_with_filter() {
let calculator = FingerprintCalculator::new();
let condition_expr = Expression::Binary {
left: Box::new(Expression::Property {
object: Box::new(Expression::Variable("n".to_string())),
property: "age".to_string(),
}),
op: linkrs_core::types::BinaryOperator::GreaterThan,
right: Box::new(Expression::Literal(Value::Int(18))),
};
let expr_ctx = Arc::new(ExpressionAnalysisContext::new());
let expr_meta = ExpressionMeta::new(condition_expr);
let expr_id = expr_ctx.register_expression(expr_meta);
let ctx_expr = linkrs_core::types::ContextualExpression::new(expr_id, expr_ctx);
let input_node = PlanNodeEnum::GetVertices(GetVerticesNode::new(1, "default", "Person"));
let filter_node = PlanNodeEnum::Filter(
FilterNode::new(input_node, ctx_expr).expect("Failed to create FilterNode"),
);
let fp = calculator.calculate_fingerprint(&filter_node);
assert!(fp.value() != 0);
}
#[test]
fn test_plan_fingerprint_with_project() {
let calculator = FingerprintCalculator::new();
let input_node = PlanNodeEnum::GetVertices(GetVerticesNode::new(1, "default", "Person"));
let expr_ctx = Arc::new(ExpressionAnalysisContext::new());
let expr1 = Expression::Property {
object: Box::new(Expression::Variable("n".to_string())),
property: "name".to_string(),
};
let expr_meta1 = ExpressionMeta::new(expr1);
let expr_id1 = expr_ctx.register_expression(expr_meta1);
let ctx_expr1 = linkrs_core::types::ContextualExpression::new(expr_id1, expr_ctx.clone());
let expr2 = Expression::Property {
object: Box::new(Expression::Variable("n".to_string())),
property: "age".to_string(),
};
let expr_meta2 = ExpressionMeta::new(expr2);
let expr_id2 = expr_ctx.register_expression(expr_meta2);
let ctx_expr2 = linkrs_core::types::ContextualExpression::new(expr_id2, expr_ctx);
let columns = vec![
linkrs_core::YieldColumn::new(ctx_expr1, "n.name".to_string()),
linkrs_core::YieldColumn::new(ctx_expr2, "n.age".to_string()),
];
let project_node = PlanNodeEnum::Project(
ProjectNode::new(input_node, columns).expect("Failed to create ProjectNode"),
);
let fp = calculator.calculate_fingerprint(&project_node);
assert!(fp.value() != 0);
}
#[test]
fn test_expression_analyzer_with_list_expression() {
let analyzer = ExpressionAnalyzer::new();
let list_expr = Expression::List(vec![
Expression::Literal(Value::Int(1)),
Expression::Literal(Value::Int(2)),
Expression::Literal(Value::Int(3)),
]);
let ctx_expr = create_test_context(list_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.node_count > 0);
}
#[test]
fn test_expression_analyzer_with_map_expression() {
let analyzer = ExpressionAnalyzer::new();
let map_expr = Expression::Map(vec![
("key1".into(), Expression::Literal(Value::Int(1))),
(
"key2".to_string(),
Expression::Literal(Value::string("value")),
),
]);
let ctx_expr = create_test_context(map_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.node_count > 0);
}
#[test]
fn test_expression_analyzer_with_type_cast() {
let analyzer = ExpressionAnalyzer::new();
let cast_expr = Expression::TypeCast {
expression: Box::new(Expression::Literal(Value::string("123"))),
target_type: linkrs_core::types::DataType::Int,
};
let ctx_expr = create_test_context(cast_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.node_count > 0);
}
#[test]
fn test_expression_analyzer_with_subscript() {
let analyzer = ExpressionAnalyzer::new();
let subscript_expr = Expression::Subscript {
collection: Box::new(Expression::Variable("arr".to_string())),
index: Box::new(Expression::Literal(Value::Int(0))),
};
let ctx_expr = create_test_context(subscript_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.referenced_variables.contains(&"arr".to_string()));
}
#[test]
fn test_expression_analyzer_with_unary_operator() {
let analyzer = ExpressionAnalyzer::new();
let unary_expr = Expression::Unary {
op: linkrs_core::types::UnaryOperator::Minus,
operand: Box::new(Expression::Literal(Value::Int(5))),
};
let ctx_expr = create_test_context(unary_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.node_count > 0);
}
#[test]
fn test_expression_analyzer_with_range() {
let analyzer = ExpressionAnalyzer::new();
let range_expr = Expression::Range {
collection: Box::new(Expression::Variable("list".to_string())),
start: Some(Box::new(Expression::Literal(Value::Int(0)))),
end: Some(Box::new(Expression::Literal(Value::Int(10)))),
};
let ctx_expr = create_test_context(range_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.referenced_variables.contains(&"list".to_string()));
}
#[test]
fn test_expression_analyzer_with_label_tag_property() {
let analyzer = ExpressionAnalyzer::new();
let label_tag_prop_expr = Expression::LabelTagProperty {
tag: Box::new(Expression::Variable("tagName".to_string())),
property: "propertyName".to_string(),
};
let ctx_expr = create_test_context(label_tag_prop_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis
.referenced_variables
.contains(&"tagName".to_string()));
}
#[test]
fn test_expression_analyzer_with_tag_property() {
let analyzer = ExpressionAnalyzer::new();
let tag_prop_expr = Expression::TagProperty {
tag_name: "Person".to_string(),
property: "name".to_string(),
};
let ctx_expr = create_test_context(tag_prop_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
}
#[test]
fn test_expression_analyzer_with_edge_property() {
let analyzer = ExpressionAnalyzer::new();
let edge_prop_expr = Expression::EdgeProperty {
edge_name: "FRIEND".to_string(),
property: "since".to_string(),
};
let ctx_expr = create_test_context(edge_prop_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
}
#[test]
fn test_expression_analyzer_with_parameter() {
let analyzer = ExpressionAnalyzer::new();
let param_expr = Expression::Parameter("userId".to_string());
let ctx_expr = create_test_context(param_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.node_count > 0);
}
#[test]
fn test_expression_analyzer_with_path() {
let analyzer = ExpressionAnalyzer::new();
let path_expr = Expression::Path(vec![
Expression::Variable("v1".to_string()),
Expression::Variable("e1".to_string()),
Expression::Variable("v2".to_string()),
]);
let ctx_expr = create_test_context(path_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.referenced_variables.contains(&"v1".to_string()));
assert!(analysis.referenced_variables.contains(&"e1".to_string()));
assert!(analysis.referenced_variables.contains(&"v2".to_string()));
}
#[test]
fn test_expression_analyzer_with_label() {
let analyzer = ExpressionAnalyzer::new();
let label_expr = Expression::Label("Person".to_string());
let ctx_expr = create_test_context(label_expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
assert!(analysis.node_count > 0);
}
#[test]
fn test_expression_analysis_default() {
let analysis = ExpressionAnalysis::new();
assert!(analysis.is_deterministic);
assert_eq!(analysis.complexity_score, 0);
assert_eq!(analysis.referenced_properties.len(), 0);
assert_eq!(analysis.referenced_variables.len(), 0);
assert_eq!(analysis.called_functions.len(), 0);
assert!(!analysis.contains_aggregate);
assert!(!analysis.contains_subquery);
assert_eq!(analysis.node_count, 0);
}
#[test]
fn test_plan_fingerprint_default() {
let fp = PlanFingerprint::new(12345);
assert_eq!(fp.value(), 12345);
}
#[test]
fn test_reference_count_analysis_default() {
let analysis = ReferenceCountAnalysis::new();
assert_eq!(analysis.repeated_count(), 0);
assert!(!analysis.is_repeated(1));
assert!(analysis.get_node_info(1).is_none());
}
#[test]
fn test_expression_analyzer_clone() {
let analyzer = ExpressionAnalyzer::new();
let _analyzer_clone = analyzer.clone();
}
#[test]
fn test_fingerprint_calculator_clone() {
let calculator = FingerprintCalculator::new();
let _calculator_clone = calculator.clone();
}
#[test]
fn test_reference_count_analyzer_clone() {
let analyzer = ReferenceCountAnalyzer::new();
let _analyzer_clone = analyzer.clone();
}
#[test]
fn test_expression_analysis_clone() {
let mut analysis = ExpressionAnalysis::new();
analysis.is_deterministic = false;
analysis.complexity_score = 50;
let analysis_clone = analysis.clone();
assert!(!analysis_clone.is_deterministic);
assert_eq!(analysis_clone.complexity_score, 50);
}
#[test]
fn test_plan_fingerprint_hash() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let fp1 = PlanFingerprint::new(12345);
let fp2 = PlanFingerprint::new(12345);
let fp3 = PlanFingerprint::new(67890);
let mut hasher1 = DefaultHasher::new();
fp1.hash(&mut hasher1);
let hash1 = hasher1.finish();
let mut hasher2 = DefaultHasher::new();
fp2.hash(&mut hasher2);
let hash2 = hasher2.finish();
let mut hasher3 = DefaultHasher::new();
fp3.hash(&mut hasher3);
let hash3 = hasher3.finish();
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash3);
}
#[test]
fn test_expression_analyzer_default() {
let analyzer = ExpressionAnalyzer::default();
let expr = Expression::Literal(Value::Int(42));
let ctx_expr = create_test_context(expr);
let analysis = analyzer.analyze(&ctx_expr);
assert!(analysis.is_deterministic);
}
#[test]
fn test_fingerprint_calculator_default() {
let calculator = FingerprintCalculator::new();
let node = PlanNodeEnum::Start(crate::planning::plan::core::nodes::StartNode::new());
let fp = calculator.calculate_fingerprint(&node);
assert!(fp.value() != 0);
}
#[test]
fn test_reference_count_analyzer_default() {
let analyzer = ReferenceCountAnalyzer::default();
let node = PlanNodeEnum::Start(crate::planning::plan::core::nodes::StartNode::new());
let analysis = analyzer.analyze(&node);
assert_eq!(analysis.repeated_count(), 0);
}