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