1use mongreldb_core::query::Condition;
39use mongreldb_kit_core::query::{Expr, Literal};
40use mongreldb_kit_core::schema::{ColumnType, IndexKind as KitIndexKind, Table as KitTable};
41use serde_json::{Map, Value};
42
43#[derive(Debug, Clone)]
45pub struct PushdownPlan {
46 pub conditions: Vec<Condition>,
48 pub fully_translated: bool,
51}
52
53impl PushdownPlan {
54 pub fn can_push(&self) -> bool {
56 !self.conditions.is_empty()
57 }
58}
59
60pub fn translate_predicate(table: &KitTable, expr: &Expr) -> Option<PushdownPlan> {
64 let mut conditions = Vec::new();
65 let fully = collect_conditions(table, expr, &mut conditions);
66 if conditions.is_empty() {
67 return None;
68 }
69 Some(PushdownPlan {
70 conditions,
71 fully_translated: fully,
72 })
73}
74
75fn collect_conditions(table: &KitTable, expr: &Expr, out: &mut Vec<Condition>) -> bool {
78 match expr {
79 Expr::And(parts) => {
80 let mut all = true;
81 for part in parts {
82 if !collect_conditions(table, part, out) {
83 all = false;
84 }
85 }
86 all
87 }
88 Expr::Eq(a, b) => push_if_some(out, try_translate_eq(table, a, b)),
89 Expr::Lt(a, b) => push_if_some(out, try_translate_cmp(table, a, b, CmpOp::Lt)),
90 Expr::Lte(a, b) => push_if_some(out, try_translate_cmp(table, a, b, CmpOp::Lte)),
91 Expr::Gt(a, b) => push_if_some(out, try_translate_cmp(table, a, b, CmpOp::Gt)),
92 Expr::Gte(a, b) => push_if_some(out, try_translate_cmp(table, a, b, CmpOp::Gte)),
93 Expr::In(a, list) => push_if_some(out, try_translate_in(table, a, list)),
94 Expr::BytesPrefix(a, prefix) => {
98 push_if_some(out, try_translate_bytes_prefix(table, a, prefix))
99 }
100 Expr::Contains(a, needle) => {
101 if let Some(c) = try_translate_contains(table, a, needle) {
105 out.push(c);
106 }
107 false
108 }
109 Expr::Like(a, pattern) => {
110 if let Some(c) = try_translate_like(table, a, pattern) {
113 out.push(c);
114 }
115 false
116 }
117 Expr::IsNull(a) => {
118 if let Some(c) = try_translate_null(table, a, true) {
119 out.push(c);
120 }
121 false
122 }
123 Expr::IsNotNull(a) => {
124 if let Some(c) = try_translate_null(table, a, false) {
125 out.push(c);
126 }
127 false
128 }
129 _ => false,
132 }
133}
134
135fn try_translate_null(table: &KitTable, a: &Expr, is_null: bool) -> Option<Condition> {
138 let Expr::Column(col_name) = a else {
139 return None;
140 };
141 let column_id = table.column(col_name)?.id as u16;
142 Some(if is_null {
143 Condition::IsNull { column_id }
144 } else {
145 Condition::IsNotNull { column_id }
146 })
147}
148
149fn push_if_some(out: &mut Vec<Condition>, opt: Option<Condition>) -> bool {
151 match opt {
152 Some(c) => {
153 out.push(c);
154 true
155 }
156 None => false,
157 }
158}
159
160#[derive(Clone, Copy)]
161enum CmpOp {
162 Lt,
163 Lte,
164 Gt,
165 Gte,
166}
167
168fn extract_column_literal<'a>(a: &'a Expr, b: &'a Expr) -> Option<(&'a str, &'a Literal)> {
171 match (a, b) {
172 (Expr::Column(name), Expr::Literal(lit)) => Some((name.as_str(), lit)),
173 (Expr::Literal(lit), Expr::Column(name)) => Some((name.as_str(), lit)),
174 _ => None, }
176}
177
178fn try_translate_eq(table: &KitTable, a: &Expr, b: &Expr) -> Option<Condition> {
180 let (col_name, lit) = extract_column_literal(a, b)?;
181 let col = table.column(col_name)?;
182 let col_id = col.id as u16;
183 let ty = col.storage_type;
184
185 if table.primary_key.len() == 1 && table.primary_key[0] == col_name {
187 let key = literal_to_index_key(lit, ty)?;
188 return Some(Condition::Pk(key));
189 }
190
191 if has_bitmap_index(table, col_name) {
194 let value = literal_to_index_key(lit, ty)?;
195 return Some(Condition::BitmapEq {
196 column_id: col_id,
197 value,
198 });
199 }
200
201 None
202}
203
204fn try_translate_cmp(table: &KitTable, a: &Expr, b: &Expr, op: CmpOp) -> Option<Condition> {
206 let (col_name, lit) = extract_column_literal(a, b)?;
207 let col = table.column(col_name)?;
208 let col_id = col.id as u16;
209 let ty = col.storage_type;
210
211 if is_int_type(ty) {
212 let v = literal_to_i64(lit)?;
213 let (lo, hi) = match op {
214 CmpOp::Lt => (i64::MIN, v.saturating_sub(1)),
215 CmpOp::Lte => (i64::MIN, v),
216 CmpOp::Gt => (v.saturating_add(1), i64::MAX),
217 CmpOp::Gte => (v, i64::MAX),
218 };
219 Some(Condition::Range {
220 column_id: col_id,
221 lo,
222 hi,
223 })
224 } else if is_float_type(ty) {
225 let v = literal_to_f64(lit)?;
226 let (lo, lo_inc, hi, hi_inc) = match op {
227 CmpOp::Lt => (f64::NEG_INFINITY, false, v, false),
228 CmpOp::Lte => (f64::NEG_INFINITY, false, v, true),
229 CmpOp::Gt => (v, false, f64::INFINITY, false),
230 CmpOp::Gte => (v, true, f64::INFINITY, false),
231 };
232 Some(Condition::RangeF64 {
233 column_id: col_id,
234 lo,
235 lo_inclusive: lo_inc,
236 hi,
237 hi_inclusive: hi_inc,
238 })
239 } else {
240 None
241 }
242}
243
244fn has_fm_index(table: &KitTable, col_name: &str) -> bool {
246 table
247 .indexes
248 .iter()
249 .any(|idx| idx.kind == KitIndexKind::Fm && idx.columns.iter().any(|c| c == col_name))
250}
251
252fn try_translate_like(table: &KitTable, a: &Expr, pattern: &str) -> Option<Condition> {
258 let Expr::Column(col_name) = a else {
259 return None;
260 };
261 let col = table.column(col_name)?;
262 if !has_fm_index(table, col_name) || pattern.contains('\\') {
263 return None;
264 }
265 let patterns: Vec<Vec<u8>> = pattern
266 .split(['%', '_'])
267 .filter(|s| !s.is_empty())
268 .map(|s| s.as_bytes().to_vec())
269 .collect();
270 if patterns.is_empty() {
271 return None;
272 }
273 Some(Condition::FmContainsAll {
274 column_id: col.id as u16,
275 patterns,
276 })
277}
278
279fn try_translate_contains(table: &KitTable, a: &Expr, needle: &str) -> Option<Condition> {
283 let Expr::Column(col_name) = a else {
284 return None;
285 };
286 let col = table.column(col_name)?;
287 if !has_fm_index(table, col_name) {
288 return None;
289 }
290 Some(Condition::FmContains {
291 column_id: col.id as u16,
292 pattern: needle.as_bytes().to_vec(),
293 })
294}
295
296fn try_translate_bytes_prefix(table: &KitTable, a: &Expr, prefix: &str) -> Option<Condition> {
302 let Expr::Column(col_name) = a else {
303 return None;
304 };
305 let col = table.column(col_name)?;
306 if !has_bitmap_index(table, col_name) {
307 return None;
308 }
309 Some(Condition::BytesPrefix {
310 column_id: col.id as u16,
311 prefix: prefix.as_bytes().to_vec(),
312 })
313}
314
315fn try_translate_in(table: &KitTable, a: &Expr, list: &[Literal]) -> Option<Condition> {
317 let Expr::Column(col_name) = a else {
318 return None;
319 };
320 let col = table.column(col_name)?;
321 let col_id = col.id as u16;
322 let ty = col.storage_type;
323 if !has_bitmap_index(table, col_name) {
324 return None;
325 }
326 let mut values = Vec::with_capacity(list.len());
327 for lit in list {
328 values.push(literal_to_index_key(lit, ty)?);
329 }
330 Some(Condition::BitmapIn {
331 column_id: col_id,
332 values,
333 })
334}
335
336pub fn pk_conditions(table: &KitTable, pk_map: &Map<String, Value>) -> Option<Vec<Condition>> {
344 if pk_map.is_empty() || table.primary_key.is_empty() {
345 return None;
346 }
347 let mut conditions = Vec::with_capacity(table.primary_key.len());
348 for pk_name in &table.primary_key {
349 let value = pk_map.get(pk_name)?;
350 let col = table.column(pk_name)?;
351 let lit = json_to_literal(value)?;
352 let key = literal_to_index_key(&lit, col.storage_type)?;
353 if table.primary_key.len() == 1 {
354 conditions.push(Condition::Pk(key));
356 } else {
357 conditions.push(Condition::BitmapEq {
359 column_id: col.id as u16,
360 value: key,
361 });
362 }
363 }
364 Some(conditions)
365}
366
367fn has_bitmap_index(table: &KitTable, col_name: &str) -> bool {
372 table.indexes.iter().any(|idx| {
373 idx.kind == KitIndexKind::Bitmap && idx.columns.iter().any(|c| c == col_name)
384 }) || table
385 .unique_constraints
386 .iter()
387 .any(|uq| uq.columns.iter().any(|c| c == col_name))
388 || table.primary_key.contains(&col_name.to_string())
389}
390
391pub(crate) fn has_declared_bitmap_index(table: &KitTable, col_name: &str) -> bool {
397 table
398 .indexes
399 .iter()
400 .any(|idx| idx.columns.iter().any(|c| c == col_name))
401 || table
402 .unique_constraints
403 .iter()
404 .any(|uq| uq.columns.iter().any(|c| c == col_name))
405}
406
407pub(crate) fn value_index_key(v: &Value, ty: ColumnType) -> Option<Vec<u8>> {
411 let lit = match v {
412 Value::Bool(b) => Literal::Bool(*b),
413 Value::Number(n) => n
414 .as_i64()
415 .map(Literal::Int)
416 .or_else(|| n.as_f64().map(Literal::Float))?,
417 Value::String(s) => Literal::Text(s.clone()),
418 _ => return None,
419 };
420 literal_to_index_key(&lit, ty)
421}
422
423fn is_int_type(ty: ColumnType) -> bool {
424 matches!(
425 ty,
426 ColumnType::Int8
427 | ColumnType::Int16
428 | ColumnType::Int32
429 | ColumnType::Int64
430 | ColumnType::Bool
431 | ColumnType::TimestampNanos
432 )
433}
434
435fn is_float_type(ty: ColumnType) -> bool {
436 matches!(ty, ColumnType::Float32 | ColumnType::Float64)
437}
438
439fn literal_to_index_key(lit: &Literal, ty: ColumnType) -> Option<Vec<u8>> {
442 match lit {
443 Literal::Null => None, Literal::Bool(b) => Some(vec![*b as u8]),
445 Literal::Int(n) => {
446 if is_float_type(ty) {
447 Some((*n as f64).to_bits().to_be_bytes().to_vec())
449 } else {
450 Some(n.to_be_bytes().to_vec())
451 }
452 }
453 Literal::Float(f) => Some(f.to_bits().to_be_bytes().to_vec()),
454 Literal::Text(s) => Some(s.as_bytes().to_vec()),
455 Literal::Json(v) => Some(serde_json::to_vec(v).ok()?),
456 }
457}
458
459fn literal_to_i64(lit: &Literal) -> Option<i64> {
460 match lit {
461 Literal::Int(n) => Some(*n),
462 Literal::Bool(b) => Some(*b as i64),
463 _ => None,
464 }
465}
466
467fn literal_to_f64(lit: &Literal) -> Option<f64> {
468 match lit {
469 Literal::Int(n) => Some(*n as f64),
470 Literal::Float(f) => Some(*f),
471 _ => None,
472 }
473}
474
475fn json_to_literal(value: &Value) -> Option<Literal> {
477 match value {
478 Value::Null => Some(Literal::Null),
479 Value::Bool(b) => Some(Literal::Bool(*b)),
480 Value::Number(n) => {
481 if let Some(i) = n.as_i64() {
482 Some(Literal::Int(i))
483 } else {
484 Some(Literal::Float(n.as_f64()?))
485 }
486 }
487 Value::String(s) => Some(Literal::Text(s.clone())),
488 Value::Array(_) | Value::Object(_) => Some(Literal::Json(value.clone())),
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495 use mongreldb_kit_core::query::Expr;
496 use mongreldb_kit_core::schema::{Column, Index, Table as KitTable};
497
498 fn sample_table() -> KitTable {
499 KitTable {
500 id: 1,
501 name: "users".into(),
502 columns: vec![
503 {
504 let mut c = Column::new(1, "id", ColumnType::Int64);
505 c.primary_key = true;
506 c
507 },
508 Column::new(2, "email", ColumnType::Text),
509 {
510 let mut c = Column::new(3, "age", ColumnType::Int64);
511 c.nullable = true;
512 c
513 },
514 {
515 let mut c = Column::new(4, "score", ColumnType::Float64);
516 c.nullable = true;
517 c
518 },
519 ],
520 primary_key: vec!["id".into()],
521 indexes: vec![Index {
522 name: "idx_email".into(),
523 columns: vec!["email".into()],
524 unique: false,
525 kind: Default::default(),
526 }],
527 foreign_keys: vec![],
528 unique_constraints: vec![],
529 check_constraints: vec![],
530 }
531 }
532
533 #[test]
534 fn translate_pk_eq() {
535 let t = sample_table();
536 let expr = Expr::Eq(
537 Box::new(Expr::Column("id".into())),
538 Box::new(Expr::Literal(Literal::Int(42))),
539 );
540 let plan = translate_predicate(&t, &expr).expect("should translate");
541 assert!(plan.fully_translated);
542 assert_eq!(plan.conditions.len(), 1);
543 assert!(matches!(plan.conditions[0], Condition::Pk(_)));
544 }
545
546 #[test]
547 fn translate_bitmap_eq() {
548 let t = sample_table();
549 let expr = Expr::Eq(
550 Box::new(Expr::Column("email".into())),
551 Box::new(Expr::Literal(Literal::Text("a@b.com".into()))),
552 );
553 let plan = translate_predicate(&t, &expr).expect("should translate");
554 assert!(plan.fully_translated);
555 assert!(matches!(
556 plan.conditions[0],
557 Condition::BitmapEq { column_id: 2, .. }
558 ));
559 }
560
561 #[test]
562 fn translate_int_range() {
563 let t = sample_table();
564 let expr = Expr::Gte(
565 Box::new(Expr::Column("age".into())),
566 Box::new(Expr::Literal(Literal::Int(18))),
567 );
568 let plan = translate_predicate(&t, &expr).expect("should translate");
569 assert!(plan.fully_translated);
570 assert!(matches!(
571 plan.conditions[0],
572 Condition::Range {
573 column_id: 3,
574 lo: 18,
575 ..
576 }
577 ));
578 }
579
580 #[test]
581 fn translate_and_of_translatable() {
582 let t = sample_table();
583 let expr = Expr::And(vec![
584 Expr::Eq(
585 Box::new(Expr::Column("email".into())),
586 Box::new(Expr::Literal(Literal::Text("a@b.com".into()))),
587 ),
588 Expr::Gt(
589 Box::new(Expr::Column("age".into())),
590 Box::new(Expr::Literal(Literal::Int(21))),
591 ),
592 ]);
593 let plan = translate_predicate(&t, &expr).expect("should translate");
594 assert!(plan.fully_translated);
595 assert_eq!(plan.conditions.len(), 2);
596 }
597
598 #[test]
599 fn translate_and_partial() {
600 let t = sample_table();
601 let expr = Expr::And(vec![
603 Expr::Eq(
604 Box::new(Expr::Column("email".into())),
605 Box::new(Expr::Literal(Literal::Text("a@b.com".into()))),
606 ),
607 Expr::Or(vec![
608 Expr::Gt(
609 Box::new(Expr::Column("age".into())),
610 Box::new(Expr::Literal(Literal::Int(21))),
611 ),
612 Expr::Lt(
613 Box::new(Expr::Column("score".into())),
614 Box::new(Expr::Literal(Literal::Float(5.0))),
615 ),
616 ]),
617 ]);
618 let plan = translate_predicate(&t, &expr).expect("should partially translate");
619 assert!(!plan.fully_translated); assert_eq!(plan.conditions.len(), 1); }
622
623 #[test]
624 fn translate_unsupported_returns_none() {
625 let t = sample_table();
626 let expr = Expr::Or(vec![
627 Expr::Eq(
628 Box::new(Expr::Column("email".into())),
629 Box::new(Expr::Literal(Literal::Text("a@b.com".into()))),
630 ),
631 Expr::Eq(
632 Box::new(Expr::Column("email".into())),
633 Box::new(Expr::Literal(Literal::Text("c@d.com".into()))),
634 ),
635 ]);
636 assert!(translate_predicate(&t, &expr).is_none());
637 }
638
639 #[test]
640 fn translate_in_to_bitmap_in() {
641 let t = sample_table();
642 let expr = Expr::In(
643 Box::new(Expr::Column("email".into())),
644 vec![
645 Literal::Text("a@b.com".into()),
646 Literal::Text("c@d.com".into()),
647 ],
648 );
649 let plan = translate_predicate(&t, &expr).expect("should translate");
650 assert!(plan.fully_translated);
651 assert!(matches!(
652 plan.conditions[0],
653 Condition::BitmapIn { column_id: 2, .. }
654 ));
655 }
656
657 #[test]
658 fn pk_conditions_single_col() {
659 let t = sample_table();
660 let mut pk_map = Map::new();
661 pk_map.insert("id".into(), Value::Number(42.into()));
662 let conds = pk_conditions(&t, &pk_map).expect("should build");
663 assert_eq!(conds.len(), 1);
664 assert!(matches!(conds[0], Condition::Pk(_)));
665 }
666}