Skip to main content

oxigdal_query/executor/join/
value.rs

1//! JoinValue type and operations
2
3/// A value that can be compared in join conditions.
4#[derive(Debug, Clone, PartialEq)]
5pub enum JoinValue {
6    /// Null value.
7    Null,
8    /// Boolean value.
9    Boolean(bool),
10    /// Integer value (stored as i64 for uniformity).
11    Integer(i64),
12    /// Float value (stored as f64 for uniformity).
13    Float(f64),
14    /// String value.
15    String(String),
16}
17
18impl JoinValue {
19    /// Check if this value is null.
20    pub fn is_null(&self) -> bool {
21        matches!(self, JoinValue::Null)
22    }
23
24    /// Convert to hashable key for hash join.
25    pub fn to_hash_key(&self) -> String {
26        match self {
27            JoinValue::Null => "__NULL__".to_string(),
28            JoinValue::Boolean(b) => format!("b:{}", b),
29            JoinValue::Integer(i) => format!("i:{}", i),
30            JoinValue::Float(f) => format!("f:{:?}", f),
31            JoinValue::String(s) => format!("s:{}", s),
32        }
33    }
34
35    /// Compare with another value for equality.
36    pub fn equals(&self, other: &JoinValue) -> Option<bool> {
37        if self.is_null() || other.is_null() {
38            return None; // NULL = NULL is undefined
39        }
40
41        match (self, other) {
42            (JoinValue::Boolean(a), JoinValue::Boolean(b)) => Some(a == b),
43            (JoinValue::Integer(a), JoinValue::Integer(b)) => Some(a == b),
44            (JoinValue::Integer(a), JoinValue::Float(b)) => Some((*a as f64) == *b),
45            (JoinValue::Float(a), JoinValue::Integer(b)) => Some(*a == (*b as f64)),
46            (JoinValue::Float(a), JoinValue::Float(b)) => Some(a == b),
47            (JoinValue::String(a), JoinValue::String(b)) => Some(a == b),
48            _ => Some(false), // Different types are not equal
49        }
50    }
51
52    /// Compare with another value.
53    pub fn compare(&self, other: &JoinValue) -> Option<std::cmp::Ordering> {
54        if self.is_null() || other.is_null() {
55            return None;
56        }
57
58        match (self, other) {
59            (JoinValue::Boolean(a), JoinValue::Boolean(b)) => Some(a.cmp(b)),
60            (JoinValue::Integer(a), JoinValue::Integer(b)) => Some(a.cmp(b)),
61            (JoinValue::Integer(a), JoinValue::Float(b)) => (*a as f64).partial_cmp(b),
62            (JoinValue::Float(a), JoinValue::Integer(b)) => a.partial_cmp(&(*b as f64)),
63            (JoinValue::Float(a), JoinValue::Float(b)) => a.partial_cmp(b),
64            (JoinValue::String(a), JoinValue::String(b)) => Some(a.cmp(b)),
65            _ => None, // Cannot compare different types
66        }
67    }
68
69    /// Negate for arithmetic operations.
70    pub fn negate(&self) -> Option<JoinValue> {
71        match self {
72            JoinValue::Integer(i) => Some(JoinValue::Integer(-i)),
73            JoinValue::Float(f) => Some(JoinValue::Float(-f)),
74            _ => None,
75        }
76    }
77
78    /// Logical NOT.
79    pub fn not(&self) -> Option<JoinValue> {
80        match self {
81            JoinValue::Boolean(b) => Some(JoinValue::Boolean(!b)),
82            _ => None,
83        }
84    }
85
86    /// Add two values.
87    pub fn add(&self, other: &JoinValue) -> Option<JoinValue> {
88        match (self, other) {
89            (JoinValue::Integer(a), JoinValue::Integer(b)) => Some(JoinValue::Integer(a + b)),
90            (JoinValue::Integer(a), JoinValue::Float(b)) => Some(JoinValue::Float(*a as f64 + b)),
91            (JoinValue::Float(a), JoinValue::Integer(b)) => Some(JoinValue::Float(a + *b as f64)),
92            (JoinValue::Float(a), JoinValue::Float(b)) => Some(JoinValue::Float(a + b)),
93            (JoinValue::String(a), JoinValue::String(b)) => {
94                Some(JoinValue::String(format!("{}{}", a, b)))
95            }
96            _ => None,
97        }
98    }
99
100    /// Subtract two values.
101    pub fn subtract(&self, other: &JoinValue) -> Option<JoinValue> {
102        match (self, other) {
103            (JoinValue::Integer(a), JoinValue::Integer(b)) => Some(JoinValue::Integer(a - b)),
104            (JoinValue::Integer(a), JoinValue::Float(b)) => Some(JoinValue::Float(*a as f64 - b)),
105            (JoinValue::Float(a), JoinValue::Integer(b)) => Some(JoinValue::Float(a - *b as f64)),
106            (JoinValue::Float(a), JoinValue::Float(b)) => Some(JoinValue::Float(a - b)),
107            _ => None,
108        }
109    }
110
111    /// Multiply two values.
112    pub fn multiply(&self, other: &JoinValue) -> Option<JoinValue> {
113        match (self, other) {
114            (JoinValue::Integer(a), JoinValue::Integer(b)) => Some(JoinValue::Integer(a * b)),
115            (JoinValue::Integer(a), JoinValue::Float(b)) => Some(JoinValue::Float(*a as f64 * b)),
116            (JoinValue::Float(a), JoinValue::Integer(b)) => Some(JoinValue::Float(a * *b as f64)),
117            (JoinValue::Float(a), JoinValue::Float(b)) => Some(JoinValue::Float(a * b)),
118            _ => None,
119        }
120    }
121
122    /// Divide two values.
123    pub fn divide(&self, other: &JoinValue) -> Option<JoinValue> {
124        match (self, other) {
125            (JoinValue::Integer(a), JoinValue::Integer(b)) if *b != 0 => {
126                Some(JoinValue::Integer(a / b))
127            }
128            (JoinValue::Integer(a), JoinValue::Float(b)) if *b != 0.0 => {
129                Some(JoinValue::Float(*a as f64 / b))
130            }
131            (JoinValue::Float(a), JoinValue::Integer(b)) if *b != 0 => {
132                Some(JoinValue::Float(a / *b as f64))
133            }
134            (JoinValue::Float(a), JoinValue::Float(b)) if *b != 0.0 => {
135                Some(JoinValue::Float(a / b))
136            }
137            _ => None,
138        }
139    }
140
141    /// Modulo two values.
142    pub fn modulo(&self, other: &JoinValue) -> Option<JoinValue> {
143        match (self, other) {
144            (JoinValue::Integer(a), JoinValue::Integer(b)) if *b != 0 => {
145                Some(JoinValue::Integer(a % b))
146            }
147            (JoinValue::Integer(a), JoinValue::Float(b)) if *b != 0.0 => {
148                Some(JoinValue::Float(*a as f64 % b))
149            }
150            (JoinValue::Float(a), JoinValue::Integer(b)) if *b != 0 => {
151                Some(JoinValue::Float(a % *b as f64))
152            }
153            (JoinValue::Float(a), JoinValue::Float(b)) if *b != 0.0 => {
154                Some(JoinValue::Float(a % b))
155            }
156            _ => None,
157        }
158    }
159
160    /// Convert to boolean for logical operations.
161    pub fn to_bool(&self) -> Option<bool> {
162        match self {
163            JoinValue::Boolean(b) => Some(*b),
164            JoinValue::Null => None,
165            _ => None,
166        }
167    }
168
169    /// Check if string matches LIKE pattern.
170    pub fn matches_like(&self, pattern: &JoinValue) -> Option<bool> {
171        match (self, pattern) {
172            (JoinValue::String(s), JoinValue::String(p)) => Some(Self::like_match(s, p)),
173            _ => None,
174        }
175    }
176
177    /// Simple LIKE pattern matching (supports % and _).
178    pub fn like_match(text: &str, pattern: &str) -> bool {
179        let text_chars: Vec<char> = text.chars().collect();
180        let pattern_chars: Vec<char> = pattern.chars().collect();
181
182        Self::like_match_recursive(&text_chars, 0, &pattern_chars, 0)
183    }
184
185    fn like_match_recursive(text: &[char], ti: usize, pattern: &[char], pi: usize) -> bool {
186        if pi >= pattern.len() {
187            return ti >= text.len();
188        }
189
190        let pattern_char = pattern[pi];
191
192        match pattern_char {
193            '%' => {
194                // Match zero or more characters
195                for i in ti..=text.len() {
196                    if Self::like_match_recursive(text, i, pattern, pi + 1) {
197                        return true;
198                    }
199                }
200                false
201            }
202            '_' => {
203                // Match exactly one character
204                if ti < text.len() {
205                    Self::like_match_recursive(text, ti + 1, pattern, pi + 1)
206                } else {
207                    false
208                }
209            }
210            c => {
211                // Match exact character (case-insensitive for simplicity)
212                if ti < text.len() && text[ti].eq_ignore_ascii_case(&c) {
213                    Self::like_match_recursive(text, ti + 1, pattern, pi + 1)
214                } else {
215                    false
216                }
217            }
218        }
219    }
220}