datafusion_functions/math/
monotonicity.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
18use std::sync::LazyLock;
19
20use datafusion_common::{exec_err, Result, ScalarValue};
21use datafusion_doc::scalar_doc_sections::DOC_SECTION_MATH;
22use datafusion_expr::interval_arithmetic::Interval;
23use datafusion_expr::sort_properties::{ExprProperties, SortProperties};
24use datafusion_expr::Documentation;
25
26/// Non-increasing on the interval \[−1, 1\], undefined otherwise.
27pub fn acos_order(input: &[ExprProperties]) -> Result<SortProperties> {
28    let arg = &input[0];
29    let range = &arg.range;
30
31    let valid_domain =
32        Interval::make_symmetric_unit_interval(&range.lower().data_type())?;
33
34    if valid_domain.contains(range)? == Interval::CERTAINLY_TRUE {
35        Ok(-arg.sort_properties)
36    } else {
37        exec_err!("Input range of ACOS contains out-of-domain values")
38    }
39}
40
41static DOCUMENTATION_ACOS: LazyLock<Documentation> = LazyLock::new(|| {
42    Documentation::builder(
43        DOC_SECTION_MATH,
44        "Returns the arc cosine or inverse cosine of a number.",
45        "acos(numeric_expression)",
46    )
47    .with_standard_argument("numeric_expression", Some("Numeric"))
48    .with_sql_example(
49        r#"```sql
50> SELECT acos(1);
51+----------+
52| acos(1)  |
53+----------+
54| 0.0      |
55+----------+
56```"#,
57    )
58    .build()
59});
60
61pub fn get_acos_doc() -> &'static Documentation {
62    &DOCUMENTATION_ACOS
63}
64
65/// Non-decreasing for x ≥ 1, undefined otherwise.
66pub fn acosh_order(input: &[ExprProperties]) -> Result<SortProperties> {
67    let arg = &input[0];
68    let range = &arg.range;
69
70    let valid_domain = Interval::try_new(
71        ScalarValue::new_one(&range.lower().data_type())?,
72        ScalarValue::try_from(&range.upper().data_type())?,
73    )?;
74
75    if valid_domain.contains(range)? == Interval::CERTAINLY_TRUE {
76        Ok(arg.sort_properties)
77    } else {
78        exec_err!("Input range of ACOSH contains out-of-domain values")
79    }
80}
81
82static DOCUMENTATION_ACOSH: LazyLock<Documentation> =
83    LazyLock::new(|| {
84        Documentation::builder(
85        DOC_SECTION_MATH,
86        "Returns the area hyperbolic cosine or inverse hyperbolic cosine of a number.",
87        "acosh(numeric_expression)",
88    )
89    .with_standard_argument("numeric_expression", Some("Numeric"))
90    .with_sql_example(r#"```sql
91> SELECT acosh(2);
92+------------+
93| acosh(2)   |
94+------------+
95| 1.31696    |
96+------------+
97```"#)
98    .build()
99    });
100
101pub fn get_acosh_doc() -> &'static Documentation {
102    &DOCUMENTATION_ACOSH
103}
104
105/// Non-decreasing on the interval \[−1, 1\], undefined otherwise.
106pub fn asin_order(input: &[ExprProperties]) -> Result<SortProperties> {
107    let arg = &input[0];
108    let range = &arg.range;
109
110    let valid_domain =
111        Interval::make_symmetric_unit_interval(&range.lower().data_type())?;
112
113    if valid_domain.contains(range)? == Interval::CERTAINLY_TRUE {
114        Ok(arg.sort_properties)
115    } else {
116        exec_err!("Input range of ASIN contains out-of-domain values")
117    }
118}
119
120static DOCUMENTATION_ASIN: LazyLock<Documentation> = LazyLock::new(|| {
121    Documentation::builder(
122        DOC_SECTION_MATH,
123        "Returns the arc sine or inverse sine of a number.",
124        "asin(numeric_expression)",
125    )
126    .with_standard_argument("numeric_expression", Some("Numeric"))
127    .with_sql_example(
128        r#"```sql
129> SELECT asin(0.5);
130+------------+
131| asin(0.5)  |
132+------------+
133| 0.5235988  |
134+------------+
135```"#,
136    )
137    .build()
138});
139
140pub fn get_asin_doc() -> &'static Documentation {
141    &DOCUMENTATION_ASIN
142}
143
144/// Non-decreasing for all real numbers.
145pub fn asinh_order(input: &[ExprProperties]) -> Result<SortProperties> {
146    Ok(input[0].sort_properties)
147}
148
149static DOCUMENTATION_ASINH: LazyLock<Documentation> = LazyLock::new(|| {
150    Documentation::builder(
151        DOC_SECTION_MATH,
152        "Returns the area hyperbolic sine or inverse hyperbolic sine of a number.",
153        "asinh(numeric_expression)",
154    )
155    .with_standard_argument("numeric_expression", Some("Numeric"))
156    .with_sql_example(
157        r#" ```sql 
158> SELECT asinh(1);
159+------------+
160| asinh(1)   |
161+------------+
162| 0.8813736  |
163+------------+
164```"#,
165    )
166    .build()
167});
168
169pub fn get_asinh_doc() -> &'static Documentation {
170    &DOCUMENTATION_ASINH
171}
172
173/// Non-decreasing for all real numbers.
174pub fn atan_order(input: &[ExprProperties]) -> Result<SortProperties> {
175    Ok(input[0].sort_properties)
176}
177
178static DOCUMENTATION_ATAN: LazyLock<Documentation> = LazyLock::new(|| {
179    Documentation::builder(
180        DOC_SECTION_MATH,
181        "Returns the arc tangent or inverse tangent of a number.",
182        "atan(numeric_expression)",
183    )
184    .with_standard_argument("numeric_expression", Some("Numeric"))
185    .with_sql_example(
186        r#"```sql
187    > SELECT atan(1);
188+-----------+
189| atan(1)   |
190+-----------+
191| 0.7853982 |
192+-----------+
193```"#,
194    )
195    .build()
196});
197
198pub fn get_atan_doc() -> &'static Documentation {
199    &DOCUMENTATION_ATAN
200}
201
202/// Non-decreasing on the interval \[−1, 1\], undefined otherwise.
203pub fn atanh_order(input: &[ExprProperties]) -> Result<SortProperties> {
204    let arg = &input[0];
205    let range = &arg.range;
206
207    let valid_domain =
208        Interval::make_symmetric_unit_interval(&range.lower().data_type())?;
209
210    if valid_domain.contains(range)? == Interval::CERTAINLY_TRUE {
211        Ok(arg.sort_properties)
212    } else {
213        exec_err!("Input range of ATANH contains out-of-domain values")
214    }
215}
216
217static DOCUMENTATION_ATANH: LazyLock<Documentation> =
218    LazyLock::new(|| {
219        Documentation::builder(
220        DOC_SECTION_MATH,
221        "Returns the area hyperbolic tangent or inverse hyperbolic tangent of a number.",
222        "atanh(numeric_expression)",
223    )
224    .with_standard_argument("numeric_expression", Some("Numeric"))
225    .with_sql_example(r#"```sql
226    > SELECT atanh(0.5);
227+-------------+
228| atanh(0.5)  |
229+-------------+
230| 0.5493061   |
231+-------------+
232```"#)
233    .build()
234    });
235
236pub fn get_atanh_doc() -> &'static Documentation {
237    &DOCUMENTATION_ATANH
238}
239
240/// Order depends on the quadrant.
241// TODO: Implement ordering rule of the ATAN2 function.
242pub fn atan2_order(_input: &[ExprProperties]) -> Result<SortProperties> {
243    Ok(SortProperties::Unordered)
244}
245
246static DOCUMENTATION_ATANH2: LazyLock<Documentation> =
247    LazyLock::new(|| {
248        Documentation::builder(
249        DOC_SECTION_MATH,
250        "Returns the arc tangent or inverse tangent of `expression_y / expression_x`.",
251        "atan2(expression_y, expression_x)",
252    )
253    .with_argument(
254        "expression_y",
255        r#"First numeric expression to operate on.
256Can be a constant, column, or function, and any combination of arithmetic operators."#,
257    )
258    .with_argument(
259        "expression_x",
260        r#"Second numeric expression to operate on.
261Can be a constant, column, or function, and any combination of arithmetic operators."#,
262    )
263    .with_sql_example(r#"```sql
264> SELECT atan2(1, 1);
265+------------+
266| atan2(1,1) |
267+------------+
268| 0.7853982  |
269+------------+
270```"#)
271    .build()
272    });
273
274pub fn get_atan2_doc() -> &'static Documentation {
275    &DOCUMENTATION_ATANH2
276}
277
278/// Non-decreasing for all real numbers.
279pub fn cbrt_order(input: &[ExprProperties]) -> Result<SortProperties> {
280    Ok(input[0].sort_properties)
281}
282
283static DOCUMENTATION_CBRT: LazyLock<Documentation> = LazyLock::new(|| {
284    Documentation::builder(
285        DOC_SECTION_MATH,
286        "Returns the cube root of a number.",
287        "cbrt(numeric_expression)",
288    )
289    .with_standard_argument("numeric_expression", Some("Numeric"))
290    .with_sql_example(
291        r#"```sql
292> SELECT cbrt(27);
293+-----------+
294| cbrt(27)  |
295+-----------+
296| 3.0       |
297+-----------+
298```"#,
299    )
300    .build()
301});
302
303pub fn get_cbrt_doc() -> &'static Documentation {
304    &DOCUMENTATION_CBRT
305}
306
307/// Non-decreasing for all real numbers.
308pub fn ceil_order(input: &[ExprProperties]) -> Result<SortProperties> {
309    Ok(input[0].sort_properties)
310}
311
312static DOCUMENTATION_CEIL: LazyLock<Documentation> = LazyLock::new(|| {
313    Documentation::builder(
314        DOC_SECTION_MATH,
315        "Returns the nearest integer greater than or equal to a number.",
316        "ceil(numeric_expression)",
317    )
318    .with_standard_argument("numeric_expression", Some("Numeric"))
319    .with_sql_example(
320        r#"```sql
321    > SELECT ceil(3.14);
322+------------+
323| ceil(3.14) |
324+------------+
325| 4.0        |
326+------------+
327```"#,
328    )
329    .build()
330});
331
332pub fn get_ceil_doc() -> &'static Documentation {
333    &DOCUMENTATION_CEIL
334}
335
336/// Non-increasing on \[0, π\] and then non-decreasing on \[π, 2π\].
337/// This pattern repeats periodically with a period of 2π.
338// TODO: Implement ordering rule of the ATAN2 function.
339pub fn cos_order(_input: &[ExprProperties]) -> Result<SortProperties> {
340    Ok(SortProperties::Unordered)
341}
342
343static DOCUMENTATION_COS: LazyLock<Documentation> = LazyLock::new(|| {
344    Documentation::builder(
345        DOC_SECTION_MATH,
346        "Returns the cosine of a number.",
347        "cos(numeric_expression)",
348    )
349    .with_standard_argument("numeric_expression", Some("Numeric"))
350    .with_sql_example(
351        r#"```sql
352> SELECT cos(0);
353+--------+
354| cos(0) |
355+--------+
356| 1.0    |
357+--------+
358```"#,
359    )
360    .build()
361});
362
363pub fn get_cos_doc() -> &'static Documentation {
364    &DOCUMENTATION_COS
365}
366
367/// Non-decreasing for x ≥ 0 and symmetrically non-increasing for x ≤ 0.
368pub fn cosh_order(input: &[ExprProperties]) -> Result<SortProperties> {
369    let arg = &input[0];
370    let range = &arg.range;
371
372    let zero_point = Interval::make_zero(&range.lower().data_type())?;
373
374    if range.gt_eq(&zero_point)? == Interval::CERTAINLY_TRUE {
375        Ok(arg.sort_properties)
376    } else if range.lt_eq(&zero_point)? == Interval::CERTAINLY_TRUE {
377        Ok(-arg.sort_properties)
378    } else {
379        Ok(SortProperties::Unordered)
380    }
381}
382
383static DOCUMENTATION_COSH: LazyLock<Documentation> = LazyLock::new(|| {
384    Documentation::builder(
385        DOC_SECTION_MATH,
386        "Returns the hyperbolic cosine of a number.",
387        "cosh(numeric_expression)",
388    )
389    .with_standard_argument("numeric_expression", Some("Numeric"))
390    .with_sql_example(
391        r#"```sql
392> SELECT cosh(1);
393+-----------+
394| cosh(1)   |
395+-----------+
396| 1.5430806 |
397+-----------+
398```"#,
399    )
400    .build()
401});
402
403pub fn get_cosh_doc() -> &'static Documentation {
404    &DOCUMENTATION_COSH
405}
406
407/// Non-decreasing function that converts radians to degrees.
408pub fn degrees_order(input: &[ExprProperties]) -> Result<SortProperties> {
409    Ok(input[0].sort_properties)
410}
411
412static DOCUMENTATION_DEGREES: LazyLock<Documentation> = LazyLock::new(|| {
413    Documentation::builder(
414        DOC_SECTION_MATH,
415        "Converts radians to degrees.",
416        "degrees(numeric_expression)",
417    )
418    .with_standard_argument("numeric_expression", Some("Numeric"))
419    .with_sql_example(
420        r#"```sql
421    > SELECT degrees(pi());
422+------------+
423| degrees(0) |
424+------------+
425| 180.0      |
426+------------+
427```"#,
428    )
429    .build()
430});
431
432pub fn get_degrees_doc() -> &'static Documentation {
433    &DOCUMENTATION_DEGREES
434}
435
436/// Non-decreasing for all real numbers.
437pub fn exp_order(input: &[ExprProperties]) -> Result<SortProperties> {
438    Ok(input[0].sort_properties)
439}
440
441static DOCUMENTATION_EXP: LazyLock<Documentation> = LazyLock::new(|| {
442    Documentation::builder(
443        DOC_SECTION_MATH,
444        "Returns the base-e exponential of a number.",
445        "exp(numeric_expression)",
446    )
447    .with_standard_argument("numeric_expression", Some("Numeric"))
448    .with_sql_example(
449        r#"```sql
450> SELECT exp(1);
451+---------+
452| exp(1)  |
453+---------+
454| 2.71828 |
455+---------+
456```"#,
457    )
458    .build()
459});
460
461pub fn get_exp_doc() -> &'static Documentation {
462    &DOCUMENTATION_EXP
463}
464
465/// Non-decreasing for all real numbers.
466pub fn floor_order(input: &[ExprProperties]) -> Result<SortProperties> {
467    Ok(input[0].sort_properties)
468}
469
470static DOCUMENTATION_FLOOR: LazyLock<Documentation> = LazyLock::new(|| {
471    Documentation::builder(
472        DOC_SECTION_MATH,
473        "Returns the nearest integer less than or equal to a number.",
474        "floor(numeric_expression)",
475    )
476    .with_standard_argument("numeric_expression", Some("Numeric"))
477    .with_sql_example(
478        r#"```sql
479> SELECT floor(3.14);
480+-------------+
481| floor(3.14) |
482+-------------+
483| 3.0         |
484+-------------+
485```"#,
486    )
487    .build()
488});
489
490pub fn get_floor_doc() -> &'static Documentation {
491    &DOCUMENTATION_FLOOR
492}
493
494/// Non-decreasing for x ≥ 0, undefined otherwise.
495pub fn ln_order(input: &[ExprProperties]) -> Result<SortProperties> {
496    let arg = &input[0];
497    let range = &arg.range;
498
499    let zero_point = Interval::make_zero(&range.lower().data_type())?;
500
501    if range.gt_eq(&zero_point)? == Interval::CERTAINLY_TRUE {
502        Ok(arg.sort_properties)
503    } else {
504        exec_err!("Input range of LN contains out-of-domain values")
505    }
506}
507
508static DOCUMENTATION_LN: LazyLock<Documentation> = LazyLock::new(|| {
509    Documentation::builder(
510        DOC_SECTION_MATH,
511        "Returns the natural logarithm of a number.",
512        "ln(numeric_expression)",
513    )
514    .with_standard_argument("numeric_expression", Some("Numeric"))
515    .with_sql_example(
516        r#"```sql
517> SELECT ln(2.71828);
518+-------------+
519| ln(2.71828) |
520+-------------+
521| 1.0         |
522+-------------+
523```"#,
524    )
525    .build()
526});
527
528pub fn get_ln_doc() -> &'static Documentation {
529    &DOCUMENTATION_LN
530}
531
532/// Non-decreasing for x ≥ 0, undefined otherwise.
533pub fn log2_order(input: &[ExprProperties]) -> Result<SortProperties> {
534    let arg = &input[0];
535    let range = &arg.range;
536
537    let zero_point = Interval::make_zero(&range.lower().data_type())?;
538
539    if range.gt_eq(&zero_point)? == Interval::CERTAINLY_TRUE {
540        Ok(arg.sort_properties)
541    } else {
542        exec_err!("Input range of LOG2 contains out-of-domain values")
543    }
544}
545
546static DOCUMENTATION_LOG2: LazyLock<Documentation> = LazyLock::new(|| {
547    Documentation::builder(
548        DOC_SECTION_MATH,
549        "Returns the base-2 logarithm of a number.",
550        "log2(numeric_expression)",
551    )
552    .with_standard_argument("numeric_expression", Some("Numeric"))
553    .with_sql_example(
554        r#"```sql
555> SELECT log2(8);
556+-----------+
557| log2(8)   |
558+-----------+
559| 3.0       |
560+-----------+
561```"#,
562    )
563    .build()
564});
565
566pub fn get_log2_doc() -> &'static Documentation {
567    &DOCUMENTATION_LOG2
568}
569
570/// Non-decreasing for x ≥ 0, undefined otherwise.
571pub fn log10_order(input: &[ExprProperties]) -> Result<SortProperties> {
572    let arg = &input[0];
573    let range = &arg.range;
574
575    let zero_point = Interval::make_zero(&range.lower().data_type())?;
576
577    if range.gt_eq(&zero_point)? == Interval::CERTAINLY_TRUE {
578        Ok(arg.sort_properties)
579    } else {
580        exec_err!("Input range of LOG10 contains out-of-domain values")
581    }
582}
583
584static DOCUMENTATION_LOG10: LazyLock<Documentation> = LazyLock::new(|| {
585    Documentation::builder(
586        DOC_SECTION_MATH,
587        "Returns the base-10 logarithm of a number.",
588        "log10(numeric_expression)",
589    )
590    .with_standard_argument("numeric_expression", Some("Numeric"))
591    .with_sql_example(
592        r#"```sql
593> SELECT log10(100);
594+-------------+
595| log10(100)  |
596+-------------+
597| 2.0         |
598+-------------+
599```"#,
600    )
601    .build()
602});
603
604pub fn get_log10_doc() -> &'static Documentation {
605    &DOCUMENTATION_LOG10
606}
607
608/// Non-decreasing for all real numbers x.
609pub fn radians_order(input: &[ExprProperties]) -> Result<SortProperties> {
610    Ok(input[0].sort_properties)
611}
612
613static DOCUMENTATION_RADIANS: LazyLock<Documentation> = LazyLock::new(|| {
614    Documentation::builder(
615        DOC_SECTION_MATH,
616        "Converts degrees to radians.",
617        "radians(numeric_expression)",
618    )
619    .with_standard_argument("numeric_expression", Some("Numeric"))
620    .with_sql_example(
621        r#"```sql
622> SELECT radians(180);
623+----------------+
624| radians(180)   |
625+----------------+
626| 3.14159265359  |
627+----------------+
628```"#,
629    )
630    .build()
631});
632
633pub fn get_radians_doc() -> &'static Documentation {
634    &DOCUMENTATION_RADIANS
635}
636
637/// Non-decreasing on \[0, π\] and then non-increasing on \[π, 2π\].
638/// This pattern repeats periodically with a period of 2π.
639// TODO: Implement ordering rule of the SIN function.
640pub fn sin_order(_input: &[ExprProperties]) -> Result<SortProperties> {
641    Ok(SortProperties::Unordered)
642}
643
644static DOCUMENTATION_SIN: LazyLock<Documentation> = LazyLock::new(|| {
645    Documentation::builder(
646        DOC_SECTION_MATH,
647        "Returns the sine of a number.",
648        "sin(numeric_expression)",
649    )
650    .with_standard_argument("numeric_expression", Some("Numeric"))
651    .with_sql_example(
652        r#"```sql
653> SELECT sin(0);
654+----------+
655| sin(0)   |
656+----------+
657| 0.0      |
658+----------+
659```"#,
660    )
661    .build()
662});
663
664pub fn get_sin_doc() -> &'static Documentation {
665    &DOCUMENTATION_SIN
666}
667
668/// Non-decreasing for all real numbers.
669pub fn sinh_order(input: &[ExprProperties]) -> Result<SortProperties> {
670    Ok(input[0].sort_properties)
671}
672
673static DOCUMENTATION_SINH: LazyLock<Documentation> = LazyLock::new(|| {
674    Documentation::builder(
675        DOC_SECTION_MATH,
676        "Returns the hyperbolic sine of a number.",
677        "sinh(numeric_expression)",
678    )
679    .with_standard_argument("numeric_expression", Some("Numeric"))
680    .with_sql_example(
681        r#"```sql
682> SELECT sinh(1);
683+-----------+
684| sinh(1)   |
685+-----------+
686| 1.1752012 |
687+-----------+
688```"#,
689    )
690    .build()
691});
692
693pub fn get_sinh_doc() -> &'static Documentation {
694    &DOCUMENTATION_SINH
695}
696
697/// Non-decreasing for x ≥ 0, undefined otherwise.
698pub fn sqrt_order(input: &[ExprProperties]) -> Result<SortProperties> {
699    let arg = &input[0];
700    let range = &arg.range;
701
702    let zero_point = Interval::make_zero(&range.lower().data_type())?;
703
704    if range.gt_eq(&zero_point)? == Interval::CERTAINLY_TRUE {
705        Ok(arg.sort_properties)
706    } else {
707        exec_err!("Input range of SQRT contains out-of-domain values")
708    }
709}
710
711static DOCUMENTATION_SQRT: LazyLock<Documentation> = LazyLock::new(|| {
712    Documentation::builder(
713        DOC_SECTION_MATH,
714        "Returns the square root of a number.",
715        "sqrt(numeric_expression)",
716    )
717    .with_standard_argument("numeric_expression", Some("Numeric"))
718    .build()
719});
720
721pub fn get_sqrt_doc() -> &'static Documentation {
722    &DOCUMENTATION_SQRT
723}
724
725/// Non-decreasing between vertical asymptotes at x = k * π ± π / 2 for any
726/// integer k.
727// TODO: Implement ordering rule of the TAN function.
728pub fn tan_order(_input: &[ExprProperties]) -> Result<SortProperties> {
729    Ok(SortProperties::Unordered)
730}
731
732static DOCUMENTATION_TAN: LazyLock<Documentation> = LazyLock::new(|| {
733    Documentation::builder(
734        DOC_SECTION_MATH,
735        "Returns the tangent of a number.",
736        "tan(numeric_expression)",
737    )
738    .with_standard_argument("numeric_expression", Some("Numeric"))
739    .with_sql_example(
740        r#"```sql
741> SELECT tan(pi()/4);
742+--------------+
743| tan(PI()/4)  |
744+--------------+
745| 1.0          |
746+--------------+
747```"#,
748    )
749    .build()
750});
751
752pub fn get_tan_doc() -> &'static Documentation {
753    &DOCUMENTATION_TAN
754}
755
756/// Non-decreasing for all real numbers.
757pub fn tanh_order(input: &[ExprProperties]) -> Result<SortProperties> {
758    Ok(input[0].sort_properties)
759}
760
761static DOCUMENTATION_TANH: LazyLock<Documentation> = LazyLock::new(|| {
762    Documentation::builder(
763        DOC_SECTION_MATH,
764        "Returns the hyperbolic tangent of a number.",
765        "tanh(numeric_expression)",
766    )
767    .with_standard_argument("numeric_expression", Some("Numeric"))
768    .with_sql_example(
769        r#"```sql
770  > SELECT tanh(20);
771  +----------+
772  | tanh(20) |
773  +----------+
774  | 1.0      |
775  +----------+
776  ```"#,
777    )
778    .build()
779});
780
781pub fn get_tanh_doc() -> &'static Documentation {
782    &DOCUMENTATION_TANH
783}
784
785#[cfg(test)]
786mod tests {
787    use arrow::compute::SortOptions;
788    use datafusion_common::Result;
789
790    use super::*;
791
792    #[derive(Debug)]
793    struct MonotonicityTestCase {
794        name: &'static str,
795        func: fn(&[ExprProperties]) -> Result<SortProperties>,
796        lower: f64,
797        upper: f64,
798        input_sort: SortProperties,
799        expected: Result<SortProperties>,
800    }
801
802    #[test]
803    fn test_monotonicity_table() {
804        fn create_ep(lower: f64, upper: f64, sp: SortProperties) -> ExprProperties {
805            ExprProperties {
806                range: Interval::try_new(
807                    ScalarValue::from(lower),
808                    ScalarValue::from(upper),
809                )
810                .unwrap(),
811                sort_properties: sp,
812                preserves_lex_ordering: false,
813            }
814        }
815
816        let test_cases = vec![
817            MonotonicityTestCase {
818                name: "acos_order within domain",
819                func: acos_order,
820                lower: -0.5,
821                upper: 0.5,
822                input_sort: SortProperties::Ordered(SortOptions {
823                    descending: false,
824                    nulls_first: false,
825                }),
826                expected: Ok(SortProperties::Ordered(SortOptions {
827                    descending: true,
828                    nulls_first: false,
829                })),
830            },
831            MonotonicityTestCase {
832                name: "acos_order out of domain",
833                func: acos_order,
834                lower: -2.0,
835                upper: 1.0,
836                input_sort: SortProperties::Ordered(SortOptions {
837                    descending: false,
838                    nulls_first: false,
839                }),
840                expected: exec_err!("Input range of ACOS contains out-of-domain values"),
841            },
842            MonotonicityTestCase {
843                name: "acosh_order within domain",
844                func: acosh_order,
845                lower: 2.0,
846                upper: 100.0,
847                input_sort: SortProperties::Ordered(SortOptions {
848                    descending: false,
849                    nulls_first: true,
850                }),
851                expected: Ok(SortProperties::Ordered(SortOptions {
852                    descending: false,
853                    nulls_first: true,
854                })),
855            },
856            MonotonicityTestCase {
857                name: "acosh_order out of domain",
858                func: acosh_order,
859                lower: 0.5,
860                upper: 1.0,
861                input_sort: SortProperties::Ordered(SortOptions {
862                    descending: true,
863                    nulls_first: false,
864                }),
865                expected: exec_err!("Input range of ACOSH contains out-of-domain values"),
866            },
867            MonotonicityTestCase {
868                name: "asin_order within domain",
869                func: asin_order,
870                lower: -0.5,
871                upper: 0.5,
872                input_sort: SortProperties::Ordered(SortOptions {
873                    descending: false,
874                    nulls_first: false,
875                }),
876                expected: Ok(SortProperties::Ordered(SortOptions {
877                    descending: false,
878                    nulls_first: false,
879                })),
880            },
881            MonotonicityTestCase {
882                name: "asin_order out of domain",
883                func: asin_order,
884                lower: -2.0,
885                upper: 1.0,
886                input_sort: SortProperties::Ordered(SortOptions {
887                    descending: false,
888                    nulls_first: false,
889                }),
890                expected: exec_err!("Input range of ASIN contains out-of-domain values"),
891            },
892            MonotonicityTestCase {
893                name: "asinh_order within domain",
894                func: asinh_order,
895                lower: -1.0,
896                upper: 1.0,
897                input_sort: SortProperties::Ordered(SortOptions {
898                    descending: false,
899                    nulls_first: false,
900                }),
901                expected: Ok(SortProperties::Ordered(SortOptions {
902                    descending: false,
903                    nulls_first: false,
904                })),
905            },
906            MonotonicityTestCase {
907                name: "asinh_order out of domain",
908                func: asinh_order,
909                lower: -2.0,
910                upper: 1.0,
911                input_sort: SortProperties::Ordered(SortOptions {
912                    descending: false,
913                    nulls_first: false,
914                }),
915                expected: Ok(SortProperties::Ordered(SortOptions {
916                    descending: false,
917                    nulls_first: false,
918                })),
919            },
920            MonotonicityTestCase {
921                name: "atan_order within domain",
922                func: atan_order,
923                lower: -1.0,
924                upper: 1.0,
925                input_sort: SortProperties::Ordered(SortOptions {
926                    descending: false,
927                    nulls_first: false,
928                }),
929                expected: Ok(SortProperties::Ordered(SortOptions {
930                    descending: false,
931                    nulls_first: false,
932                })),
933            },
934            MonotonicityTestCase {
935                name: "atan_order out of domain",
936                func: atan_order,
937                lower: -2.0,
938                upper: 1.0,
939                input_sort: SortProperties::Ordered(SortOptions {
940                    descending: false,
941                    nulls_first: false,
942                }),
943                expected: Ok(SortProperties::Ordered(SortOptions {
944                    descending: false,
945                    nulls_first: false,
946                })),
947            },
948            MonotonicityTestCase {
949                name: "atanh_order within domain",
950                func: atanh_order,
951                lower: -0.6,
952                upper: 0.6,
953                input_sort: SortProperties::Ordered(SortOptions {
954                    descending: false,
955                    nulls_first: false,
956                }),
957                expected: Ok(SortProperties::Ordered(SortOptions {
958                    descending: false,
959                    nulls_first: false,
960                })),
961            },
962            MonotonicityTestCase {
963                name: "atanh_order out of domain",
964                func: atanh_order,
965                lower: -2.0,
966                upper: 1.0,
967                input_sort: SortProperties::Ordered(SortOptions {
968                    descending: false,
969                    nulls_first: false,
970                }),
971                expected: exec_err!("Input range of ATANH contains out-of-domain values"),
972            },
973            MonotonicityTestCase {
974                name: "cbrt_order within domain",
975                func: cbrt_order,
976                lower: -1.0,
977                upper: 1.0,
978                input_sort: SortProperties::Ordered(SortOptions {
979                    descending: false,
980                    nulls_first: false,
981                }),
982                expected: Ok(SortProperties::Ordered(SortOptions {
983                    descending: false,
984                    nulls_first: false,
985                })),
986            },
987            MonotonicityTestCase {
988                name: "cbrt_order out of domain",
989                func: cbrt_order,
990                lower: -2.0,
991                upper: 1.0,
992                input_sort: SortProperties::Ordered(SortOptions {
993                    descending: false,
994                    nulls_first: false,
995                }),
996                expected: Ok(SortProperties::Ordered(SortOptions {
997                    descending: false,
998                    nulls_first: false,
999                })),
1000            },
1001            MonotonicityTestCase {
1002                name: "ceil_order within domain",
1003                func: ceil_order,
1004                lower: -1.0,
1005                upper: 1.0,
1006                input_sort: SortProperties::Ordered(SortOptions {
1007                    descending: false,
1008                    nulls_first: false,
1009                }),
1010                expected: Ok(SortProperties::Ordered(SortOptions {
1011                    descending: false,
1012                    nulls_first: false,
1013                })),
1014            },
1015            MonotonicityTestCase {
1016                name: "ceil_order out of domain",
1017                func: ceil_order,
1018                lower: -2.0,
1019                upper: 1.0,
1020                input_sort: SortProperties::Ordered(SortOptions {
1021                    descending: false,
1022                    nulls_first: false,
1023                }),
1024                expected: Ok(SortProperties::Ordered(SortOptions {
1025                    descending: false,
1026                    nulls_first: false,
1027                })),
1028            },
1029            MonotonicityTestCase {
1030                name: "cos_order within domain",
1031                func: cos_order,
1032                lower: 0.0,
1033                upper: 2.0 * std::f64::consts::PI,
1034                input_sort: SortProperties::Ordered(SortOptions {
1035                    descending: false,
1036                    nulls_first: false,
1037                }),
1038                expected: Ok(SortProperties::Unordered),
1039            },
1040            MonotonicityTestCase {
1041                name: "cos_order out of domain",
1042                func: cos_order,
1043                lower: -2.0,
1044                upper: 1.0,
1045                input_sort: SortProperties::Ordered(SortOptions {
1046                    descending: false,
1047                    nulls_first: false,
1048                }),
1049                expected: Ok(SortProperties::Unordered),
1050            },
1051            MonotonicityTestCase {
1052                name: "cosh_order within domain positive",
1053                func: cosh_order,
1054                lower: 5.0,
1055                upper: 100.0,
1056                input_sort: SortProperties::Ordered(SortOptions {
1057                    descending: false,
1058                    nulls_first: false,
1059                }),
1060                expected: Ok(SortProperties::Ordered(SortOptions {
1061                    descending: false,
1062                    nulls_first: false,
1063                })),
1064            },
1065            MonotonicityTestCase {
1066                name: "cosh_order within domain negative",
1067                func: cosh_order,
1068                lower: -100.0,
1069                upper: -5.0,
1070                input_sort: SortProperties::Ordered(SortOptions {
1071                    descending: false,
1072                    nulls_first: false,
1073                }),
1074                expected: Ok(SortProperties::Ordered(SortOptions {
1075                    descending: true,
1076                    nulls_first: false,
1077                })),
1078            },
1079            MonotonicityTestCase {
1080                name: "cosh_order out of domain so unordered",
1081                func: cosh_order,
1082                lower: -1.0,
1083                upper: 1.0,
1084                input_sort: SortProperties::Ordered(SortOptions {
1085                    descending: false,
1086                    nulls_first: false,
1087                }),
1088                expected: Ok(SortProperties::Unordered),
1089            },
1090            MonotonicityTestCase {
1091                name: "degrees_order",
1092                func: degrees_order,
1093                lower: -1.0,
1094                upper: 1.0,
1095                input_sort: SortProperties::Ordered(SortOptions {
1096                    descending: true,
1097                    nulls_first: true,
1098                }),
1099                expected: Ok(SortProperties::Ordered(SortOptions {
1100                    descending: true,
1101                    nulls_first: true,
1102                })),
1103            },
1104            MonotonicityTestCase {
1105                name: "exp_order",
1106                func: exp_order,
1107                lower: -1000.0,
1108                upper: 1000.0,
1109                input_sort: SortProperties::Ordered(SortOptions {
1110                    descending: false,
1111                    nulls_first: false,
1112                }),
1113                expected: Ok(SortProperties::Ordered(SortOptions {
1114                    descending: false,
1115                    nulls_first: false,
1116                })),
1117            },
1118            MonotonicityTestCase {
1119                name: "floor_order",
1120                func: floor_order,
1121                lower: -1.0,
1122                upper: 1.0,
1123                input_sort: SortProperties::Ordered(SortOptions {
1124                    descending: true,
1125                    nulls_first: true,
1126                }),
1127                expected: Ok(SortProperties::Ordered(SortOptions {
1128                    descending: true,
1129                    nulls_first: true,
1130                })),
1131            },
1132            MonotonicityTestCase {
1133                name: "ln_order within domain",
1134                func: ln_order,
1135                lower: 1.0,
1136                upper: 2.0,
1137                input_sort: SortProperties::Ordered(SortOptions {
1138                    descending: false,
1139                    nulls_first: false,
1140                }),
1141                expected: Ok(SortProperties::Ordered(SortOptions {
1142                    descending: false,
1143                    nulls_first: false,
1144                })),
1145            },
1146            MonotonicityTestCase {
1147                name: "ln_order out of domain",
1148                func: ln_order,
1149                lower: -5.0,
1150                upper: -4.0,
1151                input_sort: SortProperties::Ordered(SortOptions {
1152                    descending: false,
1153                    nulls_first: false,
1154                }),
1155                expected: exec_err!("Input range of LN contains out-of-domain values"),
1156            },
1157        ];
1158
1159        for tcase in test_cases {
1160            let input = vec![create_ep(tcase.lower, tcase.upper, tcase.input_sort)];
1161            let actual = (tcase.func)(&input);
1162            match (&actual, &tcase.expected) {
1163                (Ok(a), Ok(e)) => assert_eq!(
1164                    a, e,
1165                    "Test '{}' failed: got {:?}, expected {:?}",
1166                    tcase.name, a, e
1167                ),
1168                (Err(e1), Err(e2)) => {
1169                    assert_eq!(
1170                        e1.strip_backtrace().to_string(),
1171                        e2.strip_backtrace().to_string(),
1172                        "Test '{}' failed: got {:?}, expected {:?}",
1173                        tcase.name,
1174                        e1,
1175                        e2
1176                    )
1177                } // Both are errors, so it's fine
1178                _ => panic!(
1179                    "Test '{}' failed: got {:?}, expected {:?}",
1180                    tcase.name, actual, tcase.expected
1181                ),
1182            }
1183        }
1184    }
1185}