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
373 .indexes
374 .iter()
375 .any(|idx| {
376 idx.kind == KitIndexKind::Bitmap && idx.columns.iter().any(|c| c == col_name)
387 })
388 || table
389 .unique_constraints
390 .iter()
391 .any(|uq| uq.columns.iter().any(|c| c == col_name))
392 || table.primary_key.contains(&col_name.to_string())
393}
394
395pub(crate) fn has_declared_bitmap_index(table: &KitTable, col_name: &str) -> bool {
401 table
402 .indexes
403 .iter()
404 .any(|idx| idx.columns.iter().any(|c| c == col_name))
405 || table
406 .unique_constraints
407 .iter()
408 .any(|uq| uq.columns.iter().any(|c| c == col_name))
409}
410
411pub(crate) fn value_index_key(v: &Value, ty: ColumnType) -> Option<Vec<u8>> {
415 let lit = match v {
416 Value::Bool(b) => Literal::Bool(*b),
417 Value::Number(n) => n
418 .as_i64()
419 .map(Literal::Int)
420 .or_else(|| n.as_f64().map(Literal::Float))?,
421 Value::String(s) => Literal::Text(s.clone()),
422 _ => return None,
423 };
424 literal_to_index_key(&lit, ty)
425}
426
427fn is_int_type(ty: ColumnType) -> bool {
428 matches!(
429 ty,
430 ColumnType::Int8
431 | ColumnType::Int16
432 | ColumnType::Int32
433 | ColumnType::Int64
434 | ColumnType::Bool
435 | ColumnType::TimestampNanos
436 )
437}
438
439fn is_float_type(ty: ColumnType) -> bool {
440 matches!(ty, ColumnType::Float32 | ColumnType::Float64)
441}
442
443fn literal_to_index_key(lit: &Literal, ty: ColumnType) -> Option<Vec<u8>> {
446 match lit {
447 Literal::Null => None, Literal::Bool(b) => Some(vec![*b as u8]),
449 Literal::Int(n) => {
450 if is_float_type(ty) {
451 Some((*n as f64).to_bits().to_be_bytes().to_vec())
453 } else {
454 Some(n.to_be_bytes().to_vec())
455 }
456 }
457 Literal::Float(f) => Some(f.to_bits().to_be_bytes().to_vec()),
458 Literal::Text(s) => Some(s.as_bytes().to_vec()),
459 Literal::Json(v) => Some(serde_json::to_vec(v).ok()?),
460 }
461}
462
463fn literal_to_i64(lit: &Literal) -> Option<i64> {
464 match lit {
465 Literal::Int(n) => Some(*n),
466 Literal::Bool(b) => Some(*b as i64),
467 _ => None,
468 }
469}
470
471fn literal_to_f64(lit: &Literal) -> Option<f64> {
472 match lit {
473 Literal::Int(n) => Some(*n as f64),
474 Literal::Float(f) => Some(*f),
475 _ => None,
476 }
477}
478
479fn json_to_literal(value: &Value) -> Option<Literal> {
481 match value {
482 Value::Null => Some(Literal::Null),
483 Value::Bool(b) => Some(Literal::Bool(*b)),
484 Value::Number(n) => {
485 if let Some(i) = n.as_i64() {
486 Some(Literal::Int(i))
487 } else {
488 Some(Literal::Float(n.as_f64()?))
489 }
490 }
491 Value::String(s) => Some(Literal::Text(s.clone())),
492 Value::Array(_) | Value::Object(_) => Some(Literal::Json(value.clone())),
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499 use mongreldb_kit_core::query::Expr;
500 use mongreldb_kit_core::schema::{Column, Index, Table as KitTable};
501
502 fn sample_table() -> KitTable {
503 KitTable {
504 id: 1,
505 name: "users".into(),
506 columns: vec![
507 {
508 let mut c = Column::new(1, "id", ColumnType::Int64);
509 c.primary_key = true;
510 c
511 },
512 Column::new(2, "email", ColumnType::Text),
513 {
514 let mut c = Column::new(3, "age", ColumnType::Int64);
515 c.nullable = true;
516 c
517 },
518 {
519 let mut c = Column::new(4, "score", ColumnType::Float64);
520 c.nullable = true;
521 c
522 },
523 ],
524 primary_key: vec!["id".into()],
525 indexes: vec![Index {
526 name: "idx_email".into(),
527 columns: vec!["email".into()],
528 unique: false,
529 kind: Default::default(),
530 }],
531 foreign_keys: vec![],
532 unique_constraints: vec![],
533 check_constraints: vec![],
534 }
535 }
536
537 #[test]
538 fn translate_pk_eq() {
539 let t = sample_table();
540 let expr = Expr::Eq(
541 Box::new(Expr::Column("id".into())),
542 Box::new(Expr::Literal(Literal::Int(42))),
543 );
544 let plan = translate_predicate(&t, &expr).expect("should translate");
545 assert!(plan.fully_translated);
546 assert_eq!(plan.conditions.len(), 1);
547 assert!(matches!(plan.conditions[0], Condition::Pk(_)));
548 }
549
550 #[test]
551 fn translate_bitmap_eq() {
552 let t = sample_table();
553 let expr = Expr::Eq(
554 Box::new(Expr::Column("email".into())),
555 Box::new(Expr::Literal(Literal::Text("a@b.com".into()))),
556 );
557 let plan = translate_predicate(&t, &expr).expect("should translate");
558 assert!(plan.fully_translated);
559 assert!(matches!(
560 plan.conditions[0],
561 Condition::BitmapEq { column_id: 2, .. }
562 ));
563 }
564
565 #[test]
566 fn translate_int_range() {
567 let t = sample_table();
568 let expr = Expr::Gte(
569 Box::new(Expr::Column("age".into())),
570 Box::new(Expr::Literal(Literal::Int(18))),
571 );
572 let plan = translate_predicate(&t, &expr).expect("should translate");
573 assert!(plan.fully_translated);
574 assert!(matches!(
575 plan.conditions[0],
576 Condition::Range {
577 column_id: 3,
578 lo: 18,
579 ..
580 }
581 ));
582 }
583
584 #[test]
585 fn translate_and_of_translatable() {
586 let t = sample_table();
587 let expr = Expr::And(vec![
588 Expr::Eq(
589 Box::new(Expr::Column("email".into())),
590 Box::new(Expr::Literal(Literal::Text("a@b.com".into()))),
591 ),
592 Expr::Gt(
593 Box::new(Expr::Column("age".into())),
594 Box::new(Expr::Literal(Literal::Int(21))),
595 ),
596 ]);
597 let plan = translate_predicate(&t, &expr).expect("should translate");
598 assert!(plan.fully_translated);
599 assert_eq!(plan.conditions.len(), 2);
600 }
601
602 #[test]
603 fn translate_and_partial() {
604 let t = sample_table();
605 let expr = Expr::And(vec![
607 Expr::Eq(
608 Box::new(Expr::Column("email".into())),
609 Box::new(Expr::Literal(Literal::Text("a@b.com".into()))),
610 ),
611 Expr::Or(vec![
612 Expr::Gt(
613 Box::new(Expr::Column("age".into())),
614 Box::new(Expr::Literal(Literal::Int(21))),
615 ),
616 Expr::Lt(
617 Box::new(Expr::Column("score".into())),
618 Box::new(Expr::Literal(Literal::Float(5.0))),
619 ),
620 ]),
621 ]);
622 let plan = translate_predicate(&t, &expr).expect("should partially translate");
623 assert!(!plan.fully_translated); assert_eq!(plan.conditions.len(), 1); }
626
627 #[test]
628 fn translate_unsupported_returns_none() {
629 let t = sample_table();
630 let expr = Expr::Or(vec![
631 Expr::Eq(
632 Box::new(Expr::Column("email".into())),
633 Box::new(Expr::Literal(Literal::Text("a@b.com".into()))),
634 ),
635 Expr::Eq(
636 Box::new(Expr::Column("email".into())),
637 Box::new(Expr::Literal(Literal::Text("c@d.com".into()))),
638 ),
639 ]);
640 assert!(translate_predicate(&t, &expr).is_none());
641 }
642
643 #[test]
644 fn translate_in_to_bitmap_in() {
645 let t = sample_table();
646 let expr = Expr::In(
647 Box::new(Expr::Column("email".into())),
648 vec![
649 Literal::Text("a@b.com".into()),
650 Literal::Text("c@d.com".into()),
651 ],
652 );
653 let plan = translate_predicate(&t, &expr).expect("should translate");
654 assert!(plan.fully_translated);
655 assert!(matches!(
656 plan.conditions[0],
657 Condition::BitmapIn { column_id: 2, .. }
658 ));
659 }
660
661 #[test]
662 fn pk_conditions_single_col() {
663 let t = sample_table();
664 let mut pk_map = Map::new();
665 pk_map.insert("id".into(), Value::Number(42.into()));
666 let conds = pk_conditions(&t, &pk_map).expect("should build");
667 assert_eq!(conds.len(), 1);
668 assert!(matches!(conds[0], Condition::Pk(_)));
669 }
670}