Skip to main content

journal_index/
filter.rs

1use crate::{Bitmap, FieldName, FieldValuePair, FileIndex};
2use std::hash::{Hash, Hasher};
3use std::sync::Arc;
4
5/// Represents what a filter expression can match against.
6///
7/// This enum distinguishes between:
8/// - Matching a field name (e.g., "PRIORITY" matches any PRIORITY value)
9/// - Matching a specific field=value pair (e.g., "PRIORITY=error")
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11#[cfg_attr(feature = "allocative", derive(allocative::Allocative))]
12enum FilterTarget {
13    /// Match any entry that has this field, regardless of value
14    Field(FieldName),
15    /// Match entries where this specific field=value pair exists
16    Pair(FieldValuePair),
17}
18
19/// High-level filter expression that operates on field names and field=value pairs.
20///
21/// This is the primary type used when constructing filters from user queries.
22/// Use [`Filter::match_field_name()`] to match any entry with a specific field,
23/// or [`Filter::match_field_value_pair()`] to match a specific field=value combination.
24///
25/// Filters can be combined using [`Filter::and()`] and [`Filter::or()`] for complex queries.
26#[derive(Clone, Debug)]
27#[cfg_attr(feature = "allocative", derive(allocative::Allocative))]
28pub struct Filter {
29    inner: Arc<FilterExpr<FilterTarget>>,
30}
31
32impl Filter {
33    /// Create a filter that matches any entry with the given field name.
34    pub fn match_field_name(name: FieldName) -> Self {
35        Self {
36            inner: Arc::new(FilterExpr::Match(FilterTarget::Field(name))),
37        }
38    }
39
40    /// Create a filter that matches a specific field=value pair.
41    pub fn match_field_value_pair(pair: FieldValuePair) -> Self {
42        Self {
43            inner: Arc::new(FilterExpr::Match(FilterTarget::Pair(pair))),
44        }
45    }
46
47    /// Combine multiple filters with AND logic.
48    pub fn and(filters: Vec<Self>) -> Self {
49        let inner_filters: Vec<FilterExpr<FilterTarget>> =
50            filters.into_iter().map(|f| (*f.inner).clone()).collect();
51
52        Self {
53            inner: Arc::new(FilterExpr::and(inner_filters)),
54        }
55    }
56
57    /// Combine multiple filters with OR logic.
58    pub fn or(filters: Vec<Self>) -> Self {
59        let inner_filters: Vec<FilterExpr<FilterTarget>> =
60            filters.into_iter().map(|f| (*f.inner).clone()).collect();
61
62        Self {
63            inner: Arc::new(FilterExpr::or(inner_filters)),
64        }
65    }
66
67    /// Create a filter that matches nothing.
68    pub fn none() -> Self {
69        Self {
70            inner: Arc::new(FilterExpr::None),
71        }
72    }
73
74    /// Check if this is a None filter.
75    pub fn is_none(&self) -> bool {
76        matches!(self.inner.as_ref(), FilterExpr::None)
77    }
78
79    /// Evaluate this filter against a file index to get matching entry indices.
80    pub fn evaluate(&self, file_index: &FileIndex) -> Bitmap {
81        self.inner.resolve(file_index).evaluate()
82    }
83}
84
85impl PartialEq for Filter {
86    fn eq(&self, other: &Self) -> bool {
87        // Quick pointer equality check first
88        if Arc::ptr_eq(&self.inner, &other.inner) {
89            return true;
90        }
91
92        // Fall back to value equality
93        self.inner == other.inner
94    }
95}
96
97impl Eq for Filter {}
98
99impl std::hash::Hash for Filter {
100    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
101        self.inner.hash(state);
102    }
103}
104
105impl std::fmt::Display for Filter {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        write!(f, "{}", self.inner)
108    }
109}
110
111#[derive(Clone, Debug, PartialEq)]
112#[cfg_attr(feature = "allocative", derive(allocative::Allocative))]
113enum FilterExpr<T> {
114    None,
115    Match(T),
116    Conjunction(Vec<Self>),
117    Disjunction(Vec<Self>),
118}
119
120impl Eq for FilterExpr<FilterTarget> {}
121
122impl Hash for FilterExpr<FilterTarget> {
123    fn hash<H: Hasher>(&self, state: &mut H) {
124        std::mem::discriminant(self).hash(state);
125
126        match self {
127            FilterExpr::None => {}
128            FilterExpr::Match(target) => target.hash(state),
129            FilterExpr::Conjunction(filters) => filters.hash(state),
130            FilterExpr::Disjunction(filters) => filters.hash(state),
131        }
132    }
133}
134
135impl FilterExpr<FilterTarget> {
136    fn and(filters: Vec<Self>) -> Self {
137        // Flatten any nested conjunctions and remove None filters
138        let mut flattened = Vec::new();
139        for filter in filters {
140            match filter {
141                FilterExpr::Conjunction(inner) => flattened.extend(inner),
142                FilterExpr::None => continue,
143                other => flattened.push(other),
144            }
145        }
146
147        match flattened.len() {
148            0 => FilterExpr::None,
149            1 => flattened.into_iter().next().unwrap(),
150            _ => FilterExpr::Conjunction(flattened),
151        }
152    }
153
154    fn or(filters: Vec<Self>) -> Self {
155        // Flatten any nested disjunctions and remove None filters
156        let mut flattened = Vec::new();
157        for filter in filters {
158            match filter {
159                FilterExpr::Disjunction(inner) => flattened.extend(inner),
160                FilterExpr::None => continue,
161                other => flattened.push(other),
162            }
163        }
164
165        match flattened.len() {
166            0 => FilterExpr::None,
167            1 => flattened.into_iter().next().unwrap(),
168            _ => FilterExpr::Disjunction(flattened),
169        }
170    }
171
172    /// Convert a [`FilterExpr<FilterTarget>`] to [`FilterExpr<Bitmap>`] using the file index
173    fn resolve(&self, file_index: &FileIndex) -> FilterExpr<Bitmap> {
174        match self {
175            FilterExpr::None => FilterExpr::None,
176            FilterExpr::Match(target) => match target {
177                FilterTarget::Field(field_name) => {
178                    // Find all field=value pairs with matching field name
179                    let matches: Vec<_> = file_index
180                        .bitmaps()
181                        .iter()
182                        .filter(|(pair, _)| pair.field() == field_name.as_str())
183                        .map(|(_, bitmap)| FilterExpr::Match(bitmap.clone()))
184                        .collect();
185
186                    match matches.len() {
187                        0 => FilterExpr::None,
188                        1 => matches.into_iter().next().unwrap(),
189                        _ => FilterExpr::Disjunction(matches),
190                    }
191                }
192                FilterTarget::Pair(pair) => {
193                    // Lookup specific field=value pair
194                    if let Some(bitmap) = file_index.bitmaps().get(pair) {
195                        FilterExpr::Match(bitmap.clone())
196                    } else {
197                        FilterExpr::None
198                    }
199                }
200            },
201            FilterExpr::Conjunction(filters) => {
202                let mut resolved = Vec::with_capacity(filters.len());
203                for filter in filters {
204                    let r = filter.resolve(file_index);
205                    if matches!(r, FilterExpr::None) {
206                        return FilterExpr::None;
207                    }
208                    resolved.push(r);
209                }
210
211                match resolved.len() {
212                    0 => FilterExpr::None,
213                    1 => resolved.into_iter().next().unwrap(),
214                    _ => FilterExpr::Conjunction(resolved),
215                }
216            }
217            FilterExpr::Disjunction(filters) => {
218                let mut resolved = Vec::with_capacity(filters.len());
219                for filter in filters {
220                    let r = filter.resolve(file_index);
221                    if !matches!(r, FilterExpr::None) {
222                        resolved.push(r);
223                    }
224                }
225
226                match resolved.len() {
227                    0 => FilterExpr::None,
228                    1 => resolved.into_iter().next().unwrap(),
229                    _ => FilterExpr::Disjunction(resolved),
230                }
231            }
232        }
233    }
234}
235
236impl std::fmt::Display for FilterExpr<FilterTarget> {
237    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238        match self {
239            FilterExpr::None => write!(f, "None"),
240            FilterExpr::Match(target) => write_filter_target(f, target),
241            FilterExpr::Conjunction(filters) => write_filter_list(f, filters, " AND "),
242            FilterExpr::Disjunction(filters) => write_filter_list(f, filters, " OR "),
243        }
244    }
245}
246
247fn write_filter_target(f: &mut std::fmt::Formatter<'_>, target: &FilterTarget) -> std::fmt::Result {
248    match target {
249        FilterTarget::Field(name) => write!(f, "{}", name),
250        FilterTarget::Pair(pair) => write!(f, "{}", pair),
251    }
252}
253
254fn write_filter_list(
255    f: &mut std::fmt::Formatter<'_>,
256    filters: &[FilterExpr<FilterTarget>],
257    separator: &str,
258) -> std::fmt::Result {
259    write!(f, "(")?;
260    for (i, filter) in filters.iter().enumerate() {
261        if i > 0 {
262            write!(f, "{separator}")?;
263        }
264        write!(f, "{}", filter)?;
265    }
266    write!(f, ")")
267}
268
269impl FilterExpr<Bitmap> {
270    /// Get all entry indices that match this filter expression
271    fn evaluate(&self) -> Bitmap {
272        match self {
273            Self::None => Bitmap::new(),
274            Self::Match(bitmap) => bitmap.clone(),
275            Self::Conjunction(filter_exprs) => {
276                if filter_exprs.is_empty() {
277                    return Bitmap::new();
278                }
279
280                let mut result = filter_exprs[0].evaluate();
281                for expr in filter_exprs.iter().skip(1) {
282                    result &= expr.evaluate();
283                    if result.is_empty() {
284                        break; // Early termination for empty conjunction
285                    }
286                }
287                result
288            }
289            Self::Disjunction(filter_exprs) => {
290                let mut result = Bitmap::new();
291                for expr in filter_exprs.iter() {
292                    result |= expr.evaluate();
293                }
294                result
295            }
296        }
297    }
298}