Skip to content

Commit 9422227

Browse files
authored
feat(semantic): validate function args and arithmetic operands (#13)
* feat(semantic): validate function args and arithmetic operands * format code
1 parent 7868197 commit 9422227

File tree

1 file changed

+135
-7
lines changed

1 file changed

+135
-7
lines changed

rust/lance-graph/src/semantic.rs

Lines changed: 135 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -268,11 +268,14 @@ impl SemanticAnalyzer {
268268
});
269269
}
270270
}
271-
ValueExpression::Function { .. } => {
272-
// TODO: Implement function validation
271+
ValueExpression::Function { args, .. } => {
272+
for arg in args {
273+
self.analyze_value_expression(arg)?;
274+
}
273275
}
274-
ValueExpression::Arithmetic { .. } => {
275-
// TODO: Implement arithmetic validation
276+
ValueExpression::Arithmetic { left, right, .. } => {
277+
self.analyze_value_expression(left)?;
278+
self.analyze_value_expression(right)?;
276279
}
277280
}
278281
Ok(())
@@ -418,9 +421,9 @@ impl SemanticAnalyzer {
418421
mod tests {
419422
use super::*;
420423
use crate::ast::{
421-
BooleanExpression, CypherQuery, GraphPattern, LengthRange, MatchClause, NodePattern,
422-
PathPattern, PathSegment, PropertyRef, PropertyValue, RelationshipDirection,
423-
RelationshipPattern, ReturnClause, WhereClause,
424+
ArithmeticOperator, BooleanExpression, CypherQuery, GraphPattern, LengthRange, MatchClause,
425+
NodePattern, PathPattern, PathSegment, PropertyRef, PropertyValue, RelationshipDirection,
426+
RelationshipPattern, ReturnClause, ReturnItem, ValueExpression, WhereClause,
424427
};
425428
use crate::config::{GraphConfig, NodeMapping};
426429

@@ -751,4 +754,129 @@ mod tests {
751754
assert!(r.properties.contains("since"));
752755
assert!(r.properties.contains("level"));
753756
}
757+
758+
#[test]
759+
fn test_function_argument_undefined_variable_in_return() {
760+
// MATCH (n:Person) RETURN toUpper(m.name)
761+
let node = NodePattern::new(Some("n".to_string())).with_label("Person");
762+
let query = CypherQuery {
763+
match_clauses: vec![MatchClause {
764+
patterns: vec![GraphPattern::Node(node)],
765+
}],
766+
where_clause: None,
767+
return_clause: ReturnClause {
768+
distinct: false,
769+
items: vec![ReturnItem {
770+
expression: ValueExpression::Function {
771+
name: "toUpper".to_string(),
772+
args: vec![ValueExpression::Property(PropertyRef::new("m", "name"))],
773+
},
774+
alias: None,
775+
}],
776+
},
777+
limit: None,
778+
order_by: None,
779+
skip: None,
780+
};
781+
782+
let mut analyzer = SemanticAnalyzer::new(test_config());
783+
let result = analyzer.analyze(&query).unwrap();
784+
assert!(result
785+
.errors
786+
.iter()
787+
.any(|e| e.contains("Undefined variable: 'm'")));
788+
}
789+
790+
#[test]
791+
fn test_function_argument_valid_variable_ok() {
792+
// MATCH (n:Person) RETURN toUpper(n.name)
793+
let node = NodePattern::new(Some("n".to_string())).with_label("Person");
794+
let query = CypherQuery {
795+
match_clauses: vec![MatchClause {
796+
patterns: vec![GraphPattern::Node(node)],
797+
}],
798+
where_clause: None,
799+
return_clause: ReturnClause {
800+
distinct: false,
801+
items: vec![ReturnItem {
802+
expression: ValueExpression::Function {
803+
name: "toUpper".to_string(),
804+
args: vec![ValueExpression::Property(PropertyRef::new("n", "name"))],
805+
},
806+
alias: None,
807+
}],
808+
},
809+
limit: None,
810+
order_by: None,
811+
skip: None,
812+
};
813+
814+
let mut analyzer = SemanticAnalyzer::new(test_config());
815+
let result = analyzer.analyze(&query).unwrap();
816+
assert!(result.errors.is_empty());
817+
}
818+
819+
#[test]
820+
fn test_arithmetic_with_undefined_variable_in_return() {
821+
// RETURN x + 1
822+
let query = CypherQuery {
823+
match_clauses: vec![],
824+
where_clause: None,
825+
return_clause: ReturnClause {
826+
distinct: false,
827+
items: vec![ReturnItem {
828+
expression: ValueExpression::Arithmetic {
829+
left: Box::new(ValueExpression::Variable("x".to_string())),
830+
operator: ArithmeticOperator::Add,
831+
right: Box::new(ValueExpression::Literal(PropertyValue::Integer(1))),
832+
},
833+
alias: None,
834+
}],
835+
},
836+
limit: None,
837+
order_by: None,
838+
skip: None,
839+
};
840+
841+
let mut analyzer = SemanticAnalyzer::new(test_config());
842+
let result = analyzer.analyze(&query).unwrap();
843+
assert!(result
844+
.errors
845+
.iter()
846+
.any(|e| e.contains("Undefined variable: 'x'")));
847+
}
848+
849+
#[test]
850+
fn test_arithmetic_with_defined_property_ok() {
851+
// MATCH (n:Person) RETURN 1 + n.age
852+
let node = NodePattern::new(Some("n".to_string())).with_label("Person");
853+
let query = CypherQuery {
854+
match_clauses: vec![MatchClause {
855+
patterns: vec![GraphPattern::Node(node)],
856+
}],
857+
where_clause: None,
858+
return_clause: ReturnClause {
859+
distinct: false,
860+
items: vec![ReturnItem {
861+
expression: ValueExpression::Arithmetic {
862+
left: Box::new(ValueExpression::Literal(PropertyValue::Integer(1))),
863+
operator: ArithmeticOperator::Add,
864+
right: Box::new(ValueExpression::Property(PropertyRef::new("n", "age"))),
865+
},
866+
alias: None,
867+
}],
868+
},
869+
limit: None,
870+
order_by: None,
871+
skip: None,
872+
};
873+
874+
let mut analyzer = SemanticAnalyzer::new(test_config());
875+
let result = analyzer.analyze(&query).unwrap();
876+
// Should not report undefined variable 'n'
877+
assert!(result
878+
.errors
879+
.iter()
880+
.all(|e| !e.contains("Undefined variable: 'n'")));
881+
}
754882
}

0 commit comments

Comments
 (0)