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