1use crate::schema::FieldValue;
12
13#[derive(Debug, Clone)]
15pub enum FieldOp {
16 Eq(FieldValue),
17 Gt(FieldValue),
18 Gte(FieldValue),
19 Lt(FieldValue),
20 Lte(FieldValue),
21 Prefix(bytes::Bytes),
23 In(Vec<FieldValue>),
25 IsNotNull,
27}
28
29#[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 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
116fn 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}