sql_cli/sql/functions/
trigonometry.rs

1use anyhow::{anyhow, Result};
2
3use crate::data::datatable::DataValue;
4use crate::sql::functions::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
5
6/// SIN function - Sine of angle in radians
7pub struct SinFunction;
8
9impl SqlFunction for SinFunction {
10    fn signature(&self) -> FunctionSignature {
11        FunctionSignature {
12            name: "SIN",
13            category: FunctionCategory::Mathematical,
14            arg_count: ArgCount::Fixed(1),
15            description: "Returns the sine of an angle in radians",
16            returns: "FLOAT",
17            examples: vec![
18                "SELECT SIN(0)",           // Returns 0
19                "SELECT SIN(PI()/2)",      // Returns 1
20                "SELECT SIN(RADIANS(30))", // Returns 0.5
21            ],
22        }
23    }
24
25    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
26        self.validate_args(args)?;
27
28        let radians = match &args[0] {
29            DataValue::Integer(n) => *n as f64,
30            DataValue::Float(f) => *f,
31            DataValue::Null => return Ok(DataValue::Null),
32            _ => return Err(anyhow!("SIN requires a numeric argument")),
33        };
34
35        Ok(DataValue::Float(radians.sin()))
36    }
37}
38
39/// COS function - Cosine of angle in radians
40pub struct CosFunction;
41
42impl SqlFunction for CosFunction {
43    fn signature(&self) -> FunctionSignature {
44        FunctionSignature {
45            name: "COS",
46            category: FunctionCategory::Mathematical,
47            arg_count: ArgCount::Fixed(1),
48            description: "Returns the cosine of an angle in radians",
49            returns: "FLOAT",
50            examples: vec![
51                "SELECT COS(0)",           // Returns 1
52                "SELECT COS(PI())",        // Returns -1
53                "SELECT COS(RADIANS(60))", // Returns 0.5
54            ],
55        }
56    }
57
58    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
59        self.validate_args(args)?;
60
61        let radians = match &args[0] {
62            DataValue::Integer(n) => *n as f64,
63            DataValue::Float(f) => *f,
64            DataValue::Null => return Ok(DataValue::Null),
65            _ => return Err(anyhow!("COS requires a numeric argument")),
66        };
67
68        Ok(DataValue::Float(radians.cos()))
69    }
70}
71
72/// TAN function - Tangent of angle in radians
73pub struct TanFunction;
74
75impl SqlFunction for TanFunction {
76    fn signature(&self) -> FunctionSignature {
77        FunctionSignature {
78            name: "TAN",
79            category: FunctionCategory::Mathematical,
80            arg_count: ArgCount::Fixed(1),
81            description: "Returns the tangent of an angle in radians",
82            returns: "FLOAT",
83            examples: vec![
84                "SELECT TAN(0)",           // Returns 0
85                "SELECT TAN(PI()/4)",      // Returns 1
86                "SELECT TAN(RADIANS(45))", // Returns 1
87            ],
88        }
89    }
90
91    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
92        self.validate_args(args)?;
93
94        let radians = match &args[0] {
95            DataValue::Integer(n) => *n as f64,
96            DataValue::Float(f) => *f,
97            DataValue::Null => return Ok(DataValue::Null),
98            _ => return Err(anyhow!("TAN requires a numeric argument")),
99        };
100
101        Ok(DataValue::Float(radians.tan()))
102    }
103}
104
105/// ASIN function - Arcsine (inverse sine) returns angle in radians
106pub struct AsinFunction;
107
108impl SqlFunction for AsinFunction {
109    fn signature(&self) -> FunctionSignature {
110        FunctionSignature {
111            name: "ASIN",
112            category: FunctionCategory::Mathematical,
113            arg_count: ArgCount::Fixed(1),
114            description:
115                "Returns the arcsine (inverse sine) in radians. Input must be between -1 and 1",
116            returns: "FLOAT",
117            examples: vec![
118                "SELECT ASIN(0)",            // Returns 0
119                "SELECT ASIN(1)",            // Returns PI/2
120                "SELECT DEGREES(ASIN(0.5))", // Returns 30
121            ],
122        }
123    }
124
125    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
126        self.validate_args(args)?;
127
128        let value = match &args[0] {
129            DataValue::Integer(n) => *n as f64,
130            DataValue::Float(f) => *f,
131            DataValue::Null => return Ok(DataValue::Null),
132            _ => return Err(anyhow!("ASIN requires a numeric argument")),
133        };
134
135        if value < -1.0 || value > 1.0 {
136            return Err(anyhow!(
137                "ASIN input must be between -1 and 1, got {}",
138                value
139            ));
140        }
141
142        Ok(DataValue::Float(value.asin()))
143    }
144}
145
146/// ACOS function - Arccosine (inverse cosine) returns angle in radians
147pub struct AcosFunction;
148
149impl SqlFunction for AcosFunction {
150    fn signature(&self) -> FunctionSignature {
151        FunctionSignature {
152            name: "ACOS",
153            category: FunctionCategory::Mathematical,
154            arg_count: ArgCount::Fixed(1),
155            description:
156                "Returns the arccosine (inverse cosine) in radians. Input must be between -1 and 1",
157            returns: "FLOAT",
158            examples: vec![
159                "SELECT ACOS(1)",            // Returns 0
160                "SELECT ACOS(0)",            // Returns PI/2
161                "SELECT DEGREES(ACOS(0.5))", // Returns 60
162            ],
163        }
164    }
165
166    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
167        self.validate_args(args)?;
168
169        let value = match &args[0] {
170            DataValue::Integer(n) => *n as f64,
171            DataValue::Float(f) => *f,
172            DataValue::Null => return Ok(DataValue::Null),
173            _ => return Err(anyhow!("ACOS requires a numeric argument")),
174        };
175
176        if value < -1.0 || value > 1.0 {
177            return Err(anyhow!(
178                "ACOS input must be between -1 and 1, got {}",
179                value
180            ));
181        }
182
183        Ok(DataValue::Float(value.acos()))
184    }
185}
186
187/// ATAN function - Arctangent (inverse tangent) returns angle in radians
188pub struct AtanFunction;
189
190impl SqlFunction for AtanFunction {
191    fn signature(&self) -> FunctionSignature {
192        FunctionSignature {
193            name: "ATAN",
194            category: FunctionCategory::Mathematical,
195            arg_count: ArgCount::Fixed(1),
196            description: "Returns the arctangent (inverse tangent) in radians",
197            returns: "FLOAT",
198            examples: vec![
199                "SELECT ATAN(0)",          // Returns 0
200                "SELECT ATAN(1)",          // Returns PI/4
201                "SELECT DEGREES(ATAN(1))", // Returns 45
202            ],
203        }
204    }
205
206    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
207        self.validate_args(args)?;
208
209        let value = match &args[0] {
210            DataValue::Integer(n) => *n as f64,
211            DataValue::Float(f) => *f,
212            DataValue::Null => return Ok(DataValue::Null),
213            _ => return Err(anyhow!("ATAN requires a numeric argument")),
214        };
215
216        Ok(DataValue::Float(value.atan()))
217    }
218}
219
220/// ATAN2 function - Two-argument arctangent returns angle in radians
221pub struct Atan2Function;
222
223impl SqlFunction for Atan2Function {
224    fn signature(&self) -> FunctionSignature {
225        FunctionSignature {
226            name: "ATAN2",
227            category: FunctionCategory::Mathematical,
228            arg_count: ArgCount::Fixed(2),
229            description:
230                "Returns the arctangent of y/x in radians, using signs to determine quadrant",
231            returns: "FLOAT",
232            examples: vec![
233                "SELECT ATAN2(1, 1)",            // Returns PI/4
234                "SELECT ATAN2(1, 0)",            // Returns PI/2
235                "SELECT DEGREES(ATAN2(-1, -1))", // Returns -135
236            ],
237        }
238    }
239
240    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
241        self.validate_args(args)?;
242
243        let y = match &args[0] {
244            DataValue::Integer(n) => *n as f64,
245            DataValue::Float(f) => *f,
246            DataValue::Null => return Ok(DataValue::Null),
247            _ => return Err(anyhow!("ATAN2 first argument must be numeric")),
248        };
249
250        let x = match &args[1] {
251            DataValue::Integer(n) => *n as f64,
252            DataValue::Float(f) => *f,
253            DataValue::Null => return Ok(DataValue::Null),
254            _ => return Err(anyhow!("ATAN2 second argument must be numeric")),
255        };
256
257        Ok(DataValue::Float(y.atan2(x)))
258    }
259}
260
261/// COT function - Cotangent of angle in radians
262pub struct CotFunction;
263
264impl SqlFunction for CotFunction {
265    fn signature(&self) -> FunctionSignature {
266        FunctionSignature {
267            name: "COT",
268            category: FunctionCategory::Mathematical,
269            arg_count: ArgCount::Fixed(1),
270            description: "Returns the cotangent of an angle in radians (1/tan)",
271            returns: "FLOAT",
272            examples: vec![
273                "SELECT COT(PI()/4)",      // Returns 1
274                "SELECT COT(PI()/2)",      // Returns ~0
275                "SELECT COT(RADIANS(45))", // Returns 1
276            ],
277        }
278    }
279
280    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
281        self.validate_args(args)?;
282
283        let radians = match &args[0] {
284            DataValue::Integer(n) => *n as f64,
285            DataValue::Float(f) => *f,
286            DataValue::Null => return Ok(DataValue::Null),
287            _ => return Err(anyhow!("COT requires a numeric argument")),
288        };
289
290        let tan_val = radians.tan();
291        if tan_val == 0.0 {
292            return Err(anyhow!("COT undefined when tangent is zero"));
293        }
294
295        Ok(DataValue::Float(1.0 / tan_val))
296    }
297}
298
299/// SINH function - Hyperbolic sine
300pub struct SinhFunction;
301
302impl SqlFunction for SinhFunction {
303    fn signature(&self) -> FunctionSignature {
304        FunctionSignature {
305            name: "SINH",
306            category: FunctionCategory::Mathematical,
307            arg_count: ArgCount::Fixed(1),
308            description: "Returns the hyperbolic sine of a number",
309            returns: "FLOAT",
310            examples: vec![
311                "SELECT SINH(0)",  // Returns 0
312                "SELECT SINH(1)",  // Returns ~1.175
313                "SELECT SINH(-1)", // Returns ~-1.175
314            ],
315        }
316    }
317
318    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
319        self.validate_args(args)?;
320
321        let value = match &args[0] {
322            DataValue::Integer(n) => *n as f64,
323            DataValue::Float(f) => *f,
324            DataValue::Null => return Ok(DataValue::Null),
325            _ => return Err(anyhow!("SINH requires a numeric argument")),
326        };
327
328        Ok(DataValue::Float(value.sinh()))
329    }
330}
331
332/// COSH function - Hyperbolic cosine
333pub struct CoshFunction;
334
335impl SqlFunction for CoshFunction {
336    fn signature(&self) -> FunctionSignature {
337        FunctionSignature {
338            name: "COSH",
339            category: FunctionCategory::Mathematical,
340            arg_count: ArgCount::Fixed(1),
341            description: "Returns the hyperbolic cosine of a number",
342            returns: "FLOAT",
343            examples: vec![
344                "SELECT COSH(0)",  // Returns 1
345                "SELECT COSH(1)",  // Returns ~1.543
346                "SELECT COSH(-1)", // Returns ~1.543
347            ],
348        }
349    }
350
351    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
352        self.validate_args(args)?;
353
354        let value = match &args[0] {
355            DataValue::Integer(n) => *n as f64,
356            DataValue::Float(f) => *f,
357            DataValue::Null => return Ok(DataValue::Null),
358            _ => return Err(anyhow!("COSH requires a numeric argument")),
359        };
360
361        Ok(DataValue::Float(value.cosh()))
362    }
363}
364
365/// TANH function - Hyperbolic tangent
366pub struct TanhFunction;
367
368impl SqlFunction for TanhFunction {
369    fn signature(&self) -> FunctionSignature {
370        FunctionSignature {
371            name: "TANH",
372            category: FunctionCategory::Mathematical,
373            arg_count: ArgCount::Fixed(1),
374            description: "Returns the hyperbolic tangent of a number",
375            returns: "FLOAT",
376            examples: vec![
377                "SELECT TANH(0)",  // Returns 0
378                "SELECT TANH(1)",  // Returns ~0.762
379                "SELECT TANH(-1)", // Returns ~-0.762
380            ],
381        }
382    }
383
384    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
385        self.validate_args(args)?;
386
387        let value = match &args[0] {
388            DataValue::Integer(n) => *n as f64,
389            DataValue::Float(f) => *f,
390            DataValue::Null => return Ok(DataValue::Null),
391            _ => return Err(anyhow!("TANH requires a numeric argument")),
392        };
393
394        Ok(DataValue::Float(value.tanh()))
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401    use std::f64::consts::PI;
402
403    #[test]
404    fn test_sin() {
405        let func = SinFunction;
406
407        // sin(0) = 0
408        let result = func.evaluate(&[DataValue::Integer(0)]).unwrap();
409        assert_eq!(result, DataValue::Float(0.0));
410
411        // sin(π/2) = 1
412        let result = func.evaluate(&[DataValue::Float(PI / 2.0)]).unwrap();
413        if let DataValue::Float(val) = result {
414            assert!((val - 1.0).abs() < 1e-10);
415        } else {
416            panic!("Expected Float result");
417        }
418
419        // sin(π) = 0
420        let result = func.evaluate(&[DataValue::Float(PI)]).unwrap();
421        if let DataValue::Float(val) = result {
422            assert!(val.abs() < 1e-10);
423        } else {
424            panic!("Expected Float result");
425        }
426    }
427
428    #[test]
429    fn test_cos() {
430        let func = CosFunction;
431
432        // cos(0) = 1
433        let result = func.evaluate(&[DataValue::Integer(0)]).unwrap();
434        assert_eq!(result, DataValue::Float(1.0));
435
436        // cos(π/2) = 0
437        let result = func.evaluate(&[DataValue::Float(PI / 2.0)]).unwrap();
438        if let DataValue::Float(val) = result {
439            assert!(val.abs() < 1e-10);
440        } else {
441            panic!("Expected Float result");
442        }
443
444        // cos(π) = -1
445        let result = func.evaluate(&[DataValue::Float(PI)]).unwrap();
446        if let DataValue::Float(val) = result {
447            assert!((val + 1.0).abs() < 1e-10);
448        } else {
449            panic!("Expected Float result");
450        }
451    }
452
453    #[test]
454    fn test_tan() {
455        let func = TanFunction;
456
457        // tan(0) = 0
458        let result = func.evaluate(&[DataValue::Integer(0)]).unwrap();
459        assert_eq!(result, DataValue::Float(0.0));
460
461        // tan(π/4) = 1
462        let result = func.evaluate(&[DataValue::Float(PI / 4.0)]).unwrap();
463        if let DataValue::Float(val) = result {
464            assert!((val - 1.0).abs() < 1e-10);
465        } else {
466            panic!("Expected Float result");
467        }
468    }
469
470    #[test]
471    fn test_asin() {
472        let func = AsinFunction;
473
474        // asin(0) = 0
475        let result = func.evaluate(&[DataValue::Integer(0)]).unwrap();
476        assert_eq!(result, DataValue::Float(0.0));
477
478        // asin(1) = π/2
479        let result = func.evaluate(&[DataValue::Integer(1)]).unwrap();
480        if let DataValue::Float(val) = result {
481            assert!((val - PI / 2.0).abs() < 1e-10);
482        } else {
483            panic!("Expected Float result");
484        }
485
486        // asin(value > 1) should error
487        assert!(func.evaluate(&[DataValue::Float(1.5)]).is_err());
488    }
489
490    #[test]
491    fn test_atan2() {
492        let func = Atan2Function;
493
494        // atan2(1, 1) = π/4
495        let result = func
496            .evaluate(&[DataValue::Integer(1), DataValue::Integer(1)])
497            .unwrap();
498        if let DataValue::Float(val) = result {
499            assert!((val - PI / 4.0).abs() < 1e-10);
500        } else {
501            panic!("Expected Float result");
502        }
503
504        // atan2(1, 0) = π/2
505        let result = func
506            .evaluate(&[DataValue::Integer(1), DataValue::Integer(0)])
507            .unwrap();
508        if let DataValue::Float(val) = result {
509            assert!((val - PI / 2.0).abs() < 1e-10);
510        } else {
511            panic!("Expected Float result");
512        }
513    }
514}