Skip to main content

donadb_rel/
predicate.rs

1// predicate.rs — FieldOp and Predicate for scan_where().
2//
3// A Predicate names a field and applies a comparison operator to filter records.
4// Predicates are evaluated in-process after the storage-layer scan returns raw bytes.
5// When a secondary index exists for the named field, scan_where() uses the index
6// instead of a full scan — the predicate is then used only for final filtering.
7//
8// Multiple predicates: all must be satisfied (implicit AND).
9// OR semantics require calling scan_where() multiple times and merging results.
10
11use crate::schema::FieldValue;
12
13/// Comparison operator for a field predicate.
14#[derive(Debug, Clone)]
15pub enum FieldOp {
16    Eq(FieldValue),
17    Gt(FieldValue),
18    Gte(FieldValue),
19    Lt(FieldValue),
20    Lte(FieldValue),
21    /// Byte-prefix match. Only valid for Bytes/VarBytes/Address/Hash fields.
22    Prefix(bytes::Bytes),
23    /// Membership test. Field value must be one of the listed values.
24    In(Vec<FieldValue>),
25    /// Field is not null.
26    IsNotNull,
27}
28
29/// A single field filter: field name + comparison operator.
30#[derive(Debug, Clone)]
31pub struct Predicate {
32    pub field: String,
33    pub op: FieldOp,
34}
35
36impl Predicate {
37    pub fn eq(field: impl Into<String>, value: FieldValue) -> Self {
38        Self {
39            field: field.into(),
40            op: FieldOp::Eq(value),
41        }
42    }
43    pub fn gt(field: impl Into<String>, value: FieldValue) -> Self {
44        Self {
45            field: field.into(),
46            op: FieldOp::Gt(value),
47        }
48    }
49    pub fn gte(field: impl Into<String>, value: FieldValue) -> Self {
50        Self {
51            field: field.into(),
52            op: FieldOp::Gte(value),
53        }
54    }
55    pub fn lt(field: impl Into<String>, value: FieldValue) -> Self {
56        Self {
57            field: field.into(),
58            op: FieldOp::Lt(value),
59        }
60    }
61    pub fn lte(field: impl Into<String>, value: FieldValue) -> Self {
62        Self {
63            field: field.into(),
64            op: FieldOp::Lte(value),
65        }
66    }
67    pub fn prefix(field: impl Into<String>, prefix: impl Into<bytes::Bytes>) -> Self {
68        Self {
69            field: field.into(),
70            op: FieldOp::Prefix(prefix.into()),
71        }
72    }
73    pub fn in_values(field: impl Into<String>, values: Vec<FieldValue>) -> Self {
74        Self {
75            field: field.into(),
76            op: FieldOp::In(values),
77        }
78    }
79    pub fn is_not_null(field: impl Into<String>) -> Self {
80        Self {
81            field: field.into(),
82            op: FieldOp::IsNotNull,
83        }
84    }
85}
86
87impl Predicate {
88    /// Evaluate this predicate against a record field value.
89    /// Returns true if the record satisfies the predicate.
90    pub fn evaluate(&self, value: &FieldValue) -> bool {
91        match &self.op {
92            FieldOp::Eq(expected) => value == expected,
93            FieldOp::IsNotNull => value != &FieldValue::Null,
94            FieldOp::In(list) => list.iter().any(|v| v == value),
95            FieldOp::Prefix(pfx) => {
96                if let Some(b) = value.as_bytes() {
97                    b.starts_with(pfx)
98                } else {
99                    false
100                }
101            }
102            FieldOp::Gt(bound) => compare_field(value, bound) == Some(std::cmp::Ordering::Greater),
103            FieldOp::Gte(bound) => matches!(
104                compare_field(value, bound),
105                Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal)
106            ),
107            FieldOp::Lt(bound) => compare_field(value, bound) == Some(std::cmp::Ordering::Less),
108            FieldOp::Lte(bound) => matches!(
109                compare_field(value, bound),
110                Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal)
111            ),
112        }
113    }
114}
115
116/// Numeric/bytes comparison between two FieldValues of the same type.
117/// Returns None if types are incomparable.
118fn compare_field(a: &FieldValue, b: &FieldValue) -> Option<std::cmp::Ordering> {
119    match (a, b) {
120        (FieldValue::U8(x), FieldValue::U8(y)) => Some(x.cmp(y)),
121        (FieldValue::U16(x), FieldValue::U16(y)) => Some(x.cmp(y)),
122        (FieldValue::U32(x), FieldValue::U32(y)) => Some(x.cmp(y)),
123        (FieldValue::U64(x), FieldValue::U64(y)) => Some(x.cmp(y)),
124        (FieldValue::U128(x), FieldValue::U128(y)) => Some(x.cmp(y)),
125        (FieldValue::I64(x), FieldValue::I64(y)) => Some(x.cmp(y)),
126        (FieldValue::Bytes(x), FieldValue::Bytes(y)) => {
127            Some(x.as_ref() as &[u8]).map(|xr| xr.cmp(y.as_ref()))
128        }
129        _ => None,
130    }
131}