Skip to main content

nodedb_query/scan_filter/
op.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! `FilterOp` enum and its string/serde conversions.
4//!
5//! `FilterOp` is an O(1)-dispatch discriminant used by the scan filter
6//! evaluator. On-wire it travels as a lowercase string tag so physical
7//! plans remain debuggable by hand.
8
9/// Filter operator enum for O(1) dispatch instead of string comparison.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum FilterOp {
12    Eq,
13    Ne,
14    Gt,
15    Gte,
16    Lt,
17    Lte,
18    Contains,
19    Like,
20    NotLike,
21    Ilike,
22    NotIlike,
23    In,
24    NotIn,
25    IsNull,
26    IsNotNull,
27    ArrayContains,
28    ArrayContainsAll,
29    ArrayOverlap,
30    #[default]
31    MatchAll,
32    Exists,
33    NotExists,
34    Or,
35    /// Arbitrary expression predicate: the filter's `expr` field holds a
36    /// `nodedb_query::expr::SqlExpr`. The scan evaluator runs the expression
37    /// against the full row and treats truthy results as a match. Used when
38    /// the planner cannot reduce the WHERE clause to a simple `(field, op, value)`
39    /// — e.g. `LOWER(col) = 'x'`, `qty + 1 = 5`, `NOT (col = 'x')`.
40    Expr,
41    /// Column-vs-column comparison: `field` op `value` where `value` is a
42    /// `Value::String` containing the name of the other column. The comparison
43    /// reads both fields from the same document row.
44    GtColumn,
45    GteColumn,
46    LtColumn,
47    LteColumn,
48    EqColumn,
49    NeColumn,
50}
51
52impl FilterOp {
53    pub fn parse_op(s: &str) -> Self {
54        match s {
55            "eq" => Self::Eq,
56            "ne" | "neq" => Self::Ne,
57            "gt" => Self::Gt,
58            "gte" | "ge" => Self::Gte,
59            "lt" => Self::Lt,
60            "lte" | "le" => Self::Lte,
61            "contains" => Self::Contains,
62            "like" => Self::Like,
63            "not_like" => Self::NotLike,
64            "ilike" => Self::Ilike,
65            "not_ilike" => Self::NotIlike,
66            "in" => Self::In,
67            "not_in" => Self::NotIn,
68            "is_null" => Self::IsNull,
69            "is_not_null" => Self::IsNotNull,
70            "array_contains" => Self::ArrayContains,
71            "array_contains_all" => Self::ArrayContainsAll,
72            "array_overlap" => Self::ArrayOverlap,
73            "match_all" => Self::MatchAll,
74            "exists" => Self::Exists,
75            "not_exists" => Self::NotExists,
76            "or" => Self::Or,
77            "expr" => Self::Expr,
78            "gt_col" => Self::GtColumn,
79            "gte_col" => Self::GteColumn,
80            "lt_col" => Self::LtColumn,
81            "lte_col" => Self::LteColumn,
82            "eq_col" => Self::EqColumn,
83            "ne_col" => Self::NeColumn,
84            _ => Self::MatchAll,
85        }
86    }
87
88    pub fn as_str(&self) -> &'static str {
89        match self {
90            Self::Eq => "eq",
91            Self::Ne => "ne",
92            Self::Gt => "gt",
93            Self::Gte => "gte",
94            Self::Lt => "lt",
95            Self::Lte => "lte",
96            Self::Contains => "contains",
97            Self::Like => "like",
98            Self::NotLike => "not_like",
99            Self::Ilike => "ilike",
100            Self::NotIlike => "not_ilike",
101            Self::In => "in",
102            Self::NotIn => "not_in",
103            Self::IsNull => "is_null",
104            Self::IsNotNull => "is_not_null",
105            Self::ArrayContains => "array_contains",
106            Self::ArrayContainsAll => "array_contains_all",
107            Self::ArrayOverlap => "array_overlap",
108            Self::MatchAll => "match_all",
109            Self::Exists => "exists",
110            Self::NotExists => "not_exists",
111            Self::Or => "or",
112            Self::Expr => "expr",
113            Self::GtColumn => "gt_col",
114            Self::GteColumn => "gte_col",
115            Self::LtColumn => "lt_col",
116            Self::LteColumn => "lte_col",
117            Self::EqColumn => "eq_col",
118            Self::NeColumn => "ne_col",
119        }
120    }
121}
122
123impl From<&str> for FilterOp {
124    fn from(s: &str) -> Self {
125        Self::parse_op(s)
126    }
127}
128
129impl From<String> for FilterOp {
130    fn from(s: String) -> Self {
131        Self::parse_op(&s)
132    }
133}
134
135impl serde::Serialize for FilterOp {
136    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
137        serializer.serialize_str(self.as_str())
138    }
139}
140
141impl<'de> serde::Deserialize<'de> for FilterOp {
142    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
143        let s = String::deserialize(deserializer)?;
144        Ok(FilterOp::parse_op(&s))
145    }
146}