1use akar_binder::bound_statement::{BoundMatchClause, BoundStatement};
7use akar_common::types::Value;
8use akar_parser::ast::{Clause, Expression, Query, ReturnClause, ReturnItem, WhereClause};
9use akar_planner::logical_operator::LogicalOperator;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone)]
14pub struct PreparedStatement {
15 pub query: String,
17 pub bound_statement: BoundStatement,
19 pub logical_plan: Option<Vec<LogicalOperator>>,
21 pub parameters: Vec<String>,
23}
24
25impl PreparedStatement {
26 pub fn new(query: String, bound_statement: BoundStatement) -> Self {
27 let parameters = extract_parameters(&bound_statement);
29
30 Self {
31 query,
32 bound_statement,
33 logical_plan: None,
34 parameters,
35 }
36 }
37
38 pub fn parameter_names(&self) -> &[String] {
40 &self.parameters
41 }
42
43 pub fn num_parameters(&self) -> usize {
45 self.parameters.len()
46 }
47}
48
49fn extract_parameters(bound: &BoundStatement) -> Vec<String> {
51 let mut params = Vec::new();
52 collect_params_from_statement(bound, &mut params);
53 params.sort();
54 params.dedup();
55 params
56}
57
58fn collect_params_from_statement(bound: &BoundStatement, params: &mut Vec<String>) {
59 match bound {
60 BoundStatement::BoundQuery(q) => {
61 for clause in &q.clauses {
62 match clause {
63 akar_binder::bound_statement::BoundClause::BoundMatch(m)
64 | akar_binder::bound_statement::BoundClause::BoundOptionalMatch(m)
65 | akar_binder::bound_statement::BoundClause::BoundCreate(m) => {
66 collect_params_from_match_clause(m, params);
67 }
68 akar_binder::bound_statement::BoundClause::BoundReturn(r)
69 | akar_binder::bound_statement::BoundClause::BoundWith(r) => {
70 for expr in &r.expressions {
71 collect_params_from_expr(&expr.expression, params);
72 }
73 if let Some(order_by) = &r.order_by {
74 for item in order_by {
75 collect_params_from_expr(&item.expression.expression, params);
76 }
77 }
78 if let Some(name) = &r.limit_param {
79 params.push(name.clone());
80 }
81 if let Some(name) = &r.skip_param {
82 params.push(name.clone());
83 }
84 }
85 akar_binder::bound_statement::BoundClause::BoundWhere(w) => {
86 collect_params_from_expr(&w.expression.expression, params);
87 }
88 akar_binder::bound_statement::BoundClause::BoundDelete(d) => {
89 for item in &d.items {
90 collect_params_from_expr(&item.expression, params);
91 }
92 }
93 akar_binder::bound_statement::BoundClause::BoundSet(s) => {
94 for item in &s.items {
95 collect_params_from_expr(&item.property, params);
96 collect_params_from_expr(&item.value, params);
97 }
98 }
99 akar_binder::bound_statement::BoundClause::BoundUnwind(u) => {
100 collect_params_from_expr(&u.expression, params);
101 }
102 akar_binder::bound_statement::BoundClause::BoundForeach(f) => {
103 collect_params_from_expr(&f.expression, params);
104 for sub in &f.sub_statements {
105 collect_params_from_statement(sub, params);
106 }
107 }
108 akar_binder::bound_statement::BoundClause::BoundMerge(m) => {
109 for (_, v) in &m.properties {
110 collect_params_from_expr(v, params);
111 }
112 for item in m.on_create.iter().chain(m.on_match.iter()) {
113 collect_params_from_expr(&item.property, params);
114 collect_params_from_expr(&item.value, params);
115 }
116 }
117 }
118 }
119 }
120 BoundStatement::BoundCreateDml(c) => {
121 for p in &c.patterns {
122 if let Some(n) = &p.node {
123 for (_, v) in &n.properties {
124 collect_params_from_expr(v, params);
125 }
126 }
127 if let Some(e) = &p.edge {
128 for (_, v) in &e.properties {
129 collect_params_from_expr(v, params);
130 }
131 }
132 }
133 }
134 BoundStatement::BoundMerge(m) => {
135 for (_, v) in &m.properties {
136 collect_params_from_expr(v, params);
137 }
138 for p in &m.patterns {
139 if let Some(n) = &p.node {
140 for (_, v) in &n.properties {
141 collect_params_from_expr(v, params);
142 }
143 }
144 if let Some(e) = &p.edge {
145 for (_, v) in &e.properties {
146 collect_params_from_expr(v, params);
147 }
148 }
149 }
150 for item in &m.on_create {
151 collect_params_from_expr(&item.property, params);
152 collect_params_from_expr(&item.value, params);
153 }
154 for item in &m.on_match {
155 collect_params_from_expr(&item.property, params);
156 collect_params_from_expr(&item.value, params);
157 }
158 }
159 BoundStatement::BoundExplain(e) => {
160 collect_params_from_statement(&e.inner, params);
161 }
162 _ => {}
163 }
164}
165
166fn collect_params_from_match_clause(m: &BoundMatchClause, params: &mut Vec<String>) {
167 for p in &m.patterns {
168 for (_, v) in &p.properties {
169 collect_params_from_expr(v, params);
170 }
171 if let Some(e) = &p.edge {
172 for (_, v) in &e.properties {
173 collect_params_from_expr(v, params);
174 }
175 }
176 }
177}
178
179fn collect_params_from_expr(expr: &Expression, params: &mut Vec<String>) {
180 match expr {
181 Expression::Parameter(name) => {
182 params.push(name.clone());
183 }
184 Expression::PropertyAccess(obj, _) => {
185 collect_params_from_expr(obj, params);
186 }
187 Expression::FunctionCall(_, args) => {
188 for arg in args {
189 collect_params_from_expr(arg, params);
190 }
191 }
192 Expression::BinaryOp(_, left, right) => {
193 collect_params_from_expr(left, params);
194 collect_params_from_expr(right, params);
195 }
196 Expression::UnaryOp(_, inner) => {
197 collect_params_from_expr(inner, params);
198 }
199 Expression::List(items) => {
200 for item in items {
201 collect_params_from_expr(item, params);
202 }
203 }
204 Expression::Map(entries) => {
205 for (_, val) in entries {
206 collect_params_from_expr(val, params);
207 }
208 }
209 Expression::Variable(_) | Expression::Constant(_) => {}
210 Expression::ExistsSubquery(q) => {
211 for clause in &q.clauses {
212 match clause {
213 Clause::Where(w) => collect_params_from_expr(&w.expression, params),
214 Clause::Return(r) => {
215 for item in &r.expressions {
216 collect_params_from_expr(&item.expression, params);
217 }
218 }
219 _ => {}
220 }
221 }
222 }
223 Expression::Case(case_expr) => {
224 if let Some(subj) = &case_expr.subject {
225 collect_params_from_expr(subj, params);
226 }
227 for alt in &case_expr.alternatives {
228 collect_params_from_expr(&alt.when, params);
229 collect_params_from_expr(&alt.then, params);
230 }
231 if let Some(else_e) = &case_expr.else_expr {
232 collect_params_from_expr(else_e, params);
233 }
234 }
235 Expression::Star => {}
236 Expression::ListPredicate { list, predicate, .. } => {
237 collect_params_from_expr(list, params);
238 collect_params_from_expr(predicate, params);
239 }
240 Expression::Lambda { body, .. } => {
241 collect_params_from_expr(body, params);
242 }
243 }
244}
245
246pub fn substitute_params(expr: &Expression, param_values: &HashMap<String, Value>) -> Result<Expression, String> {
248 match expr {
249 Expression::Parameter(name) => {
250 let value = param_values
251 .get(name)
252 .ok_or_else(|| format!("Missing parameter: ${}", name))?;
253 Ok(value_to_expression(value)?)
254 }
255 Expression::PropertyAccess(obj, prop) => {
256 let new_obj = substitute_params(obj, param_values)?;
257 Ok(Expression::PropertyAccess(Box::new(new_obj), prop.clone()))
258 }
259 Expression::FunctionCall(name, args) => {
260 let new_args: Result<Vec<_>, _> = args.iter().map(|a| substitute_params(a, param_values)).collect();
261 Ok(Expression::FunctionCall(name.clone(), new_args?))
262 }
263 Expression::BinaryOp(op, left, right) => {
264 let new_left = substitute_params(left, param_values)?;
265 let new_right = substitute_params(right, param_values)?;
266 Ok(Expression::BinaryOp(*op, Box::new(new_left), Box::new(new_right)))
267 }
268 Expression::UnaryOp(op, inner) => {
269 let new_inner = substitute_params(inner, param_values)?;
270 Ok(Expression::UnaryOp(*op, Box::new(new_inner)))
271 }
272 Expression::List(items) => {
273 let new_items: Result<Vec<_>, _> = items.iter().map(|i| substitute_params(i, param_values)).collect();
274 Ok(Expression::List(new_items?))
275 }
276 Expression::Map(entries) => {
277 let new_entries: Result<Vec<(String, Expression)>, String> = entries
278 .iter()
279 .map(|(k, v)| Ok((k.clone(), substitute_params(v, param_values)?)))
280 .collect();
281 Ok(Expression::Map(new_entries?))
282 }
283 Expression::Variable(_) | Expression::Constant(_) => Ok(expr.clone()),
285 Expression::ExistsSubquery(q) => Ok(Expression::ExistsSubquery(Box::new(substitute_params_in_query(
286 q,
287 param_values,
288 )?))),
289 Expression::Case(case_expr) => {
290 use akar_parser::ast::{CaseAlternative, CaseExpr};
291 let subject = if let Some(subj) = &case_expr.subject {
292 Some(Box::new(substitute_params(subj, param_values)?))
293 } else {
294 None
295 };
296 let alternatives: Result<Vec<CaseAlternative>, String> = case_expr
297 .alternatives
298 .iter()
299 .map(|alt| {
300 Ok(CaseAlternative {
301 when: substitute_params(&alt.when, param_values)?,
302 then: substitute_params(&alt.then, param_values)?,
303 })
304 })
305 .collect();
306 let else_expr = if let Some(e) = &case_expr.else_expr {
307 Some(Box::new(substitute_params(e, param_values)?))
308 } else {
309 None
310 };
311 Ok(Expression::Case(CaseExpr {
312 subject,
313 alternatives: alternatives?,
314 else_expr,
315 }))
316 }
317 Expression::Star => Ok(expr.clone()),
318 Expression::ListPredicate {
319 quantifier,
320 list,
321 var_name,
322 predicate,
323 } => {
324 let new_list = substitute_params(list, param_values)?;
325 let new_predicate = substitute_params(predicate, param_values)?;
326 Ok(Expression::ListPredicate {
327 quantifier: *quantifier,
328 list: Box::new(new_list),
329 var_name: var_name.clone(),
330 predicate: Box::new(new_predicate),
331 })
332 }
333 Expression::Lambda { var_name, body } => {
334 let new_body = substitute_params(body, param_values)?;
335 Ok(Expression::Lambda {
336 var_name: var_name.clone(),
337 body: Box::new(new_body),
338 })
339 }
340 }
341}
342
343fn substitute_params_in_query(query: &Query, param_values: &HashMap<String, Value>) -> Result<Query, String> {
345 let mut new_clauses = Vec::new();
346 for clause in &query.clauses {
347 let new_clause = match clause {
348 Clause::Where(w) => {
349 let new_expr = substitute_params(&w.expression, param_values)?;
350 Clause::Where(WhereClause { expression: new_expr })
351 }
352 Clause::Return(r) => {
353 let new_items: Result<Vec<ReturnItem>, String> = r
354 .expressions
355 .iter()
356 .map(|item| {
357 let new_expr = substitute_params(&item.expression, param_values)?;
358 Ok(ReturnItem {
359 expression: new_expr,
360 alias: item.alias.clone(),
361 })
362 })
363 .collect();
364 Clause::Return(ReturnClause {
365 expressions: new_items?,
366 distinct: r.distinct,
367 order_by: r.order_by.clone(),
368 limit: r.limit,
369 skip: r.skip,
370 limit_param: r.limit_param.clone(),
371 skip_param: r.skip_param.clone(),
372 })
373 }
374 other => other.clone(),
375 };
376 new_clauses.push(new_clause);
377 }
378 Ok(Query { clauses: new_clauses })
379}
380
381fn value_to_constant(value: &Value) -> Result<akar_parser::ast::Constant, String> {
388 use akar_parser::ast::Constant;
389 match value {
390 Value::Null => Ok(Constant::Null),
391 Value::Bool(b) => Ok(Constant::Bool(*b)),
392 Value::Int64(n) => Ok(Constant::Integer(*n)),
393 Value::Int32(n) => Ok(Constant::Integer(*n as i64)),
394 Value::Int16(n) => Ok(Constant::Integer(*n as i64)),
395 Value::Int8(n) => Ok(Constant::Integer(*n as i64)),
396 Value::UInt64(n) => i64::try_from(*n)
397 .map(Constant::Integer)
398 .map_err(|_| format!("UInt64 parameter {n} exceeds i64 range and cannot be used in a query")),
399 Value::UInt32(n) => Ok(Constant::Integer(*n as i64)),
400 Value::UInt16(n) => Ok(Constant::Integer(*n as i64)),
401 Value::UInt8(n) => Ok(Constant::Integer(*n as i64)),
402 Value::Int128(n) => i64::try_from(*n)
403 .map(Constant::Integer)
404 .map_err(|_| format!("Int128 parameter {n} exceeds i64 range and cannot be used in a query")),
405 Value::Double(f) => Ok(Constant::Float(*f)),
406 Value::Float(f) => Ok(Constant::Float(*f as f64)),
407 Value::String(s) => Ok(Constant::String(s.clone())),
408 Value::Blob(_) => Err("BLOB parameters are not supported in queries".into()),
409 other => Err(format!(
410 "Parameter type {:?} cannot be used in a query",
411 std::mem::discriminant(other)
412 )),
413 }
414}
415
416fn value_to_expression(value: &Value) -> Result<akar_parser::ast::Expression, String> {
422 use akar_parser::ast::Expression;
423 match value {
424 Value::List(items) => {
425 let mut exprs = Vec::with_capacity(items.len());
426 for item in items {
427 exprs.push(value_to_expression(item)?);
428 }
429 Ok(Expression::List(exprs))
430 }
431 Value::Struct(fields) => {
434 let mut entries = Vec::with_capacity(fields.len());
435 for (name, v) in fields {
436 entries.push((name.clone(), value_to_expression(v)?));
437 }
438 Ok(Expression::Map(entries))
439 }
440 Value::Map(pairs) => {
441 let mut entries = Vec::with_capacity(pairs.len());
442 for (k, v) in pairs {
443 let key = match k {
444 Value::String(s) => s.clone(),
445 other => return Err(format!("Map key {other:?} is not a string")),
446 };
447 entries.push((key, value_to_expression(v)?));
448 }
449 Ok(Expression::Map(entries))
450 }
451 other => Ok(Expression::Constant(value_to_constant(other)?)),
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459 use akar_common::types::Value;
460 use akar_parser::ast::*;
461 use std::collections::HashMap;
462
463 #[test]
464 fn test_extract_parameters_simple() {
465 let expr = Expression::BinaryOp(
466 BinaryOp::GreaterThan,
467 Box::new(Expression::PropertyAccess(
468 Box::new(Expression::Variable("p".into())),
469 "age".into(),
470 )),
471 Box::new(Expression::Parameter("min_age".into())),
472 );
473 let mut params = Vec::new();
474 collect_params_from_expr(&expr, &mut params);
475 assert_eq!(params, vec!["min_age"]);
476 }
477
478 #[test]
479 fn test_extract_multiple_params() {
480 let expr = Expression::BinaryOp(
481 BinaryOp::And,
482 Box::new(Expression::BinaryOp(
483 BinaryOp::GreaterThan,
484 Box::new(Expression::PropertyAccess(
485 Box::new(Expression::Variable("p".into())),
486 "age".into(),
487 )),
488 Box::new(Expression::Parameter("min_age".into())),
489 )),
490 Box::new(Expression::BinaryOp(
491 BinaryOp::LessThan,
492 Box::new(Expression::PropertyAccess(
493 Box::new(Expression::Variable("p".into())),
494 "age".into(),
495 )),
496 Box::new(Expression::Parameter("max_age".into())),
497 )),
498 );
499 let mut params = Vec::new();
500 collect_params_from_expr(&expr, &mut params);
501 params.sort();
502 assert_eq!(params, vec!["max_age", "min_age"]);
503 }
504
505 #[test]
506 fn test_substitute_params() {
507 let expr = Expression::BinaryOp(
508 BinaryOp::Equal,
509 Box::new(Expression::Variable("p".into())),
510 Box::new(Expression::Parameter("name".into())),
511 );
512 let mut params = HashMap::new();
513 params.insert("name".into(), Value::String("Alice".into()));
514
515 let substituted = substitute_params(&expr, ¶ms).unwrap();
516 match substituted {
517 Expression::BinaryOp(_, _, right) => match *right {
518 Expression::Constant(Constant::String(s)) => {
519 assert_eq!(s, "Alice");
520 }
521 _ => panic!("Expected constant string"),
522 },
523 _ => panic!("Expected binary op"),
524 }
525 }
526
527 #[test]
528 fn test_substitute_missing_param() {
529 let expr = Expression::BinaryOp(
530 BinaryOp::Equal,
531 Box::new(Expression::Variable("p".into())),
532 Box::new(Expression::Parameter("missing".into())),
533 );
534 let params = HashMap::new();
535 assert!(substitute_params(&expr, ¶ms).is_err());
536 }
537
538 #[test]
539 fn test_value_to_constant() {
540 assert_eq!(value_to_constant(&Value::Int64(42)), Ok(Constant::Integer(42)));
541 assert_eq!(
542 value_to_constant(&Value::String("hi".into())),
543 Ok(Constant::String("hi".into()))
544 );
545 assert_eq!(value_to_constant(&Value::Bool(true)), Ok(Constant::Bool(true)));
546 assert_eq!(value_to_constant(&Value::Double(3.15)), Ok(Constant::Float(3.15)));
547 assert_eq!(value_to_constant(&Value::Null), Ok(Constant::Null));
548 }
549
550 #[test]
551 fn test_value_to_constant_uint64_overflow_errors() {
552 assert_eq!(
554 value_to_constant(&Value::UInt64(i64::MAX as u64 + 1)),
555 Err("UInt64 parameter 9223372036854775808 exceeds i64 range and cannot be used in a query".into())
556 );
557 assert_eq!(
558 value_to_constant(&Value::UInt64(u64::MAX)),
559 Err("UInt64 parameter 18446744073709551615 exceeds i64 range and cannot be used in a query".into())
560 );
561 assert_eq!(
563 value_to_constant(&Value::UInt64(i64::MAX as u64)),
564 Ok(Constant::Integer(i64::MAX))
565 );
566 }
567
568 #[test]
569 fn test_value_to_constant_blob_errors() {
570 assert_eq!(
571 value_to_constant(&Value::Blob(vec![1, 2, 3])),
572 Err("BLOB parameters are not supported in queries".into())
573 );
574 }
575
576 #[test]
577 fn test_no_params() {
578 let expr = Expression::BinaryOp(
579 BinaryOp::Equal,
580 Box::new(Expression::Variable("a".into())),
581 Box::new(Expression::Variable("b".into())),
582 );
583 let mut params = Vec::new();
584 collect_params_from_expr(&expr, &mut params);
585 assert!(params.is_empty());
586 }
587
588 #[test]
589 fn test_value_to_expression_list() {
590 let val = Value::List(vec![Value::Int64(1), Value::Int64(2), Value::Int64(3)]);
591 let expr = value_to_expression(&val).unwrap();
592 match expr {
593 Expression::List(items) => {
594 assert_eq!(items.len(), 3);
595 for (i, item) in items.iter().enumerate() {
597 match item {
598 Expression::Constant(Constant::Integer(n)) => {
599 assert_eq!(*n, (i + 1) as i64);
600 }
601 other => panic!("expected Integer constant at {i}, got {other:?}"),
602 }
603 }
604 }
605 other => panic!("expected List, got {other:?}"),
606 }
607 }
608
609 #[test]
610 fn test_value_to_expression_nested_list() {
611 let val = Value::List(vec![
612 Value::List(vec![Value::Int64(1), Value::Int64(2)]),
613 Value::List(vec![Value::Int64(3), Value::Int64(4)]),
614 ]);
615 let expr = value_to_expression(&val).unwrap();
616 match expr {
617 Expression::List(outer) => {
618 assert_eq!(outer.len(), 2);
619 match &outer[0] {
620 Expression::List(inner) => {
621 assert_eq!(inner.len(), 2);
622 }
623 other => panic!("expected inner List, got {other:?}"),
624 }
625 }
626 other => panic!("expected List, got {other:?}"),
627 }
628 }
629}