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