1use std::collections::HashMap;
2
3use marsdb_graph::PropertyValue;
4
5use crate::ast::{
6 CallClause, CallYield, Expr, Literal, MergeClause, NodePattern, Pattern, QueryClause,
7 QueryPart, ReturnExpr, ReturnTail, SetItem, Statement, Tail, UnwindClause, WithClause,
8 WithExpr,
9};
10use crate::error::QueryError;
11
12pub fn substitute_params(
17 stmt: &mut Statement,
18 params: &HashMap<String, PropertyValue>,
19) -> Result<(), QueryError> {
20 match stmt {
21 Statement::Begin | Statement::Commit | Statement::Rollback => {}
23 Statement::Create(patterns) => {
24 for pattern in patterns {
25 substitute_pattern(pattern, params)?;
26 }
27 }
28 Statement::CreateIndex { .. } => {}
31 Statement::Explain(inner) => substitute_params(inner, params)?,
32 Statement::Match {
33 clauses,
34 tail,
35 order_by,
36 skip,
37 limit,
38 } => {
39 for clause in clauses {
40 substitute_query_clause(clause, params)?;
41 }
42 if let Some(tail) = tail {
43 substitute_tail(tail, params)?;
44 }
45 if let Some(items) = order_by {
46 for (expr, _) in items {
47 substitute_return_expr(expr, params)?;
48 }
49 }
50 if let Some(expr) = skip {
51 substitute_return_expr(expr, params)?;
52 }
53 if let Some(expr) = limit {
54 substitute_return_expr(expr, params)?;
55 }
56 }
57 Statement::Union { parts, .. } => {
58 for part in parts {
59 substitute_params(part, params)?;
60 }
61 }
62 Statement::StandaloneCall(call) => substitute_call_clause(call, params)?,
63 }
64 Ok(())
65}
66
67fn substitute_call_clause(
73 call: &mut CallClause,
74 params: &HashMap<String, PropertyValue>,
75) -> Result<(), QueryError> {
76 if let Some(args) = &mut call.args {
77 for arg in args {
78 substitute_return_expr(arg, params)?;
79 }
80 }
81 if let Some(CallYield::Items(_, Some(where_expr))) = &mut call.yield_items {
82 substitute_expr(where_expr, params)?;
83 }
84 Ok(())
85}
86
87fn substitute_query_clause(
88 clause: &mut QueryClause,
89 params: &HashMap<String, PropertyValue>,
90) -> Result<(), QueryError> {
91 match clause {
92 QueryClause::Match(part) => substitute_query_part(part, params),
93 QueryClause::Unwind(u) => substitute_unwind_clause(u, params),
94 QueryClause::Merge(m) => substitute_merge_clause(m, params),
95 QueryClause::With(with) => substitute_with_clause(with, params),
96 QueryClause::Set(items) => {
97 for item in items {
98 substitute_set_item(item, params)?;
99 }
100 Ok(())
101 }
102 QueryClause::Delete { items, detach: _ } => {
103 for expr in items {
104 substitute_return_expr(expr, params)?;
105 }
106 Ok(())
107 }
108 QueryClause::Remove(_) => Ok(()),
111 QueryClause::Create(patterns) => {
112 for pattern in patterns {
113 substitute_pattern(pattern, params)?;
114 }
115 Ok(())
116 }
117 QueryClause::Call(call) => substitute_call_clause(call, params),
118 }
119}
120
121fn substitute_set_item(
127 item: &mut SetItem,
128 params: &HashMap<String, PropertyValue>,
129) -> Result<(), QueryError> {
130 match item {
131 SetItem::Prop(_, value) | SetItem::MapAssign { value, .. } => {
132 substitute_return_expr(value, params)
133 }
134 SetItem::Labels(..) => Ok(()),
135 }
136}
137
138fn substitute_merge_clause(
139 m: &mut MergeClause,
140 params: &HashMap<String, PropertyValue>,
141) -> Result<(), QueryError> {
142 substitute_pattern(&mut m.pattern, params)?;
143 for item in m.on_create.iter_mut().chain(m.on_match.iter_mut()) {
144 substitute_set_item(item, params)?;
145 }
146 if let Some(with) = &mut m.with {
147 substitute_with_clause(with, params)?;
148 }
149 Ok(())
150}
151
152fn substitute_query_part(
153 part: &mut QueryPart,
154 params: &HashMap<String, PropertyValue>,
155) -> Result<(), QueryError> {
156 substitute_pattern(&mut part.pattern, params)?;
157 if let Some(expr) = &mut part.where_clause {
158 substitute_expr(expr, params)?;
159 }
160 if let Some(with) = &mut part.with {
161 substitute_with_clause(with, params)?;
162 }
163 Ok(())
164}
165
166fn substitute_unwind_clause(
167 u: &mut UnwindClause,
168 params: &HashMap<String, PropertyValue>,
169) -> Result<(), QueryError> {
170 substitute_return_expr(&mut u.source.0, params)?;
171 if let Some(expr) = &mut u.where_clause {
172 substitute_with_expr(expr, params)?;
173 }
174 if let Some(with) = &mut u.with {
175 substitute_with_clause(with, params)?;
176 }
177 Ok(())
178}
179
180fn substitute_with_clause(
181 with: &mut WithClause,
182 params: &HashMap<String, PropertyValue>,
183) -> Result<(), QueryError> {
184 for item in &mut with.items {
185 substitute_return_expr(&mut item.expr, params)?;
186 }
187 if let Some(where_clause) = &mut with.where_clause {
188 substitute_with_expr(where_clause, params)?;
189 }
190 if let Some(items) = &mut with.order_by {
191 for (expr, _) in items {
192 substitute_return_expr(expr, params)?;
193 }
194 }
195 if let Some(expr) = &mut with.skip {
196 substitute_return_expr(expr, params)?;
197 }
198 if let Some(expr) = &mut with.limit {
199 substitute_return_expr(expr, params)?;
200 }
201 Ok(())
202}
203
204fn substitute_with_expr(
205 expr: &mut WithExpr,
206 params: &HashMap<String, PropertyValue>,
207) -> Result<(), QueryError> {
208 match expr {
209 WithExpr::And(l, r) | WithExpr::Or(l, r) => {
210 substitute_with_expr(l, params)?;
211 substitute_with_expr(r, params)?;
212 }
213 WithExpr::Not(e) => substitute_with_expr(e, params)?,
214 WithExpr::Compare(lhs, _, rhs) => {
215 substitute_return_expr(lhs, params)?;
216 substitute_return_expr(rhs, params)?;
217 }
218 WithExpr::IsNull(e) => substitute_return_expr(e, params)?,
219 WithExpr::Bare(e) => substitute_return_expr(e, params)?,
220 }
221 Ok(())
222}
223
224fn substitute_pattern(
225 pattern: &mut Pattern,
226 params: &HashMap<String, PropertyValue>,
227) -> Result<(), QueryError> {
228 substitute_node(&mut pattern.start, params)?;
229 for (rel, node) in &mut pattern.hops {
230 for (_, expr) in &mut rel.props {
231 substitute_return_expr(expr, params)?;
232 }
233 substitute_node(node, params)?;
234 }
235 Ok(())
236}
237
238fn substitute_node(
239 node: &mut NodePattern,
240 params: &HashMap<String, PropertyValue>,
241) -> Result<(), QueryError> {
242 for (_, expr) in &mut node.props {
243 substitute_return_expr(expr, params)?;
244 }
245 Ok(())
246}
247
248fn substitute_expr(
249 expr: &mut Expr,
250 params: &HashMap<String, PropertyValue>,
251) -> Result<(), QueryError> {
252 match expr {
253 Expr::And(l, r) | Expr::Or(l, r) => {
254 substitute_expr(l, params)?;
255 substitute_expr(r, params)?;
256 }
257 Expr::Not(e) => substitute_expr(e, params)?,
258 Expr::Compare(_, _, lit) => substitute_literal(lit, params)?,
259 Expr::PropCompare(_, _, _) => {}
260 Expr::IsNull(_) => {}
261 Expr::HasLabel(_, _) => {}
262 Expr::VarEq(_, _) => {}
263 Expr::EdgeNotInSet { .. } => {}
268 Expr::GeneralCompare(lhs, _, rhs) => {
269 substitute_return_expr(lhs, params)?;
270 substitute_return_expr(rhs, params)?;
271 }
272 Expr::GeneralIsNull(e) => substitute_return_expr(e, params)?,
273 Expr::GeneralBare(e) => substitute_return_expr(e, params)?,
274 Expr::Pattern(pattern) => substitute_pattern(pattern, params)?,
275 Expr::Exists {
276 pattern,
277 where_clause,
278 } => {
279 substitute_pattern(pattern, params)?;
280 if let Some(w) = where_clause {
281 substitute_expr(w, params)?;
282 }
283 }
284 Expr::ExistsSubquery(stmt) => substitute_params(stmt, params)?,
285 }
286 Ok(())
287}
288
289fn substitute_tail(
290 tail: &mut Tail,
291 params: &HashMap<String, PropertyValue>,
292) -> Result<(), QueryError> {
293 match tail {
294 Tail::Return(items, _) => {
295 for item in items {
296 substitute_return_expr(&mut item.expr, params)?;
297 }
298 }
299 Tail::ReturnStar(_) => {}
301 Tail::Delete(exprs, ret) | Tail::DetachDelete(exprs, ret) => {
302 for expr in exprs {
303 substitute_return_expr(expr, params)?;
304 }
305 substitute_return_tail(ret, params)?;
306 }
307 Tail::Remove(_, ret) => {
308 substitute_return_tail(ret, params)?;
309 }
310 Tail::Set(items, ret) => {
311 for item in items {
312 substitute_set_item(item, params)?;
313 }
314 substitute_return_tail(ret, params)?;
315 }
316 Tail::Create(patterns, ret) => {
317 for pattern in patterns {
318 substitute_pattern(pattern, params)?;
319 }
320 substitute_return_tail(ret, params)?;
321 }
322 }
323 Ok(())
324}
325
326fn substitute_return_tail(
331 ret: &mut Option<ReturnTail>,
332 params: &HashMap<String, PropertyValue>,
333) -> Result<(), QueryError> {
334 if let Some(rt) = ret {
335 for item in &mut rt.items {
336 substitute_return_expr(&mut item.expr, params)?;
337 }
338 }
339 Ok(())
340}
341
342fn substitute_return_expr(
343 expr: &mut ReturnExpr,
344 params: &HashMap<String, PropertyValue>,
345) -> Result<(), QueryError> {
346 match expr {
347 ReturnExpr::Var(_) | ReturnExpr::Prop(_) | ReturnExpr::CountStar => {}
348 ReturnExpr::PatternPredicate(pattern) => substitute_pattern(pattern, params)?,
349 ReturnExpr::Lit(Literal::Param(name)) => {
360 let value = params
361 .get(name)
362 .ok_or_else(|| QueryError::MissingParam(name.clone()))?
363 .clone();
364 *expr = property_value_to_return_expr(name, &value)?;
365 }
366 ReturnExpr::Lit(_) => {}
367 ReturnExpr::Call { args, .. } => {
368 for arg in args {
369 substitute_return_expr(arg, params)?;
370 }
371 }
372 ReturnExpr::Case { test, whens, else_ } => {
373 if let Some(t) = test {
374 substitute_return_expr(t, params)?;
375 }
376 for (when, then) in whens {
377 substitute_return_expr(when, params)?;
378 substitute_return_expr(then, params)?;
379 }
380 if let Some(e) = else_ {
381 substitute_return_expr(e, params)?;
382 }
383 }
384 ReturnExpr::Arith(l, _, r) => {
385 substitute_return_expr(l, params)?;
386 substitute_return_expr(r, params)?;
387 }
388 ReturnExpr::Neg(e) => substitute_return_expr(e, params)?,
389 ReturnExpr::ListLit(items) => {
390 for item in items {
391 substitute_return_expr(item, params)?;
392 }
393 }
394 ReturnExpr::Index(base, index) => {
395 substitute_return_expr(base, params)?;
396 substitute_return_expr(index, params)?;
397 }
398 ReturnExpr::PropOf(base, _) => substitute_return_expr(base, params)?,
399 ReturnExpr::Slice(base, start, end) => {
400 substitute_return_expr(base, params)?;
401 if let Some(s) = start {
402 substitute_return_expr(s, params)?;
403 }
404 if let Some(e) = end {
405 substitute_return_expr(e, params)?;
406 }
407 }
408 ReturnExpr::ListComp {
409 source,
410 where_clause,
411 project,
412 ..
413 } => {
414 substitute_return_expr(source, params)?;
415 if let Some(w) = where_clause {
416 substitute_return_expr(w, params)?;
417 }
418 if let Some(p) = project {
419 substitute_return_expr(p, params)?;
420 }
421 }
422 ReturnExpr::Quantifier {
423 source,
424 where_clause,
425 ..
426 } => {
427 substitute_return_expr(source, params)?;
428 if let Some(w) = where_clause {
429 substitute_return_expr(w, params)?;
430 }
431 }
432 ReturnExpr::MapLit(entries) => {
433 for (_, v) in entries {
434 substitute_return_expr(v, params)?;
435 }
436 }
437 ReturnExpr::And(l, r) | ReturnExpr::Or(l, r) | ReturnExpr::Xor(l, r) => {
438 substitute_return_expr(l, params)?;
439 substitute_return_expr(r, params)?;
440 }
441 ReturnExpr::Not(e) => substitute_return_expr(e, params)?,
442 ReturnExpr::Compare(l, _, r) => {
443 substitute_return_expr(l, params)?;
444 substitute_return_expr(r, params)?;
445 }
446 ReturnExpr::IsNull(e) => substitute_return_expr(e, params)?,
447 ReturnExpr::In(needle, haystack) => {
448 substitute_return_expr(needle, params)?;
449 substitute_return_expr(haystack, params)?;
450 }
451 ReturnExpr::HasLabel(..) => {}
454 ReturnExpr::PatternComprehension {
455 pattern,
456 where_clause,
457 projection,
458 ..
459 } => {
460 substitute_pattern(pattern, params)?;
461 if let Some(w) = where_clause {
462 substitute_expr(w, params)?;
463 }
464 substitute_return_expr(projection, params)?;
465 }
466 ReturnExpr::ExistsPattern {
467 pattern,
468 where_clause,
469 } => {
470 substitute_pattern(pattern, params)?;
471 if let Some(w) = where_clause {
472 substitute_expr(w, params)?;
473 }
474 }
475 ReturnExpr::ExistsSubquery(stmt) => substitute_params(stmt, params)?,
476 }
477 Ok(())
478}
479
480fn substitute_literal(
481 lit: &mut Literal,
482 params: &HashMap<String, PropertyValue>,
483) -> Result<(), QueryError> {
484 if let Literal::Param(name) = lit {
485 let value = params
486 .get(name)
487 .ok_or_else(|| QueryError::MissingParam(name.clone()))?;
488 *lit = property_value_to_literal(name, value)?;
489 }
490 Ok(())
491}
492
493fn property_value_to_literal(name: &str, pv: &PropertyValue) -> Result<Literal, QueryError> {
494 Ok(match pv {
495 PropertyValue::Null => Literal::Null,
496 PropertyValue::Bool(b) => Literal::Bool(*b),
497 PropertyValue::Int(i) => Literal::Int(*i),
498 PropertyValue::Float(f) => Literal::Float(*f),
499 PropertyValue::String(s) => Literal::String(s.clone()),
500 PropertyValue::Date(_)
514 | PropertyValue::Duration { .. }
515 | PropertyValue::LocalTime(_)
516 | PropertyValue::Time { .. }
517 | PropertyValue::LocalDateTime { .. }
518 | PropertyValue::DateTime { .. } => {
519 return Err(QueryError::Type(format!(
520 "${name}: passing a temporal value as a query parameter isn't supported in a \
521 pattern-level property comparison (only in ordinary expression position)"
522 )))
523 }
524 PropertyValue::List(_) => {
537 return Err(QueryError::Type(format!(
538 "${name}: a list-valued query parameter can't be used here (only in ordinary \
539 expression position, not a pattern-level property comparison)"
540 )))
541 }
542 PropertyValue::Map(_) => {
548 return Err(QueryError::Type(format!(
549 "${name}: a map-valued query parameter can't be used here (only in ordinary \
550 expression position, not a pattern-level property comparison)"
551 )))
552 }
553 })
554}
555
556fn property_value_to_return_expr(name: &str, pv: &PropertyValue) -> Result<ReturnExpr, QueryError> {
564 Ok(match pv {
565 PropertyValue::List(items) => ReturnExpr::ListLit(
566 items
567 .iter()
568 .map(|item| property_value_to_return_expr(name, item))
569 .collect::<Result<Vec<_>, _>>()?,
570 ),
571 PropertyValue::Map(entries) => ReturnExpr::MapLit(
572 entries
573 .iter()
574 .map(|(key, value)| Ok((key.clone(), property_value_to_return_expr(name, value)?)))
575 .collect::<Result<Vec<_>, QueryError>>()?,
576 ),
577 PropertyValue::Date(d) => temporal_call("date", crate::temporal::format_date(*d)),
586 PropertyValue::Duration {
587 months,
588 days,
589 seconds,
590 nanos,
591 } => temporal_call(
592 "duration",
593 crate::temporal::format_duration(*months, *days, *seconds, *nanos),
594 ),
595 PropertyValue::LocalTime(nanos_of_day) => temporal_call(
596 "localtime",
597 crate::temporal::format_local_time(*nanos_of_day),
598 ),
599 PropertyValue::Time {
600 nanos_of_day,
601 offset_seconds,
602 } => temporal_call(
603 "time",
604 crate::temporal::format_time(*nanos_of_day, *offset_seconds),
605 ),
606 PropertyValue::LocalDateTime {
607 epoch_seconds,
608 nanos,
609 } => temporal_call(
610 "localdatetime",
611 crate::temporal::format_local_date_time(*epoch_seconds, *nanos),
612 ),
613 PropertyValue::DateTime {
614 epoch_seconds,
615 nanos,
616 zone,
617 } => temporal_call(
618 "datetime",
619 crate::temporal::format_date_time(
620 *epoch_seconds,
621 *nanos,
622 &crate::executor::tz_from_graph(zone),
623 ),
624 ),
625 other => ReturnExpr::Lit(property_value_to_literal(name, other)?),
626 })
627}
628
629fn temporal_call(name: &str, formatted: String) -> ReturnExpr {
630 ReturnExpr::Call {
631 name: name.to_string(),
632 args: vec![ReturnExpr::Lit(Literal::String(formatted))],
633 distinct: false,
634 }
635}