Skip to main content

featherdb_query/expr/
ops.rs

1//! Binary and unary operators
2
3use featherdb_core::{Error, Result, Value};
4
5// =============================================================================
6// Binary Operators
7// =============================================================================
8
9/// Binary operations between expressions
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum BinaryOp {
12    // Arithmetic
13    Add,
14    Sub,
15    Mul,
16    Div,
17    Mod,
18
19    // Comparison
20    Eq,
21    Ne,
22    Lt,
23    Le,
24    Gt,
25    Ge,
26
27    // Logical
28    And,
29    Or,
30
31    // String
32    Like,
33    Concat,
34}
35
36impl BinaryOp {
37    /// Evaluate binary operation on two values
38    pub fn eval(&self, left: &Value, right: &Value) -> Result<Value> {
39        use Value::*;
40
41        // SQL NULL propagation: arithmetic and comparison with NULL yields NULL
42        // Exceptions: IS NULL/IS NOT NULL (handled by UnaryOp), Eq/Ne (NULL != NULL),
43        // And/Or (three-valued logic)
44        match (left, right) {
45            (Null, _) | (_, Null) => match self {
46                BinaryOp::Add
47                | BinaryOp::Sub
48                | BinaryOp::Mul
49                | BinaryOp::Div
50                | BinaryOp::Mod
51                | BinaryOp::Concat => return Ok(Null),
52                BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge | BinaryOp::Like => {
53                    return Ok(Null)
54                }
55                BinaryOp::Eq => return Ok(Boolean(matches!((left, right), (Null, Null)))),
56                BinaryOp::Ne => return Ok(Boolean(!matches!((left, right), (Null, Null)))),
57                BinaryOp::And => match (left, right) {
58                    (Boolean(false), _) | (_, Boolean(false)) => return Ok(Boolean(false)),
59                    _ => return Ok(Null),
60                },
61                BinaryOp::Or => match (left, right) {
62                    (Boolean(true), _) | (_, Boolean(true)) => return Ok(Boolean(true)),
63                    _ => return Ok(Null),
64                },
65            },
66            _ => {}
67        }
68
69        match self {
70            // Arithmetic operations
71            BinaryOp::Add => match (left, right) {
72                (Integer(a), Integer(b)) => Ok(Integer(a + b)),
73                (Real(a), Real(b)) => Ok(Real(a + b)),
74                (Integer(a), Real(b)) => Ok(Real(*a as f64 + b)),
75                (Real(a), Integer(b)) => Ok(Real(a + *b as f64)),
76                // Date + interval arithmetic
77                (Text(date), Text(interval)) if Self::looks_like_interval(interval) => {
78                    Self::date_add(date, interval)
79                }
80                _ => Self::type_error("add", left, right),
81            },
82            BinaryOp::Sub => match (left, right) {
83                (Integer(a), Integer(b)) => Ok(Integer(a - b)),
84                (Real(a), Real(b)) => Ok(Real(a - b)),
85                (Integer(a), Real(b)) => Ok(Real(*a as f64 - b)),
86                (Real(a), Integer(b)) => Ok(Real(a - *b as f64)),
87                // Date - interval arithmetic
88                (Text(date), Text(interval)) if Self::looks_like_interval(interval) => {
89                    Self::date_sub(date, interval)
90                }
91                _ => Self::type_error("subtract", left, right),
92            },
93            BinaryOp::Mul => match (left, right) {
94                (Integer(a), Integer(b)) => Ok(Integer(a * b)),
95                (Real(a), Real(b)) => Ok(Real(a * b)),
96                (Integer(a), Real(b)) => Ok(Real(*a as f64 * b)),
97                (Real(a), Integer(b)) => Ok(Real(a * *b as f64)),
98                _ => Self::type_error("multiply", left, right),
99            },
100            BinaryOp::Div => match (left, right) {
101                (Integer(a), Integer(b)) => {
102                    if *b == 0 {
103                        Err(Error::Internal("Division by zero".into()))
104                    } else {
105                        Ok(Integer(a / b))
106                    }
107                }
108                (Real(a), Real(b)) => {
109                    if *b == 0.0 {
110                        Err(Error::Internal("Division by zero".into()))
111                    } else {
112                        Ok(Real(a / b))
113                    }
114                }
115                (Integer(a), Real(b)) => {
116                    if *b == 0.0 {
117                        Err(Error::Internal("Division by zero".into()))
118                    } else {
119                        Ok(Real(*a as f64 / b))
120                    }
121                }
122                (Real(a), Integer(b)) => {
123                    if *b == 0 {
124                        Err(Error::Internal("Division by zero".into()))
125                    } else {
126                        Ok(Real(a / *b as f64))
127                    }
128                }
129                _ => Self::type_error("divide", left, right),
130            },
131            BinaryOp::Mod => match (left, right) {
132                (Integer(a), Integer(b)) => {
133                    if *b == 0 {
134                        Err(Error::Internal("Modulo by zero".into()))
135                    } else {
136                        Ok(Integer(a % b))
137                    }
138                }
139                _ => Self::type_error("modulo", left, right),
140            },
141
142            // Comparison operations
143            BinaryOp::Eq => Ok(Boolean(left == right)),
144            BinaryOp::Ne => Ok(Boolean(left != right)),
145            BinaryOp::Lt => Ok(Boolean(left < right)),
146            BinaryOp::Le => Ok(Boolean(left <= right)),
147            BinaryOp::Gt => Ok(Boolean(left > right)),
148            BinaryOp::Ge => Ok(Boolean(left >= right)),
149
150            // Logical operations
151            BinaryOp::And => match (left, right) {
152                (Boolean(a), Boolean(b)) => Ok(Boolean(*a && *b)),
153                _ => Self::type_error("AND", left, right),
154            },
155            BinaryOp::Or => match (left, right) {
156                (Boolean(a), Boolean(b)) => Ok(Boolean(*a || *b)),
157                _ => Self::type_error("OR", left, right),
158            },
159
160            // String operations
161            BinaryOp::Like => match (left, right) {
162                (Text(text), Text(pattern)) => {
163                    Ok(Boolean(super::helpers::like_match(text, pattern)))
164                }
165                _ => Self::type_error("LIKE", left, right),
166            },
167            BinaryOp::Concat => match (left, right) {
168                (Text(a), Text(b)) => Ok(Text(format!("{}{}", a, b))),
169                _ => Self::type_error("CONCAT", left, right),
170            },
171        }
172    }
173
174    fn type_error<T>(op: &str, left: &Value, right: &Value) -> Result<T> {
175        Err(Error::Internal(format!(
176            "Type mismatch for {}: cannot apply to {:?} and {:?}",
177            op, left, right
178        )))
179    }
180
181    /// Check if a string looks like an interval ("N UNIT")
182    fn looks_like_interval(s: &str) -> bool {
183        let parts: Vec<&str> = s.split_whitespace().collect();
184        if parts.len() != 2 {
185            return false;
186        }
187        parts[0].parse::<i64>().is_ok()
188            && matches!(
189                parts[1].to_uppercase().as_str(),
190                "DAY"
191                    | "DAYS"
192                    | "MONTH"
193                    | "MONTHS"
194                    | "YEAR"
195                    | "YEARS"
196                    | "HOUR"
197                    | "HOURS"
198                    | "MINUTE"
199                    | "MINUTES"
200                    | "SECOND"
201                    | "SECONDS"
202            )
203    }
204
205    /// Parse interval string "N UNIT" into (count, unit)
206    fn parse_interval(interval: &str) -> Result<(i64, String)> {
207        let parts: Vec<&str> = interval.split_whitespace().collect();
208        let n = parts[0]
209            .parse::<i64>()
210            .map_err(|_| Error::Internal(format!("Invalid interval number: {}", parts[0])))?;
211        Ok((n, parts[1].to_uppercase()))
212    }
213
214    /// Parse date string "YYYY-MM-DD" into (year, month, day)
215    fn parse_date(date: &str) -> Result<(i32, u32, u32)> {
216        let date_part = date.split_whitespace().next().unwrap_or(date);
217        let parts: Vec<&str> = date_part.split('-').collect();
218        if parts.len() < 3 {
219            return Err(Error::Internal(format!("Invalid date format: {}", date)));
220        }
221        let y = parts[0]
222            .parse::<i32>()
223            .map_err(|_| Error::Internal(format!("Invalid year: {}", parts[0])))?;
224        let m = parts[1]
225            .parse::<u32>()
226            .map_err(|_| Error::Internal(format!("Invalid month: {}", parts[1])))?;
227        let d = parts[2]
228            .parse::<u32>()
229            .map_err(|_| Error::Internal(format!("Invalid day: {}", parts[2])))?;
230        Ok((y, m, d))
231    }
232
233    /// Days in a given month (handles leap years)
234    fn days_in_month(year: i32, month: u32) -> u32 {
235        match month {
236            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
237            4 | 6 | 9 | 11 => 30,
238            2 => {
239                if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
240                    29
241                } else {
242                    28
243                }
244            }
245            _ => 30,
246        }
247    }
248
249    /// Add an interval to a date
250    fn date_add(date: &str, interval: &str) -> Result<Value> {
251        let (n, unit) = Self::parse_interval(interval)?;
252        let (mut y, mut m, mut d) = Self::parse_date(date)?;
253
254        match unit.as_str() {
255            "YEAR" | "YEARS" => {
256                y += n as i32;
257                d = d.min(Self::days_in_month(y, m));
258            }
259            "MONTH" | "MONTHS" => {
260                let total_months = (y as i64) * 12 + (m as i64 - 1) + n;
261                y = (total_months / 12) as i32;
262                m = (total_months % 12 + 1) as u32;
263                if m == 0 {
264                    m = 12;
265                    y -= 1;
266                }
267                d = d.min(Self::days_in_month(y, m));
268            }
269            "DAY" | "DAYS" => {
270                // Convert to days since epoch, add, convert back
271                let days = Self::date_to_days(y, m, d) + n;
272                let (ny, nm, nd) = Self::days_to_date(days);
273                y = ny;
274                m = nm;
275                d = nd;
276                let _ = days;
277            }
278            _ => {
279                return Err(Error::Unsupported {
280                    feature: format!("Interval unit: {}", unit),
281                })
282            }
283        }
284
285        Ok(Value::Text(format!("{:04}-{:02}-{:02}", y, m, d)))
286    }
287
288    /// Subtract an interval from a date
289    fn date_sub(date: &str, interval: &str) -> Result<Value> {
290        let (n, unit) = Self::parse_interval(interval)?;
291        // Create negated interval
292        let negated = format!("{} {}", -n, unit);
293        Self::date_add(date, &negated)
294    }
295
296    /// Convert a date to days since civil epoch (0000-03-01)
297    /// Uses Howard Hinnant's algorithm: http://howardhinnant.github.io/date_algorithms.html
298    fn date_to_days(y: i32, m: u32, d: u32) -> i64 {
299        let y = y as i64;
300        let m = m as i64;
301        let d = d as i64;
302        // Shift year so March is the first month (simplifies leap year handling)
303        let yr = if m <= 2 { y - 1 } else { y };
304        let era = if yr >= 0 { yr } else { yr - 399 } / 400;
305        let yoe = yr - era * 400; // year of era [0, 399]
306        let mo = if m > 2 { m - 3 } else { m + 9 }; // month offset [0, 11] starting March
307        let doy = (153 * mo + 2) / 5 + d - 1; // day of year [0, 365]
308        let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // day of era [0, 146096]
309        era * 146097 + doe - 719468 // days since 1970-01-01
310    }
311
312    /// Convert days since civil epoch back to date
313    /// Uses Howard Hinnant's algorithm: http://howardhinnant.github.io/date_algorithms.html
314    fn days_to_date(days: i64) -> (i32, u32, u32) {
315        let z = days + 719468;
316        let era = if z >= 0 { z } else { z - 146096 } / 146097;
317        let doe = z - era * 146097; // day of era [0, 146096]
318        let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
319        let y = yoe + era * 400;
320        let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
321        let mp = (5 * doy + 2) / 153;
322        let d = doy - (153 * mp + 2) / 5 + 1;
323        let m = if mp < 10 { mp + 3 } else { mp - 9 };
324        let y = if m <= 2 { y + 1 } else { y };
325        (y as i32, m as u32, d as u32)
326    }
327}
328
329// =============================================================================
330// Unary Operators
331// =============================================================================
332
333/// Unary operations on expressions
334#[derive(Debug, Clone, Copy, PartialEq, Eq)]
335pub enum UnaryOp {
336    /// Negation (-)
337    Negate,
338    /// Logical NOT
339    Not,
340    /// IS NULL check
341    IsNull,
342    /// IS NOT NULL check
343    IsNotNull,
344}
345
346impl UnaryOp {
347    /// Evaluate unary operation on a value
348    pub fn eval(&self, value: &Value) -> Result<Value> {
349        match self {
350            UnaryOp::Negate => match value {
351                Value::Null => Ok(Value::Null),
352                Value::Integer(n) => Ok(Value::Integer(-n)),
353                Value::Real(f) => Ok(Value::Real(-f)),
354                _ => Err(Error::Internal(format!(
355                    "Cannot negate non-numeric value: {:?}",
356                    value
357                ))),
358            },
359            UnaryOp::Not => match value {
360                Value::Null => Ok(Value::Null),
361                Value::Boolean(b) => Ok(Value::Boolean(!b)),
362                _ => Err(Error::Internal(format!(
363                    "Cannot apply NOT to non-boolean value: {:?}",
364                    value
365                ))),
366            },
367            UnaryOp::IsNull => Ok(Value::Boolean(matches!(value, Value::Null))),
368            UnaryOp::IsNotNull => Ok(Value::Boolean(!matches!(value, Value::Null))),
369        }
370    }
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    // BinaryOp tests
378    #[test]
379    fn test_binary_op_add() {
380        assert_eq!(
381            BinaryOp::Add
382                .eval(&Value::Integer(2), &Value::Integer(3))
383                .unwrap(),
384            Value::Integer(5)
385        );
386        assert_eq!(
387            BinaryOp::Add
388                .eval(&Value::Real(2.5), &Value::Real(3.5))
389                .unwrap(),
390            Value::Real(6.0)
391        );
392        assert_eq!(
393            BinaryOp::Add
394                .eval(&Value::Integer(2), &Value::Real(3.5))
395                .unwrap(),
396            Value::Real(5.5)
397        );
398    }
399
400    #[test]
401    fn test_binary_op_subtract() {
402        assert_eq!(
403            BinaryOp::Sub
404                .eval(&Value::Integer(5), &Value::Integer(3))
405                .unwrap(),
406            Value::Integer(2)
407        );
408        assert_eq!(
409            BinaryOp::Sub
410                .eval(&Value::Real(5.5), &Value::Real(2.5))
411                .unwrap(),
412            Value::Real(3.0)
413        );
414    }
415
416    #[test]
417    fn test_binary_op_multiply() {
418        assert_eq!(
419            BinaryOp::Mul
420                .eval(&Value::Integer(2), &Value::Integer(3))
421                .unwrap(),
422            Value::Integer(6)
423        );
424        assert_eq!(
425            BinaryOp::Mul
426                .eval(&Value::Real(2.5), &Value::Real(2.0))
427                .unwrap(),
428            Value::Real(5.0)
429        );
430    }
431
432    #[test]
433    fn test_binary_op_divide() {
434        assert_eq!(
435            BinaryOp::Div
436                .eval(&Value::Integer(6), &Value::Integer(3))
437                .unwrap(),
438            Value::Integer(2)
439        );
440        assert_eq!(
441            BinaryOp::Div
442                .eval(&Value::Real(5.0), &Value::Real(2.0))
443                .unwrap(),
444            Value::Real(2.5)
445        );
446
447        // Division by zero
448        assert!(BinaryOp::Div
449            .eval(&Value::Integer(5), &Value::Integer(0))
450            .is_err());
451        assert!(BinaryOp::Div
452            .eval(&Value::Real(5.0), &Value::Real(0.0))
453            .is_err());
454    }
455
456    #[test]
457    fn test_binary_op_modulo() {
458        assert_eq!(
459            BinaryOp::Mod
460                .eval(&Value::Integer(7), &Value::Integer(3))
461                .unwrap(),
462            Value::Integer(1)
463        );
464
465        // Modulo by zero
466        assert!(BinaryOp::Mod
467            .eval(&Value::Integer(5), &Value::Integer(0))
468            .is_err());
469    }
470
471    #[test]
472    fn test_binary_op_comparison() {
473        assert_eq!(
474            BinaryOp::Eq
475                .eval(&Value::Integer(5), &Value::Integer(5))
476                .unwrap(),
477            Value::Boolean(true)
478        );
479        assert_eq!(
480            BinaryOp::Ne
481                .eval(&Value::Integer(5), &Value::Integer(3))
482                .unwrap(),
483            Value::Boolean(true)
484        );
485        assert_eq!(
486            BinaryOp::Lt
487                .eval(&Value::Integer(3), &Value::Integer(5))
488                .unwrap(),
489            Value::Boolean(true)
490        );
491        assert_eq!(
492            BinaryOp::Le
493                .eval(&Value::Integer(5), &Value::Integer(5))
494                .unwrap(),
495            Value::Boolean(true)
496        );
497        assert_eq!(
498            BinaryOp::Gt
499                .eval(&Value::Integer(7), &Value::Integer(5))
500                .unwrap(),
501            Value::Boolean(true)
502        );
503        assert_eq!(
504            BinaryOp::Ge
505                .eval(&Value::Integer(5), &Value::Integer(5))
506                .unwrap(),
507            Value::Boolean(true)
508        );
509    }
510
511    #[test]
512    fn test_binary_op_logical() {
513        assert_eq!(
514            BinaryOp::And
515                .eval(&Value::Boolean(true), &Value::Boolean(true))
516                .unwrap(),
517            Value::Boolean(true)
518        );
519        assert_eq!(
520            BinaryOp::And
521                .eval(&Value::Boolean(true), &Value::Boolean(false))
522                .unwrap(),
523            Value::Boolean(false)
524        );
525        assert_eq!(
526            BinaryOp::Or
527                .eval(&Value::Boolean(true), &Value::Boolean(false))
528                .unwrap(),
529            Value::Boolean(true)
530        );
531        assert_eq!(
532            BinaryOp::Or
533                .eval(&Value::Boolean(false), &Value::Boolean(false))
534                .unwrap(),
535            Value::Boolean(false)
536        );
537    }
538
539    #[test]
540    fn test_binary_op_like() {
541        assert_eq!(
542            BinaryOp::Like
543                .eval(&Value::Text("hello".into()), &Value::Text("h%".into()))
544                .unwrap(),
545            Value::Boolean(true)
546        );
547        assert_eq!(
548            BinaryOp::Like
549                .eval(&Value::Text("hello".into()), &Value::Text("%lo".into()))
550                .unwrap(),
551            Value::Boolean(true)
552        );
553        assert_eq!(
554            BinaryOp::Like
555                .eval(&Value::Text("hello".into()), &Value::Text("h_llo".into()))
556                .unwrap(),
557            Value::Boolean(true)
558        );
559    }
560
561    // UnaryOp tests
562    #[test]
563    fn test_unary_op_negate() {
564        assert_eq!(
565            UnaryOp::Negate.eval(&Value::Integer(5)).unwrap(),
566            Value::Integer(-5)
567        );
568        assert_eq!(
569            UnaryOp::Negate.eval(&Value::Real(2.5)).unwrap(),
570            Value::Real(-2.5)
571        );
572        assert!(UnaryOp::Negate.eval(&Value::Text("hello".into())).is_err());
573    }
574
575    #[test]
576    fn test_unary_op_not() {
577        assert_eq!(
578            UnaryOp::Not.eval(&Value::Boolean(true)).unwrap(),
579            Value::Boolean(false)
580        );
581        assert_eq!(
582            UnaryOp::Not.eval(&Value::Boolean(false)).unwrap(),
583            Value::Boolean(true)
584        );
585        assert!(UnaryOp::Not.eval(&Value::Integer(5)).is_err());
586    }
587
588    #[test]
589    fn test_unary_op_is_null() {
590        assert_eq!(
591            UnaryOp::IsNull.eval(&Value::Null).unwrap(),
592            Value::Boolean(true)
593        );
594        assert_eq!(
595            UnaryOp::IsNull.eval(&Value::Integer(5)).unwrap(),
596            Value::Boolean(false)
597        );
598    }
599
600    #[test]
601    fn test_unary_op_is_not_null() {
602        assert_eq!(
603            UnaryOp::IsNotNull.eval(&Value::Null).unwrap(),
604            Value::Boolean(false)
605        );
606        assert_eq!(
607            UnaryOp::IsNotNull.eval(&Value::Integer(5)).unwrap(),
608            Value::Boolean(true)
609        );
610    }
611}