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