@@ -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 {
418421mod 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