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