This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-22651-5472d00856a1d0f6c36afb415ba89f9defff1aeb
in repository https://gitbox.apache.org/repos/asf/datafusion.git

commit 12a9b1184406756655c8597305bd96c29ab6734a
Author: Neil Conway <[email protected]>
AuthorDate: Thu Jun 4 05:38:58 2026 -0400

    fix: Remove `power(decimal, int)` code path (#22651)
    
    ## Which issue does this PR close?
    
    - Closes #22480
    - Closes #22510
    
    ## Rationale for this change
    
    `power(decimal, int)` attempted to compute `power` without loss of
    precision. For negative exponents, the code did the computation in
    `Float64` and then cast the result back to `decimal`. Unfortunately, the
    previous implementation got this wrong, because `decimal` might not have
    enough precision to accurately represent the result. It seems simpler to
    return `Float64` for the negative exponent case.
    
    We could potentially try to return `decimal` only for non-negative
    exponents and `Float64` for negative exponents, but that is complicated,
    and also means that the code would produce different results for literal
    arguments vs. columnar arguments, which I think should be avoided.
    
    On reflection, it seems simplest to just remove the `power(decimal,
    int)` code path entirely, and have `power` always return `Float64`. This
    also fixes another issue in the decimal code path (#22480)
    
    ## What changes are included in this PR?
    
    * Remove `power(decimal, ...)` support; both args will be coerced to
    Float64 if necessary, and the function will always return Float64
    * Update SLT
    * Add new test cases for #22480 and #22510
    
    ## Are these changes tested?
    
    Yes.
    
    ## Are there any user-facing changes?
    
    Yes, `power(decimal, ...)` will now return `Float64`.
---
 datafusion/core/src/execution/session_state.rs   |   4 +-
 datafusion/core/tests/expr_api/simplification.rs |  17 +-
 datafusion/functions/src/math/power.rs           | 429 ++---------------------
 datafusion/sqllogictest/test_files/decimal.slt   |  67 ++--
 datafusion/sqllogictest/test_files/math.slt      |   2 +-
 5 files changed, 89 insertions(+), 430 deletions(-)

diff --git a/datafusion/core/src/execution/session_state.rs 
b/datafusion/core/src/execution/session_state.rs
index 786450c001..ed2ea27cf4 100644
--- a/datafusion/core/src/execution/session_state.rs
+++ b/datafusion/core/src/execution/session_state.rs
@@ -30,7 +30,9 @@ use crate::datasource::provider_as_source;
 use crate::execution::SessionStateDefaults;
 use crate::execution::context::{EmptySerializerRegistry, FunctionFactory, 
QueryPlanner};
 use crate::physical_planner::{DefaultPhysicalPlanner, PhysicalPlanner};
-use arrow_schema::{DataType, FieldRef};
+#[cfg(feature = "sql")]
+use arrow_schema::DataType;
+use arrow_schema::FieldRef;
 use datafusion_catalog::MemoryCatalogProviderList;
 use datafusion_catalog::information_schema::{
     INFORMATION_SCHEMA, InformationSchemaProvider,
diff --git a/datafusion/core/tests/expr_api/simplification.rs 
b/datafusion/core/tests/expr_api/simplification.rs
index 6e1271ef19..e9a975239a 100644
--- a/datafusion/core/tests/expr_api/simplification.rs
+++ b/datafusion/core/tests/expr_api/simplification.rs
@@ -639,28 +639,29 @@ fn test_simplify_power() {
     // Power(c3, 0) ===> 1
     {
         let expr = power(col("c3_non_null"), lit(0));
-        let expected = lit(1i64);
+        let expected = lit(1.0f64);
         test_simplify(expr, expected)
     }
-    // Power(c3, 1) ===> c3
+    // Power(c3, 1) ===> cast(c3 AS Float64)
     {
         let expr = power(col("c3_non_null"), lit(1));
-        let expected = col("c3_non_null");
+        let expected =
+            Expr::Cast(Cast::new(Box::new(col("c3_non_null")), 
DataType::Float64));
         test_simplify(expr, expected)
     }
-    // Power(c3, Log(c3, c4)) ===> cast(c4 AS Int64)
+    // Power(c3, Log(c3, c4)) ===> cast(c4 AS Float64)
     // The simplifier rewrites `power(b, log(b, x))` to `x`, but the
     // rewritten expression must keep the same type as the original
-    // `power` call. `power`'s declared return type follows its base
-    // argument (c3 = Int64), so the UInt32 c4 has to be cast to Int64
-    // to preserve the output schema the optimizer already committed to.
+    // `power` call. `power` returns Float64, so the UInt32 c4 has to be cast
+    // to Float64 to preserve the output schema the optimizer already
+    // committed to.
     {
         let expr = power(
             col("c3_non_null"),
             log(col("c3_non_null"), col("c4_non_null")),
         );
         let expected =
-            Expr::Cast(Cast::new(Box::new(col("c4_non_null")), 
DataType::Int64));
+            Expr::Cast(Cast::new(Box::new(col("c4_non_null")), 
DataType::Float64));
         test_simplify(expr, expected)
     }
     // Power(c3, c4) ===> Power(c3, c4)
diff --git a/datafusion/functions/src/math/power.rs 
b/datafusion/functions/src/math/power.rs
index fe8c179bff..252a3ea0b3 100644
--- a/datafusion/functions/src/math/power.rs
+++ b/datafusion/functions/src/math/power.rs
@@ -18,25 +18,20 @@
 //! Math function: `power()`.
 use super::log::LogFunc;
 
-use crate::utils::{calculate_binary_decimal_math, calculate_binary_math};
+use crate::utils::calculate_binary_math;
 use arrow::array::{Array, ArrayRef};
-use arrow::datatypes::i256;
-use arrow::datatypes::{
-    ArrowNativeType, ArrowNativeTypeOp, DataType, Decimal32Type, Decimal64Type,
-    Decimal128Type, Decimal256Type, Float64Type, Int64Type,
-};
+use arrow::datatypes::{DataType, Float64Type};
 use arrow::error::ArrowError;
-use datafusion_common::types::{NativeType, logical_float64, logical_int64};
+use datafusion_common::types::{NativeType, logical_float64};
 use datafusion_common::utils::take_function_args;
 use datafusion_common::{Result, ScalarValue, internal_err};
 use datafusion_expr::expr::ScalarFunction;
 use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyContext};
 use datafusion_expr::{
     Cast, Coercion, ColumnarValue, Documentation, Expr, ScalarFunctionArgs, 
ScalarUDF,
-    ScalarUDFImpl, Signature, TypeSignature, TypeSignatureClass, Volatility, 
lit,
+    ScalarUDFImpl, Signature, TypeSignatureClass, Volatility, lit,
 };
 use datafusion_macros::user_doc;
-use num_traits::{NumCast, ToPrimitive};
 
 /// Matches PostgreSQL: `power(0::float8, negative)` is undefined (IEEE 754 
would yield infinity).
 #[inline]
@@ -78,295 +73,18 @@ impl Default for PowerFunc {
 
 impl PowerFunc {
     pub fn new() -> Self {
-        let integer = Coercion::new_implicit(
-            TypeSignatureClass::Native(logical_int64()),
-            vec![TypeSignatureClass::Integer],
-            NativeType::Int64,
-        );
-        let decimal = Coercion::new_exact(TypeSignatureClass::Decimal);
         let float = Coercion::new_implicit(
             TypeSignatureClass::Native(logical_float64()),
             vec![TypeSignatureClass::Numeric],
             NativeType::Float64,
         );
         Self {
-            signature: Signature::one_of(
-                vec![
-                    TypeSignature::Coercible(vec![decimal, integer]),
-                    TypeSignature::Coercible(vec![float; 2]),
-                ],
-                Volatility::Immutable,
-            ),
+            signature: Signature::coercible(vec![float; 2], 
Volatility::Immutable),
             aliases: vec![String::from("pow")],
         }
     }
 }
 
-/// Binary function to calculate a math power to integer exponent
-/// for scaled integer types.
-///
-/// Formula
-/// The power for a scaled integer `b` is
-///
-/// ```text
-/// (b * 10^(-s)) ^ e
-/// ```
-/// However, the result should be scaled back from scale 0 to scale `s`,
-/// which is done by multiplying by `10^s`.
-/// At the end, the formula is:
-///
-/// ```text
-///   b^e * 10^(-s * e) * 10^s = b^e / 10^(s * (e-1))
-/// ```
-/// Example of 2.5 ^ 4 = 39:
-///   2.5 is represented as 25 with scale 1
-///   The unscaled result is 25^4 = 390625
-///   Scale it back to 1: 390625 / 10^4 = 39
-fn pow_decimal_int<T>(base: T, scale: i8, exp: i64) -> Result<T, ArrowError>
-where
-    T: ArrowNativeType + ArrowNativeTypeOp + ToPrimitive + NumCast + Copy,
-{
-    // Negative exponent: fall back to float computation
-    if exp < 0 {
-        return pow_decimal_float(base, scale, exp as f64);
-    }
-
-    let exp: u32 = exp.try_into().map_err(|_| {
-        ArrowError::ArithmeticOverflow(format!("Unsupported exp value: {exp}"))
-    })?;
-    // Handle edge case for exp == 0
-    // If scale < 0, 10^scale (e.g., 10^-2 = 0.01) becomes 0 in integer 
arithmetic.
-    if exp == 0 {
-        return if scale >= 0 {
-            T::usize_as(10).pow_checked(scale as u32).map_err(|_| {
-                ArrowError::ArithmeticOverflow(format!(
-                    "Cannot make unscale factor for {scale} and {exp}"
-                ))
-            })
-        } else {
-            Ok(T::ZERO)
-        };
-    }
-    let powered: T = base.pow_checked(exp).map_err(|_| {
-        ArrowError::ArithmeticOverflow(format!("Cannot raise base {base:?} to 
exp {exp}"))
-    })?;
-
-    // Calculate the scale adjustment: s * (e - 1)
-    // We use i64 to prevent overflow during the intermediate multiplication
-    let mul_exp = (scale as i64).wrapping_mul(exp as i64 - 1);
-
-    if mul_exp == 0 {
-        return Ok(powered);
-    }
-
-    // If mul_exp is positive, we divide (standard case).
-    // If mul_exp is negative, we multiply (negative scale case).
-    if mul_exp > 0 {
-        let div_factor: T =
-            T::usize_as(10).pow_checked(mul_exp as u32).map_err(|_| {
-                ArrowError::ArithmeticOverflow(format!(
-                    "Cannot make div factor for {scale} and {exp}"
-                ))
-            })?;
-        powered.div_checked(div_factor)
-    } else {
-        // mul_exp is negative, so we multiply by 10^(-mul_exp)
-        let abs_exp = mul_exp.checked_neg().ok_or_else(|| {
-            ArrowError::ArithmeticOverflow(
-                "Overflow while negating scale exponent".to_string(),
-            )
-        })?;
-        let mul_factor: T =
-            T::usize_as(10).pow_checked(abs_exp as u32).map_err(|_| {
-                ArrowError::ArithmeticOverflow(format!(
-                    "Cannot make mul factor for {scale} and {exp}"
-                ))
-            })?;
-        powered.mul_checked(mul_factor)
-    }
-}
-
-/// Binary function to calculate a math power to float exponent
-/// for scaled integer types.
-fn pow_decimal_float<T>(base: T, scale: i8, exp: f64) -> Result<T, ArrowError>
-where
-    T: ArrowNativeType + ArrowNativeTypeOp + ToPrimitive + NumCast + Copy,
-{
-    if exp.is_finite() && exp.trunc() == exp && exp >= 0f64 && exp < u32::MAX 
as f64 {
-        return pow_decimal_int(base, scale, exp as i64);
-    }
-
-    if !exp.is_finite() {
-        return Err(ArrowError::ComputeError(format!(
-            "Cannot use non-finite exp: {exp}"
-        )));
-    }
-
-    pow_decimal_float_fallback(base, scale, exp)
-}
-
-/// Compute the f64 power result and scale it back.
-/// Returns the rounded i128 result for conversion to target type.
-#[inline]
-fn compute_pow_f64_result(
-    base_f64: f64,
-    scale: i8,
-    exp: f64,
-) -> Result<i128, ArrowError> {
-    let result_f64 = float64_power_checked(base_f64, exp)?;
-
-    if !result_f64.is_finite() {
-        return Err(ArrowError::ArithmeticOverflow(format!(
-            "Result of {base_f64}^{exp} is not finite"
-        )));
-    }
-
-    let scale_factor = 10f64.powi(scale as i32);
-    let result_scaled = result_f64 * scale_factor;
-    let result_rounded = result_scaled.round();
-
-    if result_rounded.abs() > i128::MAX as f64 {
-        return Err(ArrowError::ArithmeticOverflow(format!(
-            "Result {result_rounded} is too large for the target decimal type"
-        )));
-    }
-
-    Ok(result_rounded as i128)
-}
-
-/// Convert i128 result to target decimal native type using NumCast.
-/// Returns error if value overflows the target type.
-#[inline]
-fn decimal_from_i128<T>(value: i128) -> Result<T, ArrowError>
-where
-    T: NumCast,
-{
-    NumCast::from(value).ok_or_else(|| {
-        ArrowError::ArithmeticOverflow(format!(
-            "Value {value} is too large for the target decimal type"
-        ))
-    })
-}
-
-/// Fallback for `pow_decimal_int` when the exponent is negative or 
non-integer.
-fn pow_decimal_float_fallback<T>(base: T, scale: i8, exp: f64) -> Result<T, 
ArrowError>
-where
-    T: ToPrimitive + NumCast + Copy,
-{
-    if scale < 0 {
-        return Err(ArrowError::NotYetImplemented(format!(
-            "Negative scale is not yet supported: {scale}"
-        )));
-    }
-
-    let scale_factor = 10f64.powi(scale as i32);
-    let base_f64 = base.to_f64().ok_or_else(|| {
-        ArrowError::ComputeError("Cannot convert base to f64".to_string())
-    })? / scale_factor;
-
-    let result_i128 = compute_pow_f64_result(base_f64, scale, exp)?;
-
-    decimal_from_i128(result_i128)
-}
-
-/// Like `pow_decimal_float`, but specialized for Decimal256.
-fn pow_decimal256_float(base: i256, scale: i8, exp: f64) -> Result<i256, 
ArrowError> {
-    if exp.is_finite() && exp.trunc() == exp && exp >= 0f64 && exp < u32::MAX 
as f64 {
-        return pow_decimal256_int(base, scale, exp as i64);
-    }
-
-    if !exp.is_finite() {
-        return Err(ArrowError::ComputeError(format!(
-            "Cannot use non-finite exp: {exp}"
-        )));
-    }
-
-    pow_decimal256_float_fallback(base, scale, exp)
-}
-
-/// Like `pow_decimal_int`, but specialized for Decimal256.
-fn pow_decimal256_int(base: i256, scale: i8, exp: i64) -> Result<i256, 
ArrowError> {
-    if exp < 0 {
-        return pow_decimal256_float(base, scale, exp as f64);
-    }
-
-    let exp: u32 = exp.try_into().map_err(|_| {
-        ArrowError::ArithmeticOverflow(format!("Unsupported exp value: {exp}"))
-    })?;
-
-    if exp == 0 {
-        return if scale >= 0 {
-            i256::from_i128(10).pow_checked(scale as u32).map_err(|_| {
-                ArrowError::ArithmeticOverflow(format!(
-                    "Cannot make unscale factor for {scale} and {exp}"
-                ))
-            })
-        } else {
-            Ok(i256::from_i128(0))
-        };
-    }
-
-    let powered: i256 = base.pow_checked(exp).map_err(|_| {
-        ArrowError::ArithmeticOverflow(format!("Cannot raise base {base:?} to 
exp {exp}"))
-    })?;
-
-    let mul_exp = (scale as i64).wrapping_mul(exp as i64 - 1);
-
-    if mul_exp == 0 {
-        return Ok(powered);
-    }
-
-    if mul_exp > 0 {
-        let div_factor: i256 =
-            i256::from_i128(10)
-                .pow_checked(mul_exp as u32)
-                .map_err(|_| {
-                    ArrowError::ArithmeticOverflow(format!(
-                        "Cannot make div factor for {scale} and {exp}"
-                    ))
-                })?;
-        powered.div_checked(div_factor)
-    } else {
-        let abs_exp = mul_exp.checked_neg().ok_or_else(|| {
-            ArrowError::ArithmeticOverflow(
-                "Overflow while negating scale exponent".to_string(),
-            )
-        })?;
-        let mul_factor: i256 =
-            i256::from_i128(10)
-                .pow_checked(abs_exp as u32)
-                .map_err(|_| {
-                    ArrowError::ArithmeticOverflow(format!(
-                        "Cannot make mul factor for {scale} and {exp}"
-                    ))
-                })?;
-        powered.mul_checked(mul_factor)
-    }
-}
-
-/// Like `pow_decimal_float_fallback`, but specialized for Decimal256.
-fn pow_decimal256_float_fallback(
-    base: i256,
-    scale: i8,
-    exp: f64,
-) -> Result<i256, ArrowError> {
-    if scale < 0 {
-        return Err(ArrowError::NotYetImplemented(format!(
-            "Negative scale is not yet supported: {scale}"
-        )));
-    }
-
-    let scale_factor = 10f64.powi(scale as i32);
-    let base_f64 = base.to_f64().ok_or_else(|| {
-        ArrowError::ComputeError("Cannot convert base to f64".to_string())
-    })? / scale_factor;
-
-    let result_i128 = compute_pow_f64_result(base_f64, scale, exp)?;
-
-    // i256 can be constructed from i128 directly
-    Ok(i256::from_i128(result_i128))
-}
-
 impl ScalarUDFImpl for PowerFunc {
     fn name(&self) -> &str {
         "power"
@@ -377,17 +95,8 @@ impl ScalarUDFImpl for PowerFunc {
     }
 
     fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
-        // Return type as a function of (base, exponent). After signature
-        // coercion, we have to handle the following cases:
-        //
-        //   - NULL on either side -> Float64 (typed NULL)
-        //   - (Decimal, Int64)    -> the base's Decimal type
-        //   - (Float64, Float64)  -> Float64
-        let [base, exponent] = take_function_args(self.name(), arg_types)?;
-        if base.is_null() || exponent.is_null() {
-            return Ok(DataType::Float64);
-        }
-        Ok(base.clone())
+        let [_base, _exponent] = take_function_args(self.name(), arg_types)?;
+        Ok(DataType::Float64)
     }
 
     fn aliases(&self) -> &[String] {
@@ -398,18 +107,6 @@ impl ScalarUDFImpl for PowerFunc {
         let [base, exponent] = take_function_args(self.name(), &args.args)?;
         let base = base.to_array(args.number_rows)?;
 
-        macro_rules! decimal_pow_arm {
-            ($decimal_ty:ident, $pow_fn:ident, $precision:expr, $scale:expr) 
=> {
-                calculate_binary_decimal_math::<$decimal_ty, Int64Type, 
$decimal_ty, _>(
-                    &base,
-                    exponent,
-                    |b, e| $pow_fn(b, *$scale, e),
-                    *$precision,
-                    *$scale,
-                )?
-            };
-        }
-
         let arr: ArrayRef = match (base.data_type(), exponent.data_type()) {
             (DataType::Float64, DataType::Float64) => {
                 calculate_binary_math::<Float64Type, Float64Type, Float64Type, 
_>(
@@ -418,18 +115,6 @@ impl ScalarUDFImpl for PowerFunc {
                     float64_power_checked,
                 )?
             }
-            (DataType::Decimal32(precision, scale), DataType::Int64) => {
-                decimal_pow_arm!(Decimal32Type, pow_decimal_int, precision, 
scale)
-            }
-            (DataType::Decimal64(precision, scale), DataType::Int64) => {
-                decimal_pow_arm!(Decimal64Type, pow_decimal_int, precision, 
scale)
-            }
-            (DataType::Decimal128(precision, scale), DataType::Int64) => {
-                decimal_pow_arm!(Decimal128Type, pow_decimal_int, precision, 
scale)
-            }
-            (DataType::Decimal256(precision, scale), DataType::Int64) => {
-                decimal_pow_arm!(Decimal256Type, pow_decimal256_int, 
precision, scale)
-            }
             (base_type, exp_type) => {
                 return internal_err!(
                     "Unsupported data types for base {base_type:?} and 
exponent {exp_type:?} for power"
@@ -451,16 +136,31 @@ impl ScalarUDFImpl for PowerFunc {
         let [base, exponent] = take_function_args("power", args)?;
         let base_type = info.get_data_type(&base)?;
         let exponent_type = info.get_data_type(&exponent)?;
+        let return_type =
+            self.return_type(&[base_type.clone(), exponent_type.clone()])?;
 
         // Null propagation
         if base_type.is_null() || exponent_type.is_null() {
-            let return_type = self.return_type(&[base_type, exponent_type])?;
             return Ok(ExprSimplifyResult::Simplified(lit(
                 ScalarValue::Null.cast_to(&return_type)?
             )));
         }
 
-        let return_type = self.return_type(&[base_type, 
exponent_type.clone()])?;
+        // `simplify` runs on the logical expression *before* type coercion,
+        // so a simplified sub-expression may still carry its original type
+        // rather than the Float64 that `power` is declared to return. Cast it
+        // back when needed to preserve the schema the optimizer already
+        // committed to — e.g. `power(int_col, 1)` simplifies to `int_col`,
+        // and the `b` in `power(b, log(b, uint_col))` simplifies to 
`uint_col`,
+        // both of which must become Float64.
+        let cast_to_return_type = |expr: Expr, expr_type: &DataType| {
+            if expr_type == &return_type {
+                expr
+            } else {
+                Expr::Cast(Cast::new(Box::new(expr), return_type.clone()))
+            }
+        };
+
         match exponent {
             Expr::Literal(value, _)
                 if value == ScalarValue::new_zero(&exponent_type)? =>
@@ -470,22 +170,18 @@ impl ScalarUDFImpl for PowerFunc {
                 )?)))
             }
             Expr::Literal(value, _) if value == 
ScalarValue::new_one(&exponent_type)? => {
-                Ok(ExprSimplifyResult::Simplified(base))
+                Ok(ExprSimplifyResult::Simplified(cast_to_return_type(
+                    base, &base_type,
+                )))
             }
             Expr::ScalarFunction(ScalarFunction { func, mut args })
                 if is_log(&func) && args.len() == 2 && base == args[0] =>
             {
-                // The inner `b` may have a different type than the power
-                // call's `return_type` (e.g. `power(int64, log(int64,
-                // uint32))` returns Int64 but `b` is UInt32). Wrap it
-                // in a cast to preserve the optimizer's expected schema.
                 let b = args.pop().unwrap(); // length checked above
-                let result = if info.get_data_type(&b)? != return_type {
-                    Expr::Cast(Cast::new(Box::new(b), return_type))
-                } else {
-                    b
-                };
-                Ok(ExprSimplifyResult::Simplified(result))
+                let b_type = info.get_data_type(&b)?;
+                Ok(ExprSimplifyResult::Simplified(cast_to_return_type(
+                    b, &b_type,
+                )))
             }
             _ => Ok(ExprSimplifyResult::Original(vec![base, exponent])),
         }
@@ -505,69 +201,6 @@ fn is_log(func: &ScalarUDF) -> bool {
 mod tests {
     use super::*;
 
-    #[test]
-    fn test_pow_decimal128_helper() {
-        // Expression: 2.5 ^ 4 = 39.0625
-        assert_eq!(pow_decimal_int(25i128, 1, 4).unwrap(), 390i128);
-        assert_eq!(pow_decimal_int(2500i128, 3, 4).unwrap(), 39062i128);
-        assert_eq!(pow_decimal_int(25000i128, 4, 4).unwrap(), 390625i128);
-
-        // Expression: 25 ^ 4 = 390625
-        assert_eq!(pow_decimal_int(25i128, 0, 4).unwrap(), 390625i128);
-
-        // Expressions for edge cases
-        assert_eq!(pow_decimal_int(25i128, 1, 1).unwrap(), 25i128);
-        assert_eq!(pow_decimal_int(25i128, 0, 1).unwrap(), 25i128);
-        assert_eq!(pow_decimal_int(25i128, 0, 0).unwrap(), 1i128);
-        assert_eq!(pow_decimal_int(25i128, 1, 0).unwrap(), 10i128);
-
-        assert_eq!(pow_decimal_int(25i128, -1, 4).unwrap(), 390625000i128);
-    }
-
-    #[test]
-    fn test_pow_decimal_float_fallback() {
-        // Test negative exponent: 4^(-1) = 0.25
-        // 4 with scale 2 = 400, result should be 25 (0.25 with scale 2)
-        let result: i128 = pow_decimal_float(400i128, 2, -1.0).unwrap();
-        assert_eq!(result, 25);
-
-        // Test non-integer exponent: 4^0.5 = 2
-        // 4 with scale 2 = 400, result should be 200 (2.0 with scale 2)
-        let result: i128 = pow_decimal_float(400i128, 2, 0.5).unwrap();
-        assert_eq!(result, 200);
-
-        // Test 8^(1/3) = 2 (cube root)
-        // 8 with scale 1 = 80, result should be 20 (2.0 with scale 1)
-        let result: i128 = pow_decimal_float(80i128, 1, 1.0 / 3.0).unwrap();
-        assert_eq!(result, 20);
-
-        // Test negative base with integer exponent still works
-        // (-2)^3 = -8
-        // -2 with scale 1 = -20, result should be -80 (-8.0 with scale 1)
-        let result: i128 = pow_decimal_float(-20i128, 1, 3.0).unwrap();
-        assert_eq!(result, -80);
-
-        // Test positive integer exponent goes through fast path
-        // 2.5^4 = 39.0625
-        // 25 with scale 1, result should be 390 (39.0 with scale 1) - 
truncated
-        let result: i128 = pow_decimal_float(25i128, 1, 4.0).unwrap();
-        assert_eq!(result, 390); // Uses integer path
-
-        // Test non-finite exponent returns error
-        assert!(pow_decimal_float(100i128, 2, f64::NAN).is_err());
-        assert!(pow_decimal_float(100i128, 2, f64::INFINITY).is_err());
-
-        // PostgreSQL: zero to a negative power is undefined
-        assert!(pow_decimal_float(0i128, 2, -1.0).is_err());
-    }
-
-    #[test]
-    fn test_pow_decimal256_zero_to_negative_exp_errors() {
-        assert!(pow_decimal256_float(i256::ZERO, 2, -1.0).is_err());
-        // Negative integer exponent uses pow_decimal256_float via 
pow_decimal256_int
-        assert!(pow_decimal256_int(i256::ZERO, 2, -1).is_err());
-    }
-
     #[test]
     fn test_float64_power_checked_zero_negative_exp() {
         assert_eq!(float64_power_checked(0.0, 1.0).unwrap(), 0.0);
diff --git a/datafusion/sqllogictest/test_files/decimal.slt 
b/datafusion/sqllogictest/test_files/decimal.slt
index d9eac84928..dd2b294557 100644
--- a/datafusion/sqllogictest/test_files/decimal.slt
+++ b/datafusion/sqllogictest/test_files/decimal.slt
@@ -1046,17 +1046,17 @@ SELECT log(10, arrow_cast(1 , 'Decimal32(5, 1)'))
 query RT
 SELECT power(2::decimal(38, 0), 4), arrow_typeof(power(2::decimal(38, 0), 4));
 ----
-16 Decimal128(38, 0)
+16 Float64
 
 query RT
 SELECT power(10000000000::decimal(38, 0), 2), 
arrow_typeof(power(10000000000::decimal(38, 0), 2));
 ----
-100000000000000000000 Decimal128(38, 0)
+100000000000000000000 Float64
 
 query R
 SELECT power(2.5, 4)
 ----
-39
+39.0625
 
 query R
 SELECT power(2.5, 1)
@@ -1093,30 +1093,45 @@ SELECT power(2, 100000000000)
 ----
 Infinity
 
-# Negative exponent now works (fallback to f64)
+# Negative exponent returns Float64 so fractional results are representable
 query RT
 SELECT power(2::decimal(38, 0), -5), arrow_typeof(power(2::decimal(38, 0), 
-5));
 ----
-0 Decimal128(38, 0)
+0.03125 Float64
+
+query RT
+SELECT power(CAST(2 AS DECIMAL(10, 0)), -3), arrow_typeof(power(CAST(2 AS 
DECIMAL(10, 0)), -3));
+----
+0.125 Float64
 
-# Negative exponent with scale preserves decimal places
 query RT
 SELECT power(4::decimal(38, 5), -1), arrow_typeof(power(4::decimal(38, 5), 
-1));
 ----
-0.25 Decimal128(38, 5)
+0.25 Float64
+
+query IRT
+SELECT exponent, power(2::decimal(10, 0), exponent), 
arrow_typeof(power(2::decimal(10, 0), exponent))
+FROM (VALUES (-3), (3)) AS t(exponent)
+ORDER BY exponent;
+----
+-3 0.125 Float64
+3 8 Float64
 
-# Expected to have `16 Decimal128(38, 0)`
-# Due to type coericion, it becomes Float -> Float -> Float
 query RT
 SELECT power(2::decimal(38, 0), 4), arrow_typeof(power(2::decimal(38, 0), 4));
 ----
-16 Decimal128(38, 0)
+16 Float64
 
-# Arbitrary scale
 query RT
 SELECT power(2.5::decimal(38, 3), 4), arrow_typeof(power(2.5::decimal(38, 3), 
4));
 ----
-39.062 Decimal128(38, 3)
+39.0625 Float64
+
+# https://github.com/apache/datafusion/issues/22480
+query RT
+SELECT power(2.5::decimal(20, 4), 10), arrow_typeof(power(2.5::decimal(20, 4), 
10));
+----
+9536.7431640625 Float64
 
 query RT
 SELECT power(2.5, 4.0), arrow_typeof(power(2.5, 4.0));
@@ -1147,30 +1162,38 @@ SELECT power(2::decimal(38, 0), 5000000000.1),
 ----
 Infinity Float64
 
-# Integer Above u32::max - still goes through integer path which fails
-query error Arrow error: Arithmetic overflow: Unsupported exp value
-SELECT power(2::decimal(38, 0), 5000000000)
+# Integer above u32::max uses the Float64 decimal/int path
+query RT
+SELECT power(2::decimal(38, 0), 5000000000),
+       arrow_typeof(power(2::decimal(38, 0), 5000000000));
+----
+Infinity Float64
 
-query ?T
+query RT
 SELECT power(arrow_cast(2, 'Decimal32(5, 0)'), 4), 
arrow_typeof(power(arrow_cast(2, 'Decimal32(5, 0)'), 4));
 ----
-16 Decimal32(5, 0)
+16 Float64
 
-query ?T
+query RT
 SELECT power(arrow_cast(2, 'Decimal64(5, 0)'), 4), 
arrow_typeof(power(arrow_cast(2, 'Decimal64(5, 0)'), 4));
 ----
-16 Decimal64(5, 0)
+16 Float64
 
 query RT
 SELECT power(2::decimal(76, 0), 4), arrow_typeof(power(2::decimal(76, 0), 4));
 ----
-16 Decimal256(76, 0)
+16 Float64
 
 query R
 SELECT power(2.0, null)
 ----
 NULL
 
+query RT
+SELECT power(2::decimal(38, 0), null), arrow_typeof(power(2::decimal(38, 0), 
null));
+----
+NULL Float64
+
 # Array variants of power function
 query RR rowsort
 SELECT distinct c1*100000, power(c1*100000, 2) from decimal_simple;
@@ -1214,7 +1237,7 @@ select 
log(100000000000000000000000000000000000::decimal(38,0))
 ----
 35
 
-# Result is decimal since argument is decimal regardless decimals-as-floats 
parsing
+# Decimal x Int64 returns Float64 regardless of decimals-as-floats parsing
 query R
 SELECT power(10000000000::decimal(38, 0), 2);
 ----
@@ -1224,7 +1247,7 @@ query RT
 SELECT power(10000000000::decimal(38, 0), 2),
        arrow_typeof(power(10000000000::decimal(38, 0), 2));
 ----
-100000000000000000000 Decimal128(38, 0)
+100000000000000000000 Float64
 
 query R
 SELECT power(2.5, 4.0)
diff --git a/datafusion/sqllogictest/test_files/math.slt 
b/datafusion/sqllogictest/test_files/math.slt
index e261bada87..1748c9b3e5 100644
--- a/datafusion/sqllogictest/test_files/math.slt
+++ b/datafusion/sqllogictest/test_files/math.slt
@@ -844,7 +844,7 @@ select
   pow(2.5::decimal(2, 1), 4::bigint),
   arrow_typeof(pow(2.5::decimal(2, 1), 4::bigint));
 ----
-39 Decimal128(2, 1)
+39.0625 Float64
 
 # factorial negative (PostgreSQL-compatible domain error)
 query error DataFusion error: Execution error: factorial of a negative number 
is undefined


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to