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