Skip to main content

datafusion_functions/math/
power.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
18//! Math function: `power()`.
19use super::log::LogFunc;
20
21use crate::utils::calculate_binary_math;
22use arrow::array::{Array, ArrayRef};
23use arrow::datatypes::{DataType, Float64Type};
24use arrow::error::ArrowError;
25use datafusion_common::types::{NativeType, logical_float64};
26use datafusion_common::utils::take_function_args;
27use datafusion_common::{Result, ScalarValue, internal_err};
28use datafusion_expr::expr::ScalarFunction;
29use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyContext};
30use datafusion_expr::{
31    Cast, Coercion, ColumnarValue, Documentation, Expr, ScalarFunctionArgs, ScalarUDF,
32    ScalarUDFImpl, Signature, TypeSignatureClass, Volatility, lit,
33};
34use datafusion_macros::user_doc;
35
36/// Matches PostgreSQL: `power(0::float8, negative)` is undefined (IEEE 754 would yield infinity).
37#[inline]
38fn float64_power_checked(base: f64, exp: f64) -> Result<f64, ArrowError> {
39    if base == 0.0 && exp < 0.0 {
40        return Err(ArrowError::ComputeError(
41            "zero raised to a negative power is undefined".to_string(),
42        ));
43    }
44    Ok(base.powf(exp))
45}
46
47#[user_doc(
48    doc_section(label = "Math Functions"),
49    description = "Returns a base expression raised to the power of an exponent.",
50    syntax_example = "power(base, exponent)",
51    sql_example = r#"```sql
52> SELECT power(2, 3);
53+-------------+
54| power(2,3)  |
55+-------------+
56| 8           |
57+-------------+
58```"#,
59    standard_argument(name = "base", prefix = "Numeric"),
60    standard_argument(name = "exponent", prefix = "Exponent numeric")
61)]
62#[derive(Debug, PartialEq, Eq, Hash)]
63pub struct PowerFunc {
64    signature: Signature,
65    aliases: Vec<String>,
66}
67
68impl Default for PowerFunc {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74impl PowerFunc {
75    pub fn new() -> Self {
76        let float = Coercion::new_implicit(
77            TypeSignatureClass::Native(logical_float64()),
78            vec![TypeSignatureClass::Numeric],
79            NativeType::Float64,
80        );
81        Self {
82            signature: Signature::coercible(vec![float; 2], Volatility::Immutable),
83            aliases: vec![String::from("pow")],
84        }
85    }
86}
87
88impl ScalarUDFImpl for PowerFunc {
89    fn name(&self) -> &str {
90        "power"
91    }
92
93    fn signature(&self) -> &Signature {
94        &self.signature
95    }
96
97    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
98        let [_base, _exponent] = take_function_args(self.name(), arg_types)?;
99        Ok(DataType::Float64)
100    }
101
102    fn is_strict(&self) -> bool {
103        true
104    }
105
106    fn aliases(&self) -> &[String] {
107        &self.aliases
108    }
109
110    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
111        let [base, exponent] = take_function_args(self.name(), &args.args)?;
112        let base = base.to_array(args.number_rows)?;
113
114        let arr: ArrayRef = match (base.data_type(), exponent.data_type()) {
115            (DataType::Float64, DataType::Float64) => {
116                calculate_binary_math::<Float64Type, Float64Type, Float64Type, _>(
117                    &base,
118                    exponent,
119                    float64_power_checked,
120                )?
121            }
122            (base_type, exp_type) => {
123                return internal_err!(
124                    "Unsupported data types for base {base_type:?} and exponent {exp_type:?} for power"
125                );
126            }
127        };
128        Ok(ColumnarValue::Array(arr))
129    }
130
131    /// Simplify the `power` function by the relevant rules:
132    /// 1. Power(a, 0) ===> 1
133    /// 2. Power(a, 1) ===> a
134    /// 3. Power(a, Log(a, b)) ===> b
135    fn simplify(
136        &self,
137        args: Vec<Expr>,
138        info: &SimplifyContext,
139    ) -> Result<ExprSimplifyResult> {
140        let [base, exponent] = take_function_args("power", args)?;
141        let base_type = info.get_data_type(&base)?;
142        let exponent_type = info.get_data_type(&exponent)?;
143        let base_nullable = info.nullable(&base)?;
144        let return_type =
145            self.return_type(&[base_type.clone(), exponent_type.clone()])?;
146
147        // Null propagation
148        if base_type.is_null() || exponent_type.is_null() {
149            return Ok(ExprSimplifyResult::Simplified(lit(
150                ScalarValue::Null.cast_to(&return_type)?
151            )));
152        }
153
154        // `simplify` runs on the logical expression *before* type coercion,
155        // so a simplified sub-expression may still carry its original type
156        // rather than the Float64 that `power` is declared to return. Cast it
157        // back when needed to preserve the schema the optimizer already
158        // committed to — e.g. `power(int_col, 1)` simplifies to `int_col`,
159        // and the `b` in `power(b, log(b, uint_col))` simplifies to `uint_col`,
160        // both of which must become Float64.
161        let cast_to_return_type = |expr: Expr, expr_type: &DataType| {
162            if expr_type == &return_type {
163                expr
164            } else {
165                Expr::Cast(Cast::new(Box::new(expr), return_type.clone()))
166            }
167        };
168
169        match exponent {
170            Expr::Literal(value, _)
171                if value == ScalarValue::new_zero(&exponent_type)? && !base_nullable =>
172            {
173                Ok(ExprSimplifyResult::Simplified(lit(ScalarValue::new_one(
174                    &return_type,
175                )?)))
176            }
177            Expr::Literal(value, _) if value == ScalarValue::new_one(&exponent_type)? => {
178                Ok(ExprSimplifyResult::Simplified(cast_to_return_type(
179                    base, &base_type,
180                )))
181            }
182            Expr::ScalarFunction(ScalarFunction { func, mut args })
183                if is_log(&func)
184                    && args.len() == 2
185                    && base == args[0]
186                    && !base_nullable =>
187            {
188                let b = args.pop().unwrap(); // length checked above
189                let b_type = info.get_data_type(&b)?;
190                Ok(ExprSimplifyResult::Simplified(cast_to_return_type(
191                    b, &b_type,
192                )))
193            }
194            _ => Ok(ExprSimplifyResult::Original(vec![base, exponent])),
195        }
196    }
197
198    fn documentation(&self) -> Option<&Documentation> {
199        self.doc()
200    }
201}
202
203/// Return true if this function call is a call to `Log`
204fn is_log(func: &ScalarUDF) -> bool {
205    func.inner().is::<LogFunc>()
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn test_float64_power_checked_zero_negative_exp() {
214        assert_eq!(float64_power_checked(0.0, 1.0).unwrap(), 0.0);
215        assert_eq!(float64_power_checked(2.0, -1.0).unwrap(), 0.5);
216        for base in [0.0f64, -0.0] {
217            assert!(float64_power_checked(base, -1.0).is_err());
218            assert!(float64_power_checked(base, -0.5).is_err());
219        }
220    }
221}