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