Skip to main content

orc_rust/
predicate.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Predicate types for row group filtering
19//!
20//! This module provides simplified predicate expressions that can be evaluated
21//! against row group statistics to filter out row groups before decoding.
22
23/// A simplified value type for predicates
24///
25/// This is a simplified representation of scalar values for predicate evaluation.
26/// In the future, this could be replaced with arrow's ScalarValue if available.
27#[derive(Debug, Clone, PartialEq)]
28pub enum PredicateValue {
29    /// Boolean value
30    Boolean(Option<bool>),
31    /// 8-bit signed integer
32    Int8(Option<i8>),
33    /// 16-bit signed integer
34    Int16(Option<i16>),
35    /// 32-bit signed integer
36    Int32(Option<i32>),
37    /// 64-bit signed integer
38    Int64(Option<i64>),
39    /// 32-bit floating point
40    Float32(Option<f32>),
41    /// 64-bit floating point
42    Float64(Option<f64>),
43    /// UTF-8 string
44    Utf8(Option<String>),
45}
46
47// For backward compatibility, we'll use PredicateValue as the value type
48// Users can convert from arrow types if needed
49pub type ScalarValue = PredicateValue;
50
51/// Comparison operator for predicates
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum ComparisonOp {
54    /// Equal to
55    Equal,
56    /// Not equal to
57    NotEqual,
58    /// Less than
59    LessThan,
60    /// Less than or equal to
61    LessThanOrEqual,
62    /// Greater than
63    GreaterThan,
64    /// Greater than or equal to
65    GreaterThanOrEqual,
66}
67
68impl ComparisonOp {
69    /// Returns the negated comparison operator.
70    pub fn negate(&self) -> Self {
71        match self {
72            ComparisonOp::Equal => ComparisonOp::NotEqual,
73            ComparisonOp::NotEqual => ComparisonOp::Equal,
74            ComparisonOp::LessThan => ComparisonOp::GreaterThanOrEqual,
75            ComparisonOp::LessThanOrEqual => ComparisonOp::GreaterThan,
76            ComparisonOp::GreaterThan => ComparisonOp::LessThanOrEqual,
77            ComparisonOp::GreaterThanOrEqual => ComparisonOp::LessThan,
78        }
79    }
80}
81
82/// A predicate that can be evaluated against row group statistics
83///
84/// Predicates are simplified expressions used for filtering row groups before
85/// decoding. They support basic comparison operations, NULL checks, and logical
86/// combinations (AND, OR, NOT).
87///
88/// # Example
89///
90/// ```rust
91/// use orc_rust::predicate::{Predicate, ComparisonOp, PredicateValue};
92///
93/// // Create a predicate: age >= 18
94/// let predicate = Predicate::gte("age", PredicateValue::Int32(Some(18)));
95///
96/// // Create a compound predicate: age >= 18 AND city = 'NYC'
97/// let predicate = Predicate::and(vec![
98///     Predicate::gte("age", PredicateValue::Int32(Some(18))),
99///     Predicate::eq("city", PredicateValue::Utf8(Some("NYC".to_string()))),
100/// ]);
101/// ```
102#[derive(Debug, Clone, PartialEq)]
103pub enum Predicate {
104    /// Column comparison: column <op> literal
105    Comparison {
106        /// Column name to compare
107        column: String,
108        /// Comparison operator
109        op: ComparisonOp,
110        /// Value to compare against
111        value: ScalarValue,
112    },
113    /// IS NULL check
114    IsNull {
115        /// Column name to check
116        column: String,
117    },
118    /// IS NOT NULL check
119    IsNotNull {
120        /// Column name to check
121        column: String,
122    },
123    /// Logical AND of predicates
124    And(Vec<Predicate>),
125    /// Logical OR of predicates
126    Or(Vec<Predicate>),
127    /// Logical NOT
128    Not(Box<Predicate>),
129}
130
131impl Predicate {
132    /// Create a comparison predicate: column <op> value
133    pub fn comparison(column: &str, op: ComparisonOp, value: ScalarValue) -> Self {
134        Self::Comparison {
135            column: column.to_string(),
136            op,
137            value,
138        }
139    }
140
141    /// Create a predicate for column == value
142    pub fn eq(column: &str, value: ScalarValue) -> Self {
143        Self::comparison(column, ComparisonOp::Equal, value)
144    }
145
146    /// Create a predicate for column != value
147    pub fn ne(column: &str, value: ScalarValue) -> Self {
148        Self::comparison(column, ComparisonOp::NotEqual, value)
149    }
150
151    /// Create a predicate for column < value
152    pub fn lt(column: &str, value: ScalarValue) -> Self {
153        Self::comparison(column, ComparisonOp::LessThan, value)
154    }
155
156    /// Create a predicate for column <= value
157    pub fn lte(column: &str, value: ScalarValue) -> Self {
158        Self::comparison(column, ComparisonOp::LessThanOrEqual, value)
159    }
160
161    /// Create a predicate for column > value
162    pub fn gt(column: &str, value: ScalarValue) -> Self {
163        Self::comparison(column, ComparisonOp::GreaterThan, value)
164    }
165
166    /// Create a predicate for column >= value
167    pub fn gte(column: &str, value: ScalarValue) -> Self {
168        Self::comparison(column, ComparisonOp::GreaterThanOrEqual, value)
169    }
170
171    /// Create a predicate for column IS NULL
172    pub fn is_null(column: &str) -> Self {
173        Self::IsNull {
174            column: column.to_string(),
175        }
176    }
177
178    /// Create a predicate for column IS NOT NULL
179    pub fn is_not_null(column: &str) -> Self {
180        Self::IsNotNull {
181            column: column.to_string(),
182        }
183    }
184
185    /// Combine predicates with AND
186    pub fn and(predicates: Vec<Predicate>) -> Self {
187        Self::And(predicates)
188    }
189
190    /// Combine predicates with OR
191    pub fn or(predicates: Vec<Predicate>) -> Self {
192        Self::Or(predicates)
193    }
194
195    /// Negate a predicate
196    #[allow(clippy::should_implement_trait)]
197    pub fn not(predicate: Predicate) -> Self {
198        Self::Not(Box::new(predicate))
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_predicate_creation() {
208        let p1 = Predicate::eq("age", ScalarValue::Int32(Some(18)));
209        assert!(
210            matches!(p1, Predicate::Comparison { ref column, op: ComparisonOp::Equal, .. } if column == "age")
211        );
212
213        let p2 = Predicate::gt("price", ScalarValue::Float64(Some(100.0)));
214        assert!(
215            matches!(p2, Predicate::Comparison { ref column, op: ComparisonOp::GreaterThan, .. } if column == "price")
216        );
217
218        let p3 = Predicate::is_null("description");
219        assert!(matches!(p3, Predicate::IsNull { ref column } if column == "description"));
220
221        let combined = Predicate::and(vec![p1, p2]);
222        assert!(matches!(combined, Predicate::And(_)));
223    }
224}