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