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