1use crate::storage::engine::vector_metadata::{MetadataFilter, MetadataValue};
2use crate::storage::query::ast::{
3 Expr, FusionStrategy, GraphPattern, GraphQuery, HybridQuery, JoinQuery, NodePattern,
4 NodeSelector, OrderByClause, PathQuery, Projection, PropertyFilter, QueryExpr, SelectItem,
5 TableQuery, TableSource, VectorQuery, VectorSource,
6};
7use crate::storage::query::sql_lowering::{
8 expr_to_filter, filter_to_expr, projection_from_literal,
9};
10use crate::storage::schema::Value;
11
12const PROJECTION_PARAM_PREFIX: &str = "__shape_projection_param__:";
13const STRING_PARAM_PREFIX: &str = "__shape_string_param__:";
14const VALUE_PARAM_PREFIX: &str = "__shape_value_param__:";
15const ROW_SELECTOR_TABLE_PREFIX: &str = "__shape_row_selector__:";
16const METADATA_VALUE_PARAM_PREFIX: &str = "__shape_metadata_value_param__:";
17const VECTOR_TEXT_PARAM_PREFIX: &str = "__shape_vector_text_param__:";
18const VECTOR_REF_ID_PREFIX: &str = "__shape_vector_ref_id__:";
19const FLOAT32_PARAM_BITS_BASE: u32 = 0x7fc0_0000;
20const FLOAT64_PARAM_BITS_BASE: u64 = 0x7ff8_0000_0000_0000;
21const U32_PARAM_BASE: u32 = 0xfff0_0000;
22
23#[derive(Debug, Clone)]
24pub struct ParameterizedQuery {
25 pub shape: QueryExpr,
26 pub parameter_count: usize,
27}
28
29pub fn parameterize_query_expr(expr: &QueryExpr) -> Option<ParameterizedQuery> {
30 let mut next_index = 0usize;
31 let shape = parameterize_query_expr_inner(expr, &mut next_index)?;
32 Some(ParameterizedQuery {
33 shape,
34 parameter_count: next_index,
35 })
36}
37
38pub fn bind_user_param_query(expr: &QueryExpr, params: &[Value]) -> Option<QueryExpr> {
48 bind_query_expr_inner(expr, params)
49}
50
51pub fn bind_parameterized_query(
52 expr: &QueryExpr,
53 binds: &[Value],
54 parameter_count: usize,
55) -> Option<QueryExpr> {
56 if binds.len() != parameter_count {
57 return None;
58 }
59 bind_query_expr_inner(expr, binds)
60}
61
62fn parameterize_query_expr_inner(expr: &QueryExpr, next_index: &mut usize) -> Option<QueryExpr> {
63 match expr {
64 QueryExpr::Table(query) => Some(QueryExpr::Table(parameterize_table_query(
65 query, next_index,
66 )?)),
67 QueryExpr::Join(query) => {
68 Some(QueryExpr::Join(parameterize_join_query(query, next_index)?))
69 }
70 QueryExpr::Graph(query) => Some(QueryExpr::Graph(parameterize_graph_query(
71 query, next_index,
72 )?)),
73 QueryExpr::Path(query) => {
74 Some(QueryExpr::Path(parameterize_path_query(query, next_index)?))
75 }
76 QueryExpr::Vector(query) => Some(QueryExpr::Vector(parameterize_vector_query(
77 query, next_index,
78 )?)),
79 QueryExpr::Hybrid(query) => Some(QueryExpr::Hybrid(parameterize_hybrid_query(
80 query, next_index,
81 )?)),
82 _ => None,
83 }
84}
85
86fn parameterize_table_query(query: &TableQuery, next_index: &mut usize) -> Option<TableQuery> {
87 let source = match &query.source {
88 Some(TableSource::Name(name)) => Some(TableSource::Name(name.clone())),
89 Some(TableSource::Subquery(inner)) => Some(TableSource::Subquery(Box::new(
90 parameterize_query_expr_inner(inner, next_index)?,
91 ))),
92 None => None,
93 };
94
95 let select_items = query
96 .select_items
97 .iter()
98 .map(|item| parameterize_select_item(item, next_index))
99 .collect::<Option<Vec<_>>>()?;
100
101 let where_expr = query
102 .where_expr
103 .as_ref()
104 .map(|expr| parameterize_expr(expr, next_index))
105 .or_else(|| {
106 query
107 .filter
108 .as_ref()
109 .map(|filter| parameterize_expr(&filter_to_expr(filter), next_index))
110 });
111
112 let group_by_exprs = if !query.group_by_exprs.is_empty() {
113 query
114 .group_by_exprs
115 .iter()
116 .map(|expr| parameterize_expr(expr, next_index))
117 .collect()
118 } else {
119 Vec::new()
120 };
121
122 let having_expr = query
123 .having_expr
124 .as_ref()
125 .map(|expr| parameterize_expr(expr, next_index))
126 .or_else(|| {
127 query
128 .having
129 .as_ref()
130 .map(|filter| parameterize_expr(&filter_to_expr(filter), next_index))
131 });
132
133 let order_by = query
134 .order_by
135 .iter()
136 .map(|clause| parameterize_order_by(clause, next_index))
137 .collect::<Option<Vec<_>>>()?;
138
139 Some(TableQuery {
140 table: query.table.clone(),
141 source,
142 alias: query.alias.clone(),
143 select_items,
144 columns: Vec::new(),
145 where_expr,
146 filter: None,
147 group_by_exprs,
148 group_by: Vec::new(),
149 having_expr,
150 having: None,
151 order_by,
152 limit: query.limit,
153 limit_param: query.limit_param,
154 offset: query.offset,
155 offset_param: query.offset_param,
156 expand: query.expand.clone(),
157 as_of: query.as_of.clone(),
158 })
159}
160
161fn parameterize_select_item(item: &SelectItem, next_index: &mut usize) -> Option<SelectItem> {
162 match item {
163 SelectItem::Wildcard => Some(SelectItem::Wildcard),
164 SelectItem::Expr { expr, alias } => Some(SelectItem::Expr {
165 expr: parameterize_expr(expr, next_index),
166 alias: alias.clone(),
167 }),
168 }
169}
170
171fn parameterize_order_by(clause: &OrderByClause, next_index: &mut usize) -> Option<OrderByClause> {
172 Some(OrderByClause {
173 field: clause.field.clone(),
174 expr: clause
175 .expr
176 .as_ref()
177 .map(|expr| parameterize_expr(expr, next_index)),
178 ascending: clause.ascending,
179 nulls_first: clause.nulls_first,
180 })
181}
182
183fn parameterize_expr(expr: &Expr, next_index: &mut usize) -> Expr {
184 match expr {
185 Expr::Literal { value, span } => {
186 let index = *next_index;
187 *next_index += 1;
188 let _ = value;
189 Expr::Parameter { index, span: *span }
190 }
191 Expr::Column { .. } | Expr::Parameter { .. } => expr.clone(),
192 Expr::BinaryOp { op, lhs, rhs, span } => Expr::BinaryOp {
193 op: *op,
194 lhs: Box::new(parameterize_expr(lhs, next_index)),
195 rhs: Box::new(parameterize_expr(rhs, next_index)),
196 span: *span,
197 },
198 Expr::UnaryOp { op, operand, span } => Expr::UnaryOp {
199 op: *op,
200 operand: Box::new(parameterize_expr(operand, next_index)),
201 span: *span,
202 },
203 Expr::Cast {
204 inner,
205 target,
206 span,
207 } => Expr::Cast {
208 inner: Box::new(parameterize_expr(inner, next_index)),
209 target: *target,
210 span: *span,
211 },
212 Expr::FunctionCall { name, args, span } => Expr::FunctionCall {
213 name: name.clone(),
214 args: args
215 .iter()
216 .map(|arg| parameterize_expr(arg, next_index))
217 .collect(),
218 span: *span,
219 },
220 Expr::Case {
221 branches,
222 else_,
223 span,
224 } => Expr::Case {
225 branches: branches
226 .iter()
227 .map(|(cond, value)| {
228 (
229 parameterize_expr(cond, next_index),
230 parameterize_expr(value, next_index),
231 )
232 })
233 .collect(),
234 else_: else_
235 .as_ref()
236 .map(|expr| Box::new(parameterize_expr(expr, next_index))),
237 span: *span,
238 },
239 Expr::IsNull {
240 operand,
241 negated,
242 span,
243 } => Expr::IsNull {
244 operand: Box::new(parameterize_expr(operand, next_index)),
245 negated: *negated,
246 span: *span,
247 },
248 Expr::InList {
249 target,
250 values,
251 negated,
252 span,
253 } => Expr::InList {
254 target: Box::new(parameterize_expr(target, next_index)),
255 values: values
256 .iter()
257 .map(|value| parameterize_expr(value, next_index))
258 .collect(),
259 negated: *negated,
260 span: *span,
261 },
262 Expr::Between {
263 target,
264 low,
265 high,
266 negated,
267 span,
268 } => Expr::Between {
269 target: Box::new(parameterize_expr(target, next_index)),
270 low: Box::new(parameterize_expr(low, next_index)),
271 high: Box::new(parameterize_expr(high, next_index)),
272 negated: *negated,
273 span: *span,
274 },
275 }
276}
277
278fn bind_query_expr_inner(expr: &QueryExpr, binds: &[Value]) -> Option<QueryExpr> {
279 match expr {
280 QueryExpr::Table(query) => Some(QueryExpr::Table(bind_table_query(query, binds)?)),
281 QueryExpr::Join(query) => Some(QueryExpr::Join(bind_join_query(query, binds)?)),
282 QueryExpr::Graph(query) => Some(QueryExpr::Graph(bind_graph_query(query, binds)?)),
283 QueryExpr::Path(query) => Some(QueryExpr::Path(bind_path_query(query, binds)?)),
284 QueryExpr::Vector(query) => Some(QueryExpr::Vector(bind_vector_query(query, binds)?)),
285 QueryExpr::Hybrid(query) => Some(QueryExpr::Hybrid(bind_hybrid_query(query, binds)?)),
286 _ => None,
287 }
288}
289
290fn parameterize_vector_query(query: &VectorQuery, next_index: &mut usize) -> Option<VectorQuery> {
291 Some(VectorQuery {
292 alias: query.alias.clone(),
293 collection: query.collection.clone(),
294 query_vector: parameterize_vector_source(&query.query_vector, next_index)?,
295 k: query.k,
296 filter: query
297 .filter
298 .as_ref()
299 .map(|filter| parameterize_metadata_filter(filter, next_index)),
300 metric: query.metric,
301 include_vectors: query.include_vectors,
302 include_metadata: query.include_metadata,
303 threshold: query
304 .threshold
305 .map(|_| encode_f32_placeholder(allocate_param_index(next_index))),
306 })
307}
308
309fn bind_vector_query(query: &VectorQuery, binds: &[Value]) -> Option<VectorQuery> {
310 Some(VectorQuery {
311 alias: query.alias.clone(),
312 collection: query.collection.clone(),
313 query_vector: bind_vector_source(&query.query_vector, binds)?,
314 k: query.k,
315 filter: query
316 .filter
317 .as_ref()
318 .and_then(|filter| bind_metadata_filter(filter, binds)),
319 metric: query.metric,
320 include_vectors: query.include_vectors,
321 include_metadata: query.include_metadata,
322 threshold: query
323 .threshold
324 .and_then(|value| bind_placeholder_f32(value, binds)),
325 })
326}
327
328fn parameterize_hybrid_query(query: &HybridQuery, next_index: &mut usize) -> Option<HybridQuery> {
329 Some(HybridQuery {
330 alias: query.alias.clone(),
331 structured: Box::new(parameterize_query_expr_inner(
332 &query.structured,
333 next_index,
334 )?),
335 vector: parameterize_vector_query(&query.vector, next_index)?,
336 fusion: parameterize_fusion_strategy(&query.fusion, next_index),
337 limit: query.limit,
338 })
339}
340
341fn bind_hybrid_query(query: &HybridQuery, binds: &[Value]) -> Option<HybridQuery> {
342 Some(HybridQuery {
343 alias: query.alias.clone(),
344 structured: Box::new(bind_query_expr_inner(&query.structured, binds)?),
345 vector: bind_vector_query(&query.vector, binds)?,
346 fusion: bind_fusion_strategy(&query.fusion, binds)?,
347 limit: query.limit,
348 })
349}
350
351fn parameterize_vector_source(
352 source: &VectorSource,
353 next_index: &mut usize,
354) -> Option<VectorSource> {
355 match source {
356 VectorSource::Literal(values) => Some(VectorSource::Literal(
357 values
358 .iter()
359 .map(|_| encode_f32_placeholder(allocate_param_index(next_index)))
360 .collect(),
361 )),
362 VectorSource::Text(_) => Some(VectorSource::Text(format!(
363 "{VECTOR_TEXT_PARAM_PREFIX}{}",
364 allocate_param_index(next_index)
365 ))),
366 VectorSource::Reference { collection, .. } => Some(VectorSource::Reference {
367 collection: format!(
368 "{VECTOR_REF_ID_PREFIX}{}:{collection}",
369 allocate_param_index(next_index)
370 ),
371 vector_id: 0,
372 }),
373 VectorSource::Subquery(expr) => Some(VectorSource::Subquery(Box::new(
374 parameterize_query_expr_inner(expr, next_index)?,
375 ))),
376 }
377}
378
379fn bind_vector_source(source: &VectorSource, binds: &[Value]) -> Option<VectorSource> {
380 match source {
381 VectorSource::Literal(values) => Some(VectorSource::Literal(
382 values
383 .iter()
384 .map(|value| bind_placeholder_f32(*value, binds))
385 .collect::<Option<Vec<_>>>()?,
386 )),
387 VectorSource::Text(text) => {
388 if let Some(index) = parse_placeholder_index(text, VECTOR_TEXT_PARAM_PREFIX) {
389 Some(VectorSource::Text(bind_value_to_string(binds.get(index)?)?))
390 } else {
391 Some(VectorSource::Text(text.clone()))
392 }
393 }
394 VectorSource::Reference {
395 collection,
396 vector_id,
397 } => {
398 if let Some((index, original_collection)) =
399 parse_prefixed_index_with_suffix(collection, VECTOR_REF_ID_PREFIX)
400 {
401 Some(VectorSource::Reference {
402 collection: original_collection.to_string(),
403 vector_id: bind_value_to_u64(binds.get(index)?)?,
404 })
405 } else {
406 Some(VectorSource::Reference {
407 collection: collection.clone(),
408 vector_id: *vector_id,
409 })
410 }
411 }
412 VectorSource::Subquery(expr) => Some(VectorSource::Subquery(Box::new(
413 bind_query_expr_inner(expr, binds)?,
414 ))),
415 }
416}
417
418fn parameterize_fusion_strategy(fusion: &FusionStrategy, next_index: &mut usize) -> FusionStrategy {
419 match fusion {
420 FusionStrategy::Rerank { .. } => FusionStrategy::Rerank {
421 weight: encode_f32_placeholder(allocate_param_index(next_index)),
422 },
423 FusionStrategy::FilterThenSearch => FusionStrategy::FilterThenSearch,
424 FusionStrategy::SearchThenFilter => FusionStrategy::SearchThenFilter,
425 FusionStrategy::RRF { .. } => FusionStrategy::RRF {
426 k: encode_u32_placeholder(allocate_param_index(next_index)),
427 },
428 FusionStrategy::Intersection => FusionStrategy::Intersection,
429 FusionStrategy::Union { .. } => FusionStrategy::Union {
430 structured_weight: encode_f32_placeholder(allocate_param_index(next_index)),
431 vector_weight: encode_f32_placeholder(allocate_param_index(next_index)),
432 },
433 }
434}
435
436fn bind_fusion_strategy(fusion: &FusionStrategy, binds: &[Value]) -> Option<FusionStrategy> {
437 match fusion {
438 FusionStrategy::Rerank { weight } => Some(FusionStrategy::Rerank {
439 weight: bind_placeholder_f32(*weight, binds)?,
440 }),
441 FusionStrategy::FilterThenSearch => Some(FusionStrategy::FilterThenSearch),
442 FusionStrategy::SearchThenFilter => Some(FusionStrategy::SearchThenFilter),
443 FusionStrategy::RRF { k } => Some(FusionStrategy::RRF {
444 k: bind_placeholder_u32(*k, binds)?,
445 }),
446 FusionStrategy::Intersection => Some(FusionStrategy::Intersection),
447 FusionStrategy::Union {
448 structured_weight,
449 vector_weight,
450 } => Some(FusionStrategy::Union {
451 structured_weight: bind_placeholder_f32(*structured_weight, binds)?,
452 vector_weight: bind_placeholder_f32(*vector_weight, binds)?,
453 }),
454 }
455}
456
457fn parameterize_metadata_filter(filter: &MetadataFilter, next_index: &mut usize) -> MetadataFilter {
458 match filter {
459 MetadataFilter::Eq(key, value) => {
460 MetadataFilter::Eq(key.clone(), parameterize_metadata_value(value, next_index))
461 }
462 MetadataFilter::Ne(key, value) => {
463 MetadataFilter::Ne(key.clone(), parameterize_metadata_value(value, next_index))
464 }
465 MetadataFilter::Gt(key, value) => {
466 MetadataFilter::Gt(key.clone(), parameterize_metadata_value(value, next_index))
467 }
468 MetadataFilter::Gte(key, value) => {
469 MetadataFilter::Gte(key.clone(), parameterize_metadata_value(value, next_index))
470 }
471 MetadataFilter::Lt(key, value) => {
472 MetadataFilter::Lt(key.clone(), parameterize_metadata_value(value, next_index))
473 }
474 MetadataFilter::Lte(key, value) => {
475 MetadataFilter::Lte(key.clone(), parameterize_metadata_value(value, next_index))
476 }
477 MetadataFilter::In(key, values) => MetadataFilter::In(
478 key.clone(),
479 values
480 .iter()
481 .map(|value| parameterize_metadata_value(value, next_index))
482 .collect(),
483 ),
484 MetadataFilter::NotIn(key, values) => MetadataFilter::NotIn(
485 key.clone(),
486 values
487 .iter()
488 .map(|value| parameterize_metadata_value(value, next_index))
489 .collect(),
490 ),
491 MetadataFilter::Contains(_, _) => MetadataFilter::Contains(
492 match filter {
493 MetadataFilter::Contains(key, _) => key.clone(),
494 _ => unreachable!(),
495 },
496 format!("{STRING_PARAM_PREFIX}{}", allocate_param_index(next_index)),
497 ),
498 MetadataFilter::StartsWith(_, _) => MetadataFilter::StartsWith(
499 match filter {
500 MetadataFilter::StartsWith(key, _) => key.clone(),
501 _ => unreachable!(),
502 },
503 format!("{STRING_PARAM_PREFIX}{}", allocate_param_index(next_index)),
504 ),
505 MetadataFilter::EndsWith(_, _) => MetadataFilter::EndsWith(
506 match filter {
507 MetadataFilter::EndsWith(key, _) => key.clone(),
508 _ => unreachable!(),
509 },
510 format!("{STRING_PARAM_PREFIX}{}", allocate_param_index(next_index)),
511 ),
512 MetadataFilter::Exists(key) => MetadataFilter::Exists(key.clone()),
513 MetadataFilter::NotExists(key) => MetadataFilter::NotExists(key.clone()),
514 MetadataFilter::And(filters) => MetadataFilter::And(
515 filters
516 .iter()
517 .map(|filter| parameterize_metadata_filter(filter, next_index))
518 .collect(),
519 ),
520 MetadataFilter::Or(filters) => MetadataFilter::Or(
521 filters
522 .iter()
523 .map(|filter| parameterize_metadata_filter(filter, next_index))
524 .collect(),
525 ),
526 MetadataFilter::Not(inner) => {
527 MetadataFilter::Not(Box::new(parameterize_metadata_filter(inner, next_index)))
528 }
529 }
530}
531
532fn bind_metadata_filter(filter: &MetadataFilter, binds: &[Value]) -> Option<MetadataFilter> {
533 match filter {
534 MetadataFilter::Eq(key, value) => Some(MetadataFilter::Eq(
535 key.clone(),
536 bind_metadata_value(value, binds)?,
537 )),
538 MetadataFilter::Ne(key, value) => Some(MetadataFilter::Ne(
539 key.clone(),
540 bind_metadata_value(value, binds)?,
541 )),
542 MetadataFilter::Gt(key, value) => Some(MetadataFilter::Gt(
543 key.clone(),
544 bind_metadata_value(value, binds)?,
545 )),
546 MetadataFilter::Gte(key, value) => Some(MetadataFilter::Gte(
547 key.clone(),
548 bind_metadata_value(value, binds)?,
549 )),
550 MetadataFilter::Lt(key, value) => Some(MetadataFilter::Lt(
551 key.clone(),
552 bind_metadata_value(value, binds)?,
553 )),
554 MetadataFilter::Lte(key, value) => Some(MetadataFilter::Lte(
555 key.clone(),
556 bind_metadata_value(value, binds)?,
557 )),
558 MetadataFilter::In(key, values) => Some(MetadataFilter::In(
559 key.clone(),
560 values
561 .iter()
562 .map(|value| bind_metadata_value(value, binds))
563 .collect::<Option<Vec<_>>>()?,
564 )),
565 MetadataFilter::NotIn(key, values) => Some(MetadataFilter::NotIn(
566 key.clone(),
567 values
568 .iter()
569 .map(|value| bind_metadata_value(value, binds))
570 .collect::<Option<Vec<_>>>()?,
571 )),
572 MetadataFilter::Contains(key, value) => Some(MetadataFilter::Contains(
573 key.clone(),
574 bind_placeholder_string(value, binds)?.unwrap_or_default(),
575 )),
576 MetadataFilter::StartsWith(key, value) => Some(MetadataFilter::StartsWith(
577 key.clone(),
578 bind_placeholder_string(value, binds)?.unwrap_or_default(),
579 )),
580 MetadataFilter::EndsWith(key, value) => Some(MetadataFilter::EndsWith(
581 key.clone(),
582 bind_placeholder_string(value, binds)?.unwrap_or_default(),
583 )),
584 MetadataFilter::Exists(key) => Some(MetadataFilter::Exists(key.clone())),
585 MetadataFilter::NotExists(key) => Some(MetadataFilter::NotExists(key.clone())),
586 MetadataFilter::And(filters) => Some(MetadataFilter::And(
587 filters
588 .iter()
589 .map(|filter| bind_metadata_filter(filter, binds))
590 .collect::<Option<Vec<_>>>()?,
591 )),
592 MetadataFilter::Or(filters) => Some(MetadataFilter::Or(
593 filters
594 .iter()
595 .map(|filter| bind_metadata_filter(filter, binds))
596 .collect::<Option<Vec<_>>>()?,
597 )),
598 MetadataFilter::Not(inner) => Some(MetadataFilter::Not(Box::new(bind_metadata_filter(
599 inner, binds,
600 )?))),
601 }
602}
603
604fn parameterize_metadata_value(_value: &MetadataValue, next_index: &mut usize) -> MetadataValue {
605 MetadataValue::String(format!(
606 "{METADATA_VALUE_PARAM_PREFIX}{}",
607 allocate_param_index(next_index)
608 ))
609}
610
611fn bind_metadata_value(value: &MetadataValue, binds: &[Value]) -> Option<MetadataValue> {
612 match value {
613 MetadataValue::String(text) => {
614 if let Some(index) = parse_placeholder_index(text, METADATA_VALUE_PARAM_PREFIX) {
615 Some(bind_value_to_metadata_value(binds.get(index)?)?)
616 } else {
617 Some(MetadataValue::String(text.clone()))
618 }
619 }
620 other => Some(other.clone()),
621 }
622}
623
624fn parameterize_join_query(query: &JoinQuery, next_index: &mut usize) -> Option<JoinQuery> {
625 Some(JoinQuery {
626 left: Box::new(parameterize_query_expr_inner(&query.left, next_index)?),
627 right: Box::new(parameterize_query_expr_inner(&query.right, next_index)?),
628 join_type: query.join_type,
629 on: query.on.clone(),
630 filter: query
631 .filter
632 .as_ref()
633 .map(|filter| parameterize_filter(filter, next_index)),
634 order_by: query
635 .order_by
636 .iter()
637 .map(|clause| parameterize_order_by(clause, next_index))
638 .collect::<Option<Vec<_>>>()?,
639 limit: query.limit,
640 offset: query.offset,
641 return_items: query
642 .return_items
643 .iter()
644 .map(|item| parameterize_select_item(item, next_index))
645 .collect::<Option<Vec<_>>>()?,
646 return_: query
647 .return_
648 .iter()
649 .map(|projection| parameterize_projection(projection, next_index))
650 .collect::<Option<Vec<_>>>()?,
651 })
652}
653
654fn bind_join_query(query: &JoinQuery, binds: &[Value]) -> Option<JoinQuery> {
655 Some(JoinQuery {
656 left: Box::new(bind_query_expr_inner(&query.left, binds)?),
657 right: Box::new(bind_query_expr_inner(&query.right, binds)?),
658 join_type: query.join_type,
659 on: query.on.clone(),
660 filter: query
661 .filter
662 .as_ref()
663 .and_then(|filter| bind_filter(filter, binds)),
664 order_by: query
665 .order_by
666 .iter()
667 .map(|clause| bind_order_by(clause, binds))
668 .collect::<Option<Vec<_>>>()?,
669 limit: query.limit,
670 offset: query.offset,
671 return_items: query
672 .return_items
673 .iter()
674 .map(|item| bind_select_item(item, binds))
675 .collect::<Option<Vec<_>>>()?,
676 return_: query
677 .return_
678 .iter()
679 .map(|projection| bind_projection(projection, binds))
680 .collect::<Option<Vec<_>>>()?,
681 })
682}
683
684fn parameterize_graph_query(query: &GraphQuery, next_index: &mut usize) -> Option<GraphQuery> {
685 Some(GraphQuery {
686 alias: query.alias.clone(),
687 pattern: parameterize_graph_pattern(&query.pattern, next_index),
688 filter: query
689 .filter
690 .as_ref()
691 .map(|filter| parameterize_filter(filter, next_index)),
692 return_: query
693 .return_
694 .iter()
695 .map(|projection| parameterize_projection(projection, next_index))
696 .collect::<Option<Vec<_>>>()?,
697 })
698}
699
700fn bind_graph_query(query: &GraphQuery, binds: &[Value]) -> Option<GraphQuery> {
701 Some(GraphQuery {
702 alias: query.alias.clone(),
703 pattern: bind_graph_pattern(&query.pattern, binds)?,
704 filter: query
705 .filter
706 .as_ref()
707 .and_then(|filter| bind_filter(filter, binds)),
708 return_: query
709 .return_
710 .iter()
711 .map(|projection| bind_projection(projection, binds))
712 .collect::<Option<Vec<_>>>()?,
713 })
714}
715
716fn parameterize_path_query(query: &PathQuery, next_index: &mut usize) -> Option<PathQuery> {
717 Some(PathQuery {
718 alias: query.alias.clone(),
719 from: parameterize_node_selector(&query.from, next_index),
720 to: parameterize_node_selector(&query.to, next_index),
721 via: query.via.clone(),
722 max_length: query.max_length,
723 filter: query
724 .filter
725 .as_ref()
726 .map(|filter| parameterize_filter(filter, next_index)),
727 return_: query
728 .return_
729 .iter()
730 .map(|projection| parameterize_projection(projection, next_index))
731 .collect::<Option<Vec<_>>>()?,
732 })
733}
734
735fn bind_path_query(query: &PathQuery, binds: &[Value]) -> Option<PathQuery> {
736 Some(PathQuery {
737 alias: query.alias.clone(),
738 from: bind_node_selector(&query.from, binds)?,
739 to: bind_node_selector(&query.to, binds)?,
740 via: query.via.clone(),
741 max_length: query.max_length,
742 filter: query
743 .filter
744 .as_ref()
745 .and_then(|filter| bind_filter(filter, binds)),
746 return_: query
747 .return_
748 .iter()
749 .map(|projection| bind_projection(projection, binds))
750 .collect::<Option<Vec<_>>>()?,
751 })
752}
753
754fn parameterize_filter(
755 filter: &crate::storage::query::ast::Filter,
756 next_index: &mut usize,
757) -> crate::storage::query::ast::Filter {
758 expr_to_filter(¶meterize_expr(&filter_to_expr(filter), next_index))
759}
760
761fn bind_filter(
762 filter: &crate::storage::query::ast::Filter,
763 binds: &[Value],
764) -> Option<crate::storage::query::ast::Filter> {
765 Some(expr_to_filter(&bind_expr(&filter_to_expr(filter), binds)?))
766}
767
768fn parameterize_projection(projection: &Projection, next_index: &mut usize) -> Option<Projection> {
769 match projection {
770 Projection::All => Some(Projection::All),
771 Projection::Column(column) => {
772 Some(parameterize_projection_column(column, None, next_index))
773 }
774 Projection::Alias(column, alias) => Some(parameterize_projection_column(
775 column,
776 Some(alias.as_str()),
777 next_index,
778 )),
779 Projection::Function(name, args) => Some(Projection::Function(
780 name.clone(),
781 args.iter()
782 .map(|arg| parameterize_projection(arg, next_index))
783 .collect::<Option<Vec<_>>>()?,
784 )),
785 Projection::Expression(filter, alias) => Some(Projection::Expression(
786 Box::new(parameterize_filter(filter, next_index)),
787 alias.clone(),
788 )),
789 Projection::Field(field, alias) => Some(Projection::Field(field.clone(), alias.clone())),
790 }
791}
792
793fn bind_projection(projection: &Projection, binds: &[Value]) -> Option<Projection> {
794 match projection {
795 Projection::All => Some(Projection::All),
796 Projection::Column(column) => bind_projection_column(column, None, binds),
797 Projection::Alias(column, alias) => {
798 bind_projection_column(column, Some(alias.as_str()), binds)
799 }
800 Projection::Function(name, args) => Some(Projection::Function(
801 name.clone(),
802 args.iter()
803 .map(|arg| bind_projection(arg, binds))
804 .collect::<Option<Vec<_>>>()?,
805 )),
806 Projection::Expression(filter, alias) => Some(Projection::Expression(
807 Box::new(bind_filter(filter, binds)?),
808 alias.clone(),
809 )),
810 Projection::Field(field, alias) => Some(Projection::Field(field.clone(), alias.clone())),
811 }
812}
813
814fn parameterize_projection_column(
815 column: &str,
816 alias: Option<&str>,
817 next_index: &mut usize,
818) -> Projection {
819 if column.starts_with("LIT:") {
820 let index = *next_index;
821 *next_index += 1;
822 let placeholder = format!("{PROJECTION_PARAM_PREFIX}{index}");
823 if let Some(alias) = alias {
824 Projection::Alias(placeholder, alias.to_string())
825 } else {
826 Projection::Column(placeholder)
827 }
828 } else if let Some(alias) = alias {
829 Projection::Alias(column.to_string(), alias.to_string())
830 } else {
831 Projection::Column(column.to_string())
832 }
833}
834
835fn bind_projection_column(
836 column: &str,
837 alias: Option<&str>,
838 binds: &[Value],
839) -> Option<Projection> {
840 if let Some(index) = parse_placeholder_index(column, PROJECTION_PARAM_PREFIX) {
841 let projection = projection_from_literal(binds.get(index)?)?;
842 Some(attach_projection_alias(projection, alias))
843 } else if let Some(alias) = alias {
844 Some(Projection::Alias(column.to_string(), alias.to_string()))
845 } else {
846 Some(Projection::Column(column.to_string()))
847 }
848}
849
850fn parameterize_graph_pattern(pattern: &GraphPattern, next_index: &mut usize) -> GraphPattern {
851 GraphPattern {
852 nodes: pattern
853 .nodes
854 .iter()
855 .map(|node| parameterize_node_pattern(node, next_index))
856 .collect(),
857 edges: pattern.edges.clone(),
858 }
859}
860
861fn bind_graph_pattern(pattern: &GraphPattern, binds: &[Value]) -> Option<GraphPattern> {
862 Some(GraphPattern {
863 nodes: pattern
864 .nodes
865 .iter()
866 .map(|node| bind_node_pattern(node, binds))
867 .collect::<Option<Vec<_>>>()?,
868 edges: pattern.edges.clone(),
869 })
870}
871
872fn parameterize_node_pattern(node: &NodePattern, next_index: &mut usize) -> NodePattern {
873 NodePattern {
874 alias: node.alias.clone(),
875 node_label: node.node_label.clone(),
876 properties: node
877 .properties
878 .iter()
879 .map(|property| parameterize_property_filter(property, next_index))
880 .collect(),
881 }
882}
883
884fn bind_node_pattern(node: &NodePattern, binds: &[Value]) -> Option<NodePattern> {
885 Some(NodePattern {
886 alias: node.alias.clone(),
887 node_label: node.node_label.clone(),
888 properties: node
889 .properties
890 .iter()
891 .map(|property| bind_property_filter(property, binds))
892 .collect::<Option<Vec<_>>>()?,
893 })
894}
895
896fn parameterize_property_filter(filter: &PropertyFilter, next_index: &mut usize) -> PropertyFilter {
897 PropertyFilter {
898 name: filter.name.clone(),
899 op: filter.op,
900 value: parameterize_value_placeholder(next_index),
901 }
902}
903
904fn bind_property_filter(filter: &PropertyFilter, binds: &[Value]) -> Option<PropertyFilter> {
905 Some(PropertyFilter {
906 name: filter.name.clone(),
907 op: filter.op,
908 value: bind_value_placeholder(&filter.value, binds)?,
909 })
910}
911
912fn parameterize_node_selector(selector: &NodeSelector, next_index: &mut usize) -> NodeSelector {
913 match selector {
914 NodeSelector::ById(_) => {
915 let index = *next_index;
916 *next_index += 1;
917 NodeSelector::ById(format!("{STRING_PARAM_PREFIX}{index}"))
918 }
919 NodeSelector::ByType { node_label, filter } => NodeSelector::ByType {
920 node_label: node_label.clone(),
921 filter: filter
922 .as_ref()
923 .map(|filter| parameterize_property_filter(filter, next_index)),
924 },
925 NodeSelector::ByRow { table, .. } => {
926 let index = *next_index;
927 *next_index += 1;
928 NodeSelector::ByRow {
929 table: format!("{ROW_SELECTOR_TABLE_PREFIX}{index}:{table}"),
930 row_id: 0,
931 }
932 }
933 }
934}
935
936fn bind_node_selector(selector: &NodeSelector, binds: &[Value]) -> Option<NodeSelector> {
937 match selector {
938 NodeSelector::ById(id) => {
939 if let Some(index) = parse_placeholder_index(id, STRING_PARAM_PREFIX) {
940 Some(NodeSelector::ById(bind_value_to_string(binds.get(index)?)?))
941 } else {
942 Some(NodeSelector::ById(id.clone()))
943 }
944 }
945 NodeSelector::ByType { node_label, filter } => Some(NodeSelector::ByType {
946 node_label: node_label.clone(),
947 filter: filter
948 .as_ref()
949 .and_then(|filter| bind_property_filter(filter, binds)),
950 }),
951 NodeSelector::ByRow { table, row_id } => {
952 if let Some((index, original_table)) = parse_row_selector_placeholder(table) {
953 Some(NodeSelector::ByRow {
954 table: original_table.to_string(),
955 row_id: bind_value_to_u64(binds.get(index)?)?,
956 })
957 } else {
958 Some(NodeSelector::ByRow {
959 table: table.clone(),
960 row_id: *row_id,
961 })
962 }
963 }
964 }
965}
966
967fn parameterize_value_placeholder(next_index: &mut usize) -> Value {
968 let index = *next_index;
969 *next_index += 1;
970 Value::text(format!("{VALUE_PARAM_PREFIX}{index}"))
971}
972
973fn bind_value_placeholder(value: &Value, binds: &[Value]) -> Option<Value> {
974 match value {
975 Value::Text(text) => {
976 if let Some(index) = parse_placeholder_index(text, VALUE_PARAM_PREFIX) {
977 binds.get(index).cloned()
978 } else {
979 Some(value.clone())
980 }
981 }
982 _ => Some(value.clone()),
983 }
984}
985
986fn attach_projection_alias(projection: Projection, alias: Option<&str>) -> Projection {
987 let Some(alias) = alias else {
988 return projection;
989 };
990 match projection {
991 Projection::Field(field, _) => Projection::Field(field, Some(alias.to_string())),
992 Projection::Expression(filter, _) => {
993 Projection::Expression(filter, Some(alias.to_string()))
994 }
995 Projection::Function(name, args) => {
996 if name.contains(':') {
997 Projection::Function(name, args)
998 } else {
999 Projection::Function(format!("{name}:{alias}"), args)
1000 }
1001 }
1002 Projection::Column(column) => Projection::Alias(column, alias.to_string()),
1003 Projection::Alias(column, _) => Projection::Alias(column, alias.to_string()),
1004 Projection::All => Projection::All,
1005 }
1006}
1007
1008fn bind_value_to_string(value: &Value) -> Option<String> {
1009 match value {
1010 Value::Null => None,
1011 _ => Some(value.to_string()),
1012 }
1013}
1014
1015fn bind_placeholder_string(value: &str, binds: &[Value]) -> Option<Option<String>> {
1016 if let Some(index) = parse_placeholder_index(value, STRING_PARAM_PREFIX) {
1017 Some(bind_value_to_string(binds.get(index)?))
1018 } else {
1019 Some(Some(value.to_string()))
1020 }
1021}
1022
1023fn bind_value_to_u64(value: &Value) -> Option<u64> {
1024 match value {
1025 Value::UnsignedInteger(value) => Some(*value),
1026 Value::Integer(value) if *value >= 0 => Some(*value as u64),
1027 Value::BigInt(value) if *value >= 0 => Some(*value as u64),
1028 Value::Text(value) => value.parse().ok(),
1029 _ => None,
1030 }
1031}
1032
1033fn parse_placeholder_index(value: &str, prefix: &str) -> Option<usize> {
1034 value.strip_prefix(prefix)?.parse().ok()
1035}
1036
1037fn parse_prefixed_index_with_suffix<'a>(value: &'a str, prefix: &str) -> Option<(usize, &'a str)> {
1038 let rest = value.strip_prefix(prefix)?;
1039 let (index, suffix) = rest.split_once(':')?;
1040 Some((index.parse().ok()?, suffix))
1041}
1042
1043fn parse_row_selector_placeholder(value: &str) -> Option<(usize, &str)> {
1044 let rest = value.strip_prefix(ROW_SELECTOR_TABLE_PREFIX)?;
1045 let (index, table) = rest.split_once(':')?;
1046 Some((index.parse().ok()?, table))
1047}
1048
1049fn allocate_param_index(next_index: &mut usize) -> usize {
1050 let index = *next_index;
1051 *next_index += 1;
1052 index
1053}
1054
1055fn encode_f32_placeholder(index: usize) -> f32 {
1056 f32::from_bits(FLOAT32_PARAM_BITS_BASE | (index as u32 & 0x003f_ffff))
1057}
1058
1059fn decode_f32_placeholder(value: f32) -> Option<usize> {
1060 let bits = value.to_bits();
1061 if bits & FLOAT32_PARAM_BITS_BASE == FLOAT32_PARAM_BITS_BASE {
1062 Some((bits & 0x003f_ffff) as usize)
1063 } else {
1064 None
1065 }
1066}
1067
1068fn bind_placeholder_f32(value: f32, binds: &[Value]) -> Option<f32> {
1069 if let Some(index) = decode_f32_placeholder(value) {
1070 bind_value_to_f32(binds.get(index)?)
1071 } else {
1072 Some(value)
1073 }
1074}
1075
1076fn encode_u32_placeholder(index: usize) -> u32 {
1077 U32_PARAM_BASE | (index as u32 & 0x000f_ffff)
1078}
1079
1080fn decode_u32_placeholder(value: u32) -> Option<usize> {
1081 if value & 0xfff0_0000 == U32_PARAM_BASE {
1082 Some((value & 0x000f_ffff) as usize)
1083 } else {
1084 None
1085 }
1086}
1087
1088fn bind_placeholder_u32(value: u32, binds: &[Value]) -> Option<u32> {
1089 if let Some(index) = decode_u32_placeholder(value) {
1090 bind_value_to_u64(binds.get(index)?).and_then(|value| u32::try_from(value).ok())
1091 } else {
1092 Some(value)
1093 }
1094}
1095
1096fn bind_value_to_f32(value: &Value) -> Option<f32> {
1097 match value {
1098 Value::Float(value) => Some(*value as f32),
1099 Value::Integer(value) => Some(*value as f32),
1100 Value::UnsignedInteger(value) => Some(*value as f32),
1101 Value::BigInt(value) => Some(*value as f32),
1102 Value::Text(value) => value.parse().ok(),
1103 _ => None,
1104 }
1105}
1106
1107fn bind_value_to_metadata_value(value: &Value) -> Option<MetadataValue> {
1108 match value {
1109 Value::Text(value) => Some(MetadataValue::String(value.to_string())),
1110 Value::Integer(value) => Some(MetadataValue::Integer(*value)),
1111 Value::UnsignedInteger(value) => i64::try_from(*value).ok().map(MetadataValue::Integer),
1112 Value::BigInt(value) => Some(MetadataValue::Integer(*value)),
1113 Value::Float(value) => Some(MetadataValue::Float(*value)),
1114 Value::Boolean(value) => Some(MetadataValue::Bool(*value)),
1115 Value::Null => Some(MetadataValue::Null),
1116 _ => None,
1117 }
1118}
1119
1120fn bind_table_query(query: &TableQuery, binds: &[Value]) -> Option<TableQuery> {
1121 let source = match &query.source {
1122 Some(TableSource::Name(name)) => Some(TableSource::Name(name.clone())),
1123 Some(TableSource::Subquery(inner)) => Some(TableSource::Subquery(Box::new(
1124 bind_query_expr_inner(inner, binds)?,
1125 ))),
1126 None => None,
1127 };
1128
1129 Some(TableQuery {
1130 table: query.table.clone(),
1131 source,
1132 alias: query.alias.clone(),
1133 select_items: query
1134 .select_items
1135 .iter()
1136 .map(|item| bind_select_item(item, binds))
1137 .collect::<Option<Vec<_>>>()?,
1138 columns: Vec::new(),
1139 where_expr: query
1140 .where_expr
1141 .as_ref()
1142 .and_then(|expr| bind_expr(expr, binds)),
1143 filter: None,
1144 group_by_exprs: query
1145 .group_by_exprs
1146 .iter()
1147 .map(|expr| bind_expr(expr, binds))
1148 .collect::<Option<Vec<_>>>()?,
1149 group_by: Vec::new(),
1150 having_expr: query
1151 .having_expr
1152 .as_ref()
1153 .and_then(|expr| bind_expr(expr, binds)),
1154 having: None,
1155 order_by: query
1156 .order_by
1157 .iter()
1158 .map(|clause| bind_order_by(clause, binds))
1159 .collect::<Option<Vec<_>>>()?,
1160 limit: query.limit,
1161 limit_param: query.limit_param,
1162 offset: query.offset,
1163 offset_param: query.offset_param,
1164 expand: query.expand.clone(),
1165 as_of: query.as_of.clone(),
1166 })
1167}
1168
1169fn bind_select_item(item: &SelectItem, binds: &[Value]) -> Option<SelectItem> {
1170 match item {
1171 SelectItem::Wildcard => Some(SelectItem::Wildcard),
1172 SelectItem::Expr { expr, alias } => Some(SelectItem::Expr {
1173 expr: bind_expr(expr, binds)?,
1174 alias: alias.clone(),
1175 }),
1176 }
1177}
1178
1179fn bind_order_by(clause: &OrderByClause, binds: &[Value]) -> Option<OrderByClause> {
1180 Some(OrderByClause {
1181 field: clause.field.clone(),
1182 expr: clause.expr.as_ref().and_then(|expr| bind_expr(expr, binds)),
1183 ascending: clause.ascending,
1184 nulls_first: clause.nulls_first,
1185 })
1186}
1187
1188fn bind_expr(expr: &Expr, binds: &[Value]) -> Option<Expr> {
1189 match expr {
1190 Expr::Literal { .. } | Expr::Column { .. } => Some(expr.clone()),
1191 Expr::Parameter { index, span } => Some(Expr::Literal {
1192 value: binds.get(*index)?.clone(),
1193 span: *span,
1194 }),
1195 Expr::BinaryOp { op, lhs, rhs, span } => Some(Expr::BinaryOp {
1196 op: *op,
1197 lhs: Box::new(bind_expr(lhs, binds)?),
1198 rhs: Box::new(bind_expr(rhs, binds)?),
1199 span: *span,
1200 }),
1201 Expr::UnaryOp { op, operand, span } => Some(Expr::UnaryOp {
1202 op: *op,
1203 operand: Box::new(bind_expr(operand, binds)?),
1204 span: *span,
1205 }),
1206 Expr::Cast {
1207 inner,
1208 target,
1209 span,
1210 } => Some(Expr::Cast {
1211 inner: Box::new(bind_expr(inner, binds)?),
1212 target: *target,
1213 span: *span,
1214 }),
1215 Expr::FunctionCall { name, args, span } => Some(Expr::FunctionCall {
1216 name: name.clone(),
1217 args: args
1218 .iter()
1219 .map(|arg| bind_expr(arg, binds))
1220 .collect::<Option<Vec<_>>>()?,
1221 span: *span,
1222 }),
1223 Expr::Case {
1224 branches,
1225 else_,
1226 span,
1227 } => Some(Expr::Case {
1228 branches: branches
1229 .iter()
1230 .map(|(cond, value)| Some((bind_expr(cond, binds)?, bind_expr(value, binds)?)))
1231 .collect::<Option<Vec<_>>>()?,
1232 else_: else_
1233 .as_ref()
1234 .and_then(|expr| bind_expr(expr, binds).map(Box::new)),
1235 span: *span,
1236 }),
1237 Expr::IsNull {
1238 operand,
1239 negated,
1240 span,
1241 } => Some(Expr::IsNull {
1242 operand: Box::new(bind_expr(operand, binds)?),
1243 negated: *negated,
1244 span: *span,
1245 }),
1246 Expr::InList {
1247 target,
1248 values,
1249 negated,
1250 span,
1251 } => Some(Expr::InList {
1252 target: Box::new(bind_expr(target, binds)?),
1253 values: values
1254 .iter()
1255 .map(|value| bind_expr(value, binds))
1256 .collect::<Option<Vec<_>>>()?,
1257 negated: *negated,
1258 span: *span,
1259 }),
1260 Expr::Between {
1261 target,
1262 low,
1263 high,
1264 negated,
1265 span,
1266 } => Some(Expr::Between {
1267 target: Box::new(bind_expr(target, binds)?),
1268 low: Box::new(bind_expr(low, binds)?),
1269 high: Box::new(bind_expr(high, binds)?),
1270 negated: *negated,
1271 span: *span,
1272 }),
1273 }
1274}
1275
1276#[cfg(test)]
1277mod tests {
1278 use super::*;
1279 use crate::storage::query::ast::{BinOp, FieldRef, SelectItem, TableQuery};
1280
1281 #[test]
1282 fn table_shape_round_trips_with_new_binds() {
1283 let query = QueryExpr::Table(TableQuery {
1284 table: "users".to_string(),
1285 source: None,
1286 alias: None,
1287 select_items: vec![SelectItem::Expr {
1288 expr: Expr::Column {
1289 field: FieldRef::TableColumn {
1290 table: String::new(),
1291 column: "name".to_string(),
1292 },
1293 span: crate::storage::query::ast::Span::synthetic(),
1294 },
1295 alias: None,
1296 }],
1297 columns: Vec::new(),
1298 where_expr: Some(Expr::BinaryOp {
1299 op: BinOp::Eq,
1300 lhs: Box::new(Expr::Column {
1301 field: FieldRef::TableColumn {
1302 table: String::new(),
1303 column: "age".to_string(),
1304 },
1305 span: crate::storage::query::ast::Span::synthetic(),
1306 }),
1307 rhs: Box::new(Expr::Literal {
1308 value: Value::Integer(18),
1309 span: crate::storage::query::ast::Span::synthetic(),
1310 }),
1311 span: crate::storage::query::ast::Span::synthetic(),
1312 }),
1313 filter: None,
1314 group_by_exprs: Vec::new(),
1315 group_by: Vec::new(),
1316 having_expr: None,
1317 having: None,
1318 order_by: Vec::new(),
1319 limit: None,
1320 limit_param: None,
1321 offset: None,
1322 offset_param: None,
1323 expand: None,
1324 as_of: None,
1325 });
1326
1327 let prepared = parameterize_query_expr(&query).unwrap();
1328 assert_eq!(prepared.parameter_count, 1);
1329
1330 let rebound = bind_parameterized_query(
1331 &prepared.shape,
1332 &[Value::Integer(42)],
1333 prepared.parameter_count,
1334 )
1335 .unwrap();
1336
1337 let QueryExpr::Table(bound_table) = rebound else {
1338 panic!("expected table query");
1339 };
1340 match bound_table.where_expr.unwrap() {
1341 Expr::BinaryOp { rhs, .. } => match *rhs {
1342 Expr::Literal { value, .. } => assert_eq!(value, Value::Integer(42)),
1343 other => panic!("expected rebound literal, got {other:?}"),
1344 },
1345 other => panic!("expected binary op, got {other:?}"),
1346 }
1347 }
1348}