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            // Checked arithmetic: overflow yields None (surfaced as an execution
90            // error by the caller) instead of panicking or silently wrapping.
91            (JoinValue::Integer(a), JoinValue::Integer(b)) => {
92                a.checked_add(*b).map(JoinValue::Integer)
93            }
94            (JoinValue::Integer(a), JoinValue::Float(b)) => Some(JoinValue::Float(*a as f64 + b)),
95            (JoinValue::Float(a), JoinValue::Integer(b)) => Some(JoinValue::Float(a + *b as f64)),
96            (JoinValue::Float(a), JoinValue::Float(b)) => Some(JoinValue::Float(a + b)),
97            (JoinValue::String(a), JoinValue::String(b)) => {
98                Some(JoinValue::String(format!("{}{}", a, b)))
99            }
100            _ => None,
101        }
102    }
103
104    /// Subtract two values.
105    pub fn subtract(&self, other: &JoinValue) -> Option<JoinValue> {
106        match (self, other) {
107            (JoinValue::Integer(a), JoinValue::Integer(b)) => {
108                a.checked_sub(*b).map(JoinValue::Integer)
109            }
110            (JoinValue::Integer(a), JoinValue::Float(b)) => Some(JoinValue::Float(*a as f64 - b)),
111            (JoinValue::Float(a), JoinValue::Integer(b)) => Some(JoinValue::Float(a - *b as f64)),
112            (JoinValue::Float(a), JoinValue::Float(b)) => Some(JoinValue::Float(a - b)),
113            _ => None,
114        }
115    }
116
117    /// Multiply two values.
118    pub fn multiply(&self, other: &JoinValue) -> Option<JoinValue> {
119        match (self, other) {
120            (JoinValue::Integer(a), JoinValue::Integer(b)) => {
121                a.checked_mul(*b).map(JoinValue::Integer)
122            }
123            (JoinValue::Integer(a), JoinValue::Float(b)) => Some(JoinValue::Float(*a as f64 * b)),
124            (JoinValue::Float(a), JoinValue::Integer(b)) => Some(JoinValue::Float(a * *b as f64)),
125            (JoinValue::Float(a), JoinValue::Float(b)) => Some(JoinValue::Float(a * b)),
126            _ => None,
127        }
128    }
129
130    /// Divide two values.
131    pub fn divide(&self, other: &JoinValue) -> Option<JoinValue> {
132        match (self, other) {
133            (JoinValue::Integer(a), JoinValue::Integer(b)) if *b != 0 => {
134                Some(JoinValue::Integer(a / b))
135            }
136            (JoinValue::Integer(a), JoinValue::Float(b)) if *b != 0.0 => {
137                Some(JoinValue::Float(*a as f64 / b))
138            }
139            (JoinValue::Float(a), JoinValue::Integer(b)) if *b != 0 => {
140                Some(JoinValue::Float(a / *b as f64))
141            }
142            (JoinValue::Float(a), JoinValue::Float(b)) if *b != 0.0 => {
143                Some(JoinValue::Float(a / b))
144            }
145            _ => None,
146        }
147    }
148
149    /// Modulo two values.
150    pub fn modulo(&self, other: &JoinValue) -> Option<JoinValue> {
151        match (self, other) {
152            (JoinValue::Integer(a), JoinValue::Integer(b)) if *b != 0 => {
153                Some(JoinValue::Integer(a % b))
154            }
155            (JoinValue::Integer(a), JoinValue::Float(b)) if *b != 0.0 => {
156                Some(JoinValue::Float(*a as f64 % b))
157            }
158            (JoinValue::Float(a), JoinValue::Integer(b)) if *b != 0 => {
159                Some(JoinValue::Float(a % *b as f64))
160            }
161            (JoinValue::Float(a), JoinValue::Float(b)) if *b != 0.0 => {
162                Some(JoinValue::Float(a % b))
163            }
164            _ => None,
165        }
166    }
167
168    /// Convert to boolean for logical operations.
169    pub fn to_bool(&self) -> Option<bool> {
170        match self {
171            JoinValue::Boolean(b) => Some(*b),
172            JoinValue::Null => None,
173            _ => None,
174        }
175    }
176
177    /// Check if string matches a case-sensitive `LIKE` pattern.
178    pub fn matches_like(&self, pattern: &JoinValue) -> Option<bool> {
179        match (self, pattern) {
180            (JoinValue::String(s), JoinValue::String(p)) => Some(Self::like_match(s, p)),
181            _ => None,
182        }
183    }
184
185    /// Check if string matches a case-insensitive `ILIKE` pattern.
186    pub fn matches_ilike(&self, pattern: &JoinValue) -> Option<bool> {
187        match (self, pattern) {
188            (JoinValue::String(s), JoinValue::String(p)) => {
189                Some(crate::executor::like::like_match(s, p, true))
190            }
191            _ => None,
192        }
193    }
194
195    /// Simple case-sensitive `LIKE` pattern matching (supports `%` and `_`).
196    pub fn like_match(text: &str, pattern: &str) -> bool {
197        crate::executor::like::like_match(text, pattern, false)
198    }
199}