Skip to main content

lance_graph/
parameter_substitution.rs

1use crate::ast::*;
2use crate::error::{GraphError, Result};
3use std::collections::HashMap;
4
5/// Substitute parameters with literal values in the AST
6pub fn substitute_parameters(
7    query: &mut CypherQuery,
8    parameters: &HashMap<String, serde_json::Value>,
9) -> Result<()> {
10    // Substitute in READING clauses
11    for reading_clause in &mut query.reading_clauses {
12        substitute_in_reading_clause(reading_clause, parameters)?;
13    }
14
15    // Substitute in WHERE clause
16    if let Some(where_clause) = &mut query.where_clause {
17        substitute_in_where_clause(where_clause, parameters)?;
18    }
19
20    // Substitute in WITH clause
21    if let Some(with_clause) = &mut query.with_clause {
22        substitute_in_with_clause(with_clause, parameters)?;
23    }
24
25    // Substitute in post-WITH READING clauses
26    for reading_clause in &mut query.post_with_reading_clauses {
27        substitute_in_reading_clause(reading_clause, parameters)?;
28    }
29
30    // Substitute in post-WITH WHERE clause
31    if let Some(post_where) = &mut query.post_with_where_clause {
32        substitute_in_where_clause(post_where, parameters)?;
33    }
34
35    // Substitute in RETURN clause
36    substitute_in_return_clause(&mut query.return_clause, parameters)?;
37
38    // Substitute in ORDER BY clause
39    if let Some(order_by) = &mut query.order_by {
40        substitute_in_order_by_clause(order_by, parameters)?;
41    }
42
43    Ok(())
44}
45
46fn substitute_in_reading_clause(
47    clause: &mut ReadingClause,
48    parameters: &HashMap<String, serde_json::Value>,
49) -> Result<()> {
50    match clause {
51        ReadingClause::Match(match_clause) => {
52            for pattern in &mut match_clause.patterns {
53                substitute_in_graph_pattern(pattern, parameters)?;
54            }
55        }
56        ReadingClause::Unwind(unwind_clause) => {
57            substitute_in_value_expression(&mut unwind_clause.expression, parameters)?;
58        }
59    }
60    Ok(())
61}
62
63fn substitute_in_graph_pattern(
64    pattern: &mut GraphPattern,
65    parameters: &HashMap<String, serde_json::Value>,
66) -> Result<()> {
67    match pattern {
68        GraphPattern::Node(node) => {
69            for value in node.properties.values_mut() {
70                substitute_in_property_value(value, parameters)?;
71            }
72        }
73        GraphPattern::Path(path) => {
74            substitute_in_node_pattern(&mut path.start_node, parameters)?;
75            for segment in &mut path.segments {
76                substitute_in_relationship_pattern(&mut segment.relationship, parameters)?;
77                substitute_in_node_pattern(&mut segment.end_node, parameters)?;
78            }
79        }
80    }
81    Ok(())
82}
83
84fn substitute_in_node_pattern(
85    node: &mut NodePattern,
86    parameters: &HashMap<String, serde_json::Value>,
87) -> Result<()> {
88    for value in node.properties.values_mut() {
89        substitute_in_property_value(value, parameters)?;
90    }
91    Ok(())
92}
93
94fn substitute_in_relationship_pattern(
95    rel: &mut RelationshipPattern,
96    parameters: &HashMap<String, serde_json::Value>,
97) -> Result<()> {
98    for value in rel.properties.values_mut() {
99        substitute_in_property_value(value, parameters)?;
100    }
101    Ok(())
102}
103
104fn substitute_in_property_value(
105    value: &mut PropertyValue,
106    parameters: &HashMap<String, serde_json::Value>,
107) -> Result<()> {
108    if let PropertyValue::Parameter(name) = value {
109        let param_value =
110            parameters
111                .get(&name.to_lowercase())
112                .ok_or_else(|| GraphError::PlanError {
113                    message: format!("Missing parameter: ${}", name),
114                    location: snafu::Location::new(file!(), line!(), column!()),
115                })?;
116
117        *value = json_to_property_value(param_value)?;
118    }
119    Ok(())
120}
121
122fn substitute_in_where_clause(
123    where_clause: &mut WhereClause,
124    parameters: &HashMap<String, serde_json::Value>,
125) -> Result<()> {
126    substitute_in_boolean_expression(&mut where_clause.expression, parameters)
127}
128
129fn substitute_in_with_clause(
130    with_clause: &mut WithClause,
131    parameters: &HashMap<String, serde_json::Value>,
132) -> Result<()> {
133    for item in &mut with_clause.items {
134        substitute_in_value_expression(&mut item.expression, parameters)?;
135    }
136    if let Some(order_by) = &mut with_clause.order_by {
137        substitute_in_order_by_clause(order_by, parameters)?;
138    }
139    Ok(())
140}
141
142fn substitute_in_return_clause(
143    return_clause: &mut ReturnClause,
144    parameters: &HashMap<String, serde_json::Value>,
145) -> Result<()> {
146    for item in &mut return_clause.items {
147        substitute_in_value_expression(&mut item.expression, parameters)?;
148    }
149    Ok(())
150}
151
152fn substitute_in_order_by_clause(
153    order_by: &mut OrderByClause,
154    parameters: &HashMap<String, serde_json::Value>,
155) -> Result<()> {
156    for item in &mut order_by.items {
157        substitute_in_value_expression(&mut item.expression, parameters)?;
158    }
159    Ok(())
160}
161
162fn substitute_in_boolean_expression(
163    expr: &mut BooleanExpression,
164    parameters: &HashMap<String, serde_json::Value>,
165) -> Result<()> {
166    match expr {
167        BooleanExpression::Comparison { left, right, .. } => {
168            substitute_in_value_expression(left, parameters)?;
169            substitute_in_value_expression(right, parameters)?;
170        }
171        BooleanExpression::And(left, right) | BooleanExpression::Or(left, right) => {
172            substitute_in_boolean_expression(left, parameters)?;
173            substitute_in_boolean_expression(right, parameters)?;
174        }
175        BooleanExpression::Not(inner) => {
176            substitute_in_boolean_expression(inner, parameters)?;
177        }
178        BooleanExpression::Exists(_) => {}
179        BooleanExpression::In { expression, list } => {
180            substitute_in_value_expression(expression, parameters)?;
181            for item in list {
182                substitute_in_value_expression(item, parameters)?;
183            }
184        }
185        BooleanExpression::Like { expression, .. }
186        | BooleanExpression::ILike { expression, .. }
187        | BooleanExpression::Contains { expression, .. }
188        | BooleanExpression::StartsWith { expression, .. }
189        | BooleanExpression::EndsWith { expression, .. }
190        | BooleanExpression::IsNull(expression)
191        | BooleanExpression::IsNotNull(expression) => {
192            substitute_in_value_expression(expression, parameters)?;
193        }
194    }
195    Ok(())
196}
197
198fn substitute_in_value_expression(
199    expr: &mut ValueExpression,
200    parameters: &HashMap<String, serde_json::Value>,
201) -> Result<()> {
202    match expr {
203        ValueExpression::Parameter(name) => {
204            let param_value =
205                parameters
206                    .get(&name.to_lowercase())
207                    .ok_or_else(|| GraphError::PlanError {
208                        message: format!("Missing parameter: ${}", name),
209                        location: snafu::Location::new(file!(), line!(), column!()),
210                    })?;
211
212            // Check for array to VectorLiteral conversion
213            if let serde_json::Value::Array(arr) = param_value {
214                let mut floats = Vec::new();
215                for v in arr {
216                    if let Some(f) = v.as_f64() {
217                        floats.push(f as f32);
218                    } else {
219                        return Err(GraphError::PlanError {
220                            message: format!(
221                                "Parameter ${} is a list but contains non-numeric values. Only float vectors are supported as list parameters currently.",
222                                name
223                            ),
224                            location: snafu::Location::new(file!(), line!(), column!()),
225                        });
226                    }
227                }
228                *expr = ValueExpression::VectorLiteral(floats);
229                return Ok(());
230            }
231
232            // Scalar conversion
233            let prop_val = json_to_property_value(param_value)?;
234            *expr = ValueExpression::Literal(prop_val);
235        }
236        ValueExpression::ScalarFunction { args, .. }
237        | ValueExpression::AggregateFunction { args, .. } => {
238            for arg in args {
239                substitute_in_value_expression(arg, parameters)?;
240            }
241        }
242        ValueExpression::Arithmetic { left, right, .. } => {
243            substitute_in_value_expression(left, parameters)?;
244            substitute_in_value_expression(right, parameters)?;
245        }
246        ValueExpression::VectorDistance { left, right, .. }
247        | ValueExpression::VectorSimilarity { left, right, .. } => {
248            substitute_in_value_expression(left, parameters)?;
249            substitute_in_value_expression(right, parameters)?;
250        }
251        _ => {}
252    }
253    Ok(())
254}
255
256fn json_to_property_value(value: &serde_json::Value) -> Result<PropertyValue> {
257    match value {
258        serde_json::Value::Null => Ok(PropertyValue::Null),
259        serde_json::Value::Bool(b) => Ok(PropertyValue::Boolean(*b)),
260        serde_json::Value::Number(n) => {
261            if let Some(i) = n.as_i64() {
262                Ok(PropertyValue::Integer(i))
263            } else if let Some(f) = n.as_f64() {
264                Ok(PropertyValue::Float(f))
265            } else {
266                Err(GraphError::PlanError {
267                    message: format!("Number parameter could not be converted to i64 or f64: {}", n),
268                    location: snafu::Location::new(file!(), line!(), column!()),
269                })
270            }
271        }
272        serde_json::Value::String(s) => Ok(PropertyValue::String(s.clone())),
273        serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
274            Err(GraphError::PlanError {
275                message: "Complex types (List, Map) are not fully supported as parameters yet (except float vectors).".to_string(),
276                location: snafu::Location::new(file!(), line!(), column!()),
277            })
278        }
279    }
280}