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