Skip to main content

reddb_server/storage/query/planner/
shape.rs

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
38/// Bind user-supplied positional `$N` parameters into a freshly-parsed
39/// query expression. Unlike [`bind_parameterized_query`] — which is the
40/// other half of the auto-parameterized prepared-statement shape cache —
41/// this entry point assumes the parser already emitted `Expr::Parameter`
42/// nodes (one per `$N` reference) and just substitutes them in place.
43///
44/// Validation (arity, gaps) is performed by `user_params::validate`
45/// before this call; here we trust the slice indexes match the parser's
46/// 0-based slot numbering.
47pub 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(&parameterize_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}