From 69306853199397a5a59cceea54546ad865c0fc85 Mon Sep 17 00:00:00 2001 From: theirix Date: Sun, 12 Oct 2025 19:18:26 +0100 Subject: [PATCH 01/10] Support Decimal128 and Decimal256 for pow --- datafusion/functions/src/math/power.rs | 449 +++++++++++++++++++++++-- datafusion/functions/src/utils.rs | 19 ++ 2 files changed, 432 insertions(+), 36 deletions(-) diff --git a/datafusion/functions/src/math/power.rs b/datafusion/functions/src/math/power.rs index ad2e795d086e..f71cfa2adee7 100644 --- a/datafusion/functions/src/math/power.rs +++ b/datafusion/functions/src/math/power.rs @@ -21,18 +21,22 @@ use std::sync::Arc; use super::log::LogFunc; -use arrow::array::{ArrayRef, AsArray, Int64Array}; -use arrow::datatypes::{ArrowNativeTypeOp, DataType, Float64Type}; -use datafusion_common::{ - arrow_datafusion_err, exec_datafusion_err, exec_err, plan_datafusion_err, - DataFusionError, Result, ScalarValue, +use crate::utils::{calculate_binary_math, decimal128_to_i128, decimal256_to_i256}; +use arrow::array::ArrayRef; +use arrow::datatypes::{ + ArrowNativeTypeOp, DataType, Decimal128Type, Decimal256Type, Float32Type, + Float64Type, Int32Type, Int64Type, }; +use arrow::error::ArrowError; +use arrow_buffer::i256; +use datafusion_common::{exec_err, plan_datafusion_err, plan_err, Result, ScalarValue}; use datafusion_expr::expr::ScalarFunction; use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyInfo}; use datafusion_expr::{ ColumnarValue, Documentation, Expr, ScalarFunctionArgs, ScalarUDF, TypeSignature, }; use datafusion_expr::{ScalarUDFImpl, Signature, Volatility}; +use datafusion_expr_common::signature::TypeSignature::Numeric; use datafusion_macros::user_doc; #[user_doc( @@ -68,8 +72,11 @@ impl PowerFunc { Self { signature: Signature::one_of( vec![ + TypeSignature::Exact(vec![Int32, Int32]), TypeSignature::Exact(vec![Int64, Int64]), + TypeSignature::Exact(vec![Float32, Float32]), TypeSignature::Exact(vec![Float64, Float64]), + Numeric(2), // Catch-all for all decimals ], Volatility::Immutable, ), @@ -78,6 +85,69 @@ impl PowerFunc { } } +fn pow_decimal128_helper(base: i128, scale: i8, exp: u32) -> Result { + decimal128_to_i128(base, scale)?.pow_checked(exp) +} + +/// Binary function to calculate a math power +/// Returns error if base is invalid +fn pow_decimal128_float(base: i128, scale: i8, exp: f64) -> Result { + if !exp.is_finite() || exp.trunc() != exp { + return Err(ArrowError::ComputeError(format!( + "Cannot use non-integer exp: {exp}" + ))); + } + if exp < 0f64 || exp >= u32::MAX as f64 { + return Err(ArrowError::ArithmeticOverflow(format!( + "Unsupported exp value: {exp}" + ))); + } + + pow_decimal128_helper(base, scale, exp as u32) +} + +/// Binary function to calculate a math power +fn pow_decimal128_int(base: i128, scale: i8, exp: i64) -> Result { + match exp.try_into() { + Ok(exp) => pow_decimal128_helper(base, scale, exp), + Err(_) => Err(ArrowError::ArithmeticOverflow(format!( + "Cannot use non-positive exp: {exp}" + ))), + } +} + +fn pow_decimal256_helper(base: i256, scale: i8, exp: u32) -> Result { + decimal256_to_i256(base, scale)?.pow_checked(exp) +} + +/// Binary function to calculate a math power +/// Returns error if base is invalid +fn pow_decimal256_float(base: i256, scale: i8, exp: f64) -> Result { + if !exp.is_finite() || exp.trunc() != exp { + return Err(ArrowError::ComputeError(format!( + "Cannot use non-integer exp: {exp}" + ))); + } + if exp < 0f64 || exp >= u32::MAX as f64 { + return Err(ArrowError::ArithmeticOverflow(format!( + "Unsupported exp value: {exp}" + ))); + } + + pow_decimal256_helper(base, scale, exp as u32) +} + +/// Binary function to calculate a math power +/// Returns error if base is invalid +fn pow_decimal256_int(base: i256, scale: i8, exp: i64) -> Result { + match exp.try_into() { + Ok(exp) => pow_decimal256_helper(base, scale, exp), + Err(_) => Err(ArrowError::ArithmeticOverflow(format!( + "Unsupported exp value: {exp}" + ))), + } +} + impl ScalarUDFImpl for PowerFunc { fn as_any(&self) -> &dyn Any { self @@ -92,8 +162,20 @@ impl ScalarUDFImpl for PowerFunc { fn return_type(&self, arg_types: &[DataType]) -> Result { match arg_types[0] { + DataType::Int32 => Ok(DataType::Int32), DataType::Int64 => Ok(DataType::Int64), - _ => Ok(DataType::Float64), + DataType::Float32 => Ok(DataType::Float32), + DataType::Float64 => Ok(DataType::Float64), + DataType::Decimal128(precision, scale) => { + Ok(DataType::Decimal128(precision, scale)) + } + DataType::Decimal256(precision, scale) => { + Ok(DataType::Decimal256(precision, scale)) + } + _ => plan_err!( + "Unsupported arguments {arg_types:?} for function {}", + self.name() + ), } } @@ -104,37 +186,86 @@ impl ScalarUDFImpl for PowerFunc { fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result { let args = ColumnarValue::values_to_arrays(&args.args)?; - let arr: ArrayRef = match args[0].data_type() { + let base = &args[0]; + let exponent = ColumnarValue::Array(Arc::clone(&args[1])); + + let arr: ArrayRef = match base.data_type() { + DataType::Float32 => { + calculate_binary_math::( + base, + &exponent, + |b, e| Ok(f32::powf(b, e)), + )? + } DataType::Float64 => { - let bases = args[0].as_primitive::(); - let exponents = args[1].as_primitive::(); - let result = arrow::compute::binary::<_, _, _, Float64Type>( - bases, - exponents, - f64::powf, - )?; - Arc::new(result) as _ + calculate_binary_math::( + &base, + &exponent, + |b, e| Ok(f64::powf(b, e)), + )? + } + DataType::Int32 => { + calculate_binary_math::( + &base, + &exponent, + |b, e| match e.try_into() { + Ok(exp_u32) => b.pow_checked(exp_u32), + Err(_) => Err(ArrowError::ArithmeticOverflow( + format!("Exponent {e} in integer computation is out of bounds.") + )), + }, + )? } DataType::Int64 => { - let bases = downcast_named_arg!(&args[0], "base", Int64Array); - let exponents = downcast_named_arg!(&args[1], "exponent", Int64Array); - bases - .iter() - .zip(exponents.iter()) - .map(|(base, exp)| match (base, exp) { - (Some(base), Some(exp)) => Ok(Some(base.pow_checked( - exp.try_into().map_err(|_| { - exec_datafusion_err!( - "Can't use negative exponents: {exp} in integer computation, please use Float." - ) - })?, - ).map_err(|e| arrow_datafusion_err!(e))?)), - _ => Ok(None), - }) - .collect::>() - .map(Arc::new)? as _ + calculate_binary_math::( + &base, + &exponent, + |b, e| match e.try_into() { + Ok(exp_u32) => b.pow_checked(exp_u32), + Err(_) => Err(ArrowError::ArithmeticOverflow( + format!("Exponent {e} in integer computation is out of bounds.") + )), + }, + )? } - + DataType::Decimal128(_precision, scale) => match exponent.data_type() { + DataType::Int64 => { + calculate_binary_math::( + &base, + &exponent, + |b, e| pow_decimal128_int(b, *scale, e), + )? + } + DataType::Float64 => { + calculate_binary_math::( + &base, + &exponent, + |b, e| pow_decimal128_float(b, *scale, e), + )? + } + other => { + return exec_err!("Unsupported data type {other:?} for exponent") + } + }, + DataType::Decimal256(_precision, scale) => match exponent.data_type() { + DataType::Int64 => { + calculate_binary_math::( + &base, + &exponent, + |b, e| pow_decimal256_int(b, *scale, e), + )? + } + DataType::Float64 => { + calculate_binary_math::( + &base, + &exponent, + |b, e| pow_decimal256_float(b, *scale, e), + )? + } + other => { + return exec_err!("Unsupported data type {other:?} for exponent") + } + }, other => { return exec_err!( "Unsupported data type {other:?} for function {}", @@ -198,9 +329,11 @@ fn is_log(func: &ScalarUDF) -> bool { #[cfg(test)] mod tests { use super::*; - use arrow::array::Float64Array; - use arrow::datatypes::Field; - use datafusion_common::cast::{as_float64_array, as_int64_array}; + use arrow::array::{Decimal128Array, Float64Array, Int64Array}; + use arrow::datatypes::{Field, DECIMAL128_MAX_SCALE, DECIMAL256_MAX_SCALE}; + use datafusion_common::cast::{ + as_decimal128_array, as_decimal256_array, as_float64_array, as_int64_array, + }; use datafusion_common::config::ConfigOptions; #[test] @@ -279,4 +412,248 @@ mod tests { } } } + + #[test] + fn test_power_i128_exp_int() { + let arg_fields = vec![ + Field::new( + "a", + DataType::Decimal128(DECIMAL128_MAX_SCALE as u8, 0), + true, + ) + .into(), + Field::new("a", DataType::Int64, true).into(), + ]; + let args = ScalarFunctionArgs { + args: vec![ + ColumnarValue::Array(Arc::new( + Decimal128Array::from(vec![2, 2, 3, 5, 0, 5]) + .with_precision_and_scale(DECIMAL128_MAX_SCALE as u8, 0) + .unwrap(), + )), // base + ColumnarValue::Array(Arc::new(Int64Array::from(vec![3, 2, 4, 4, 4, 0]))), // exponent + ], + arg_fields, + number_rows: 6, + return_field: Field::new( + "f", + DataType::Decimal128(DECIMAL128_MAX_SCALE as u8, 0), + true, + ) + .into(), + config_options: Arc::new(ConfigOptions::default()), + }; + let result = PowerFunc::new() + .invoke_with_args(args) + .expect("failed to initialize function power"); + + match result { + ColumnarValue::Array(arr) => { + let ints = as_decimal128_array(&arr) + .expect("failed to convert result to an array"); + + assert_eq!(ints.len(), 6); + assert_eq!(ints.value(0), i128::from(8)); + assert_eq!(ints.value(1), i128::from(4)); + assert_eq!(ints.value(2), i128::from(81)); + assert_eq!(ints.value(3), i128::from(625)); + assert_eq!(ints.value(4), i128::from(0)); + assert_eq!(ints.value(5), i128::from(1)); + } + ColumnarValue::Scalar(_) => { + panic!("Expected an array value") + } + } + } + + #[test] + fn test_power_i128_exp_int_scalar() { + let arg_fields = vec![ + Field::new( + "a", + DataType::Decimal128(DECIMAL128_MAX_SCALE as u8, 0), + true, + ) + .into(), + Field::new("a", DataType::Int64, true).into(), + ]; + let args = ScalarFunctionArgs { + args: vec![ + ColumnarValue::Scalar(ScalarValue::Decimal128( + Some(2), + DECIMAL128_MAX_SCALE as u8, + 0, + )), // base + ColumnarValue::Scalar(ScalarValue::Int64(Some(3))), // exponent + ], + arg_fields, + number_rows: 1, + return_field: Field::new( + "f", + DataType::Decimal128(DECIMAL128_MAX_SCALE as u8, 0), + true, + ) + .into(), + config_options: Arc::new(ConfigOptions::default()), + }; + let result = PowerFunc::new() + .invoke_with_args(args) + .expect("failed to initialize function power"); + + match result { + ColumnarValue::Array(arr) => { + let ints = as_decimal128_array(&arr) + .expect("failed to convert result to an array"); + + assert_eq!(ints.len(), 1); + assert_eq!(ints.value(0), i128::from(8)); + } + ColumnarValue::Scalar(_) => { + panic!("Expected an array value") + } + } + } + + #[test] + fn test_power_i128_exp_float() { + let arg_fields = vec![ + Field::new( + "a", + DataType::Decimal128(DECIMAL128_MAX_SCALE as u8, 0), + true, + ) + .into(), + Field::new("a", DataType::Float64, true).into(), + ]; + let args = ScalarFunctionArgs { + args: vec![ + ColumnarValue::Array(Arc::new( + Decimal128Array::from(vec![2, 2, 3, 5, 0, 5]) + .with_precision_and_scale(DECIMAL128_MAX_SCALE as u8, 0) + .unwrap(), + )), // base + ColumnarValue::Array(Arc::new(Float64Array::from(vec![ + 3.0, 2.0, 4.0, 4.0, 4.0, 0.0, + ]))), // exponent + ], + arg_fields, + number_rows: 6, + return_field: Field::new( + "f", + DataType::Decimal128(DECIMAL128_MAX_SCALE as u8, 0), + true, + ) + .into(), + config_options: Arc::new(ConfigOptions::default()), + }; + let result = PowerFunc::new() + .invoke_with_args(args) + .expect("failed to initialize function power"); + + match result { + ColumnarValue::Array(arr) => { + let ints = as_decimal128_array(&arr) + .expect("failed to convert result to an array"); + + assert_eq!(ints.len(), 6); + assert_eq!(ints.value(0), i128::from(8)); + assert_eq!(ints.value(1), i128::from(4)); + assert_eq!(ints.value(2), i128::from(81)); + assert_eq!(ints.value(3), i128::from(625)); + assert_eq!(ints.value(4), i128::from(0)); + assert_eq!(ints.value(5), i128::from(1)); + } + ColumnarValue::Scalar(_) => { + panic!("Expected an array value") + } + } + } + + #[test] + fn test_power_i128_exp_float_fail() { + let bad_exponents = [ + 3.5, + -1.0, + u32::MAX as f64 + 10.0, + f64::INFINITY, + -f64::INFINITY, + f64::NAN, + ]; + for exp in bad_exponents { + let arg_fields = vec![ + Field::new( + "a", + DataType::Decimal128(DECIMAL128_MAX_SCALE as u8, 0), + true, + ) + .into(), + Field::new("a", DataType::Float64, true).into(), + ]; + let args = ScalarFunctionArgs { + args: vec![ + ColumnarValue::Scalar(ScalarValue::Decimal128(Some(2), 38, 0)), // base + ColumnarValue::Scalar(ScalarValue::Float64(Some(exp))), // exponent + ], + arg_fields, + number_rows: 1, + return_field: Field::new( + "f", + DataType::Decimal128(DECIMAL128_MAX_SCALE as u8, 0), + true, + ) + .into(), + config_options: Arc::new(ConfigOptions::default()), + }; + let result = PowerFunc::new().invoke_with_args(args); + assert!(result.is_err()); + } + } + + #[test] + fn test_power_i256_exp_int_scalar() { + let arg_fields = vec![ + Field::new( + "a", + DataType::Decimal256(DECIMAL256_MAX_SCALE as u8, 0), + true, + ) + .into(), + Field::new("a", DataType::Int64, true).into(), + ]; + let args = ScalarFunctionArgs { + args: vec![ + ColumnarValue::Scalar(ScalarValue::Decimal256( + Some(i256::from(2)), + DECIMAL256_MAX_SCALE as u8, + 0, + )), // base + ColumnarValue::Scalar(ScalarValue::Int64(Some(3))), // exponent + ], + arg_fields, + number_rows: 1, + return_field: Field::new( + "f", + DataType::Decimal256(DECIMAL256_MAX_SCALE as u8, 0), + true, + ) + .into(), + config_options: Arc::new(ConfigOptions::default()), + }; + let result = PowerFunc::new() + .invoke_with_args(args) + .expect("failed to initialize function power"); + + match result { + ColumnarValue::Array(arr) => { + let ints = as_decimal256_array(&arr) + .expect("failed to convert result to an array"); + + assert_eq!(ints.len(), 1); + assert_eq!(ints.value(0), i256::from(8)); + } + ColumnarValue::Scalar(_) => { + panic!("Expected an array value") + } + } + } } diff --git a/datafusion/functions/src/utils.rs b/datafusion/functions/src/utils.rs index 932d61e8007c..253774858317 100644 --- a/datafusion/functions/src/utils.rs +++ b/datafusion/functions/src/utils.rs @@ -23,6 +23,7 @@ use datafusion_common::{DataFusionError, Result, ScalarValue}; use datafusion_expr::function::Hint; use datafusion_expr::ColumnarValue; use std::sync::Arc; +use arrow_buffer::i256; /// Creates a function to identify the optimal return type of a string function given /// the type of its first argument. @@ -192,6 +193,24 @@ pub fn decimal128_to_i128(value: i128, scale: i8) -> Result { } } +/// Converts Decimal256 components (value and scale) to an unscaled i256 +pub fn decimal256_to_i256(value: i256, scale: i8) -> Result { + if scale < 0 { + Err(ArrowError::ComputeError( + "Negative scale is not supported".into(), + )) + } else if scale == 0 { + Ok(value) + } else { + match i256::from(10).checked_pow(scale as u32) { + Some(divisor) => Ok(value / divisor), + None => Err(ArrowError::ComputeError(format!( + "Cannot get a power of {scale}" + ))), + } + } +} + #[cfg(test)] pub mod test { /// $FUNC ScalarUDFImpl to test From df48725ddae7e7dd87bcf7eff163368825df96a3 Mon Sep 17 00:00:00 2001 From: theirix Date: Sun, 12 Oct 2025 19:58:34 +0100 Subject: [PATCH 02/10] Format --- datafusion/functions/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datafusion/functions/src/utils.rs b/datafusion/functions/src/utils.rs index 253774858317..4a80f1e5f965 100644 --- a/datafusion/functions/src/utils.rs +++ b/datafusion/functions/src/utils.rs @@ -19,11 +19,11 @@ use arrow::array::{Array, ArrayRef, ArrowPrimitiveType, AsArray, PrimitiveArray} use arrow::compute::try_binary; use arrow::datatypes::DataType; use arrow::error::ArrowError; +use arrow_buffer::i256; use datafusion_common::{DataFusionError, Result, ScalarValue}; use datafusion_expr::function::Hint; use datafusion_expr::ColumnarValue; use std::sync::Arc; -use arrow_buffer::i256; /// Creates a function to identify the optimal return type of a string function given /// the type of its first argument. From 27f8f550010eadd435e5bc3da863656bd440c558 Mon Sep 17 00:00:00 2001 From: theirix Date: Sun, 12 Oct 2025 20:26:16 +0100 Subject: [PATCH 03/10] Format --- datafusion/functions/src/math/power.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/datafusion/functions/src/math/power.rs b/datafusion/functions/src/math/power.rs index f71cfa2adee7..848b421e453d 100644 --- a/datafusion/functions/src/math/power.rs +++ b/datafusion/functions/src/math/power.rs @@ -210,9 +210,9 @@ impl ScalarUDFImpl for PowerFunc { &exponent, |b, e| match e.try_into() { Ok(exp_u32) => b.pow_checked(exp_u32), - Err(_) => Err(ArrowError::ArithmeticOverflow( - format!("Exponent {e} in integer computation is out of bounds.") - )), + Err(_) => Err(ArrowError::ArithmeticOverflow(format!( + "Exponent {e} in integer computation is out of bounds." + ))), }, )? } @@ -222,9 +222,9 @@ impl ScalarUDFImpl for PowerFunc { &exponent, |b, e| match e.try_into() { Ok(exp_u32) => b.pow_checked(exp_u32), - Err(_) => Err(ArrowError::ArithmeticOverflow( - format!("Exponent {e} in integer computation is out of bounds.") - )), + Err(_) => Err(ArrowError::ArithmeticOverflow(format!( + "Exponent {e} in integer computation is out of bounds." + ))), }, )? } From bc0086d437cdd25afc131f7adb99c6bb67439e61 Mon Sep 17 00:00:00 2001 From: theirix Date: Mon, 13 Oct 2025 20:19:35 +0100 Subject: [PATCH 04/10] Add SLT tests for power (one is incorrect) --- .../sqllogictest/test_files/decimal.slt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/datafusion/sqllogictest/test_files/decimal.slt b/datafusion/sqllogictest/test_files/decimal.slt index 502821fcc304..50b18f1631d6 100644 --- a/datafusion/sqllogictest/test_files/decimal.slt +++ b/datafusion/sqllogictest/test_files/decimal.slt @@ -884,6 +884,20 @@ select log(2.0, null); ---- NULL +# power with decimals + +# TODO: Result should be decimal +query RT +SELECT power(2::decimal(12, 0), 4), arrow_typeof(power(2::decimal(12, 0), 4)); +---- +16 Float64 + +query RR +SELECT power(2::decimal(39, 0), 4), power(2::decimal(39, 0), 0); +---- +16 1 + + # Set parse_float_as_decimal to false to test float parsing statement ok set datafusion.sql_parser.parse_float_as_decimal = false; @@ -907,3 +921,16 @@ query R select log(100000000000000000000000000000000000::decimal(38,0)) ---- 34 + +# power with decimals-as-floats + +query R +SELECT power(2::decimal(39, 0), 4); +---- +16 + +# Result is float +query RT +SELECT power(2::decimal(12, 0), 4), arrow_typeof(power(2::decimal(12, 0), 4)); +---- +16 Float64 From 4cedb2855202585b3e323d5483437e590c34c212 Mon Sep 17 00:00:00 2001 From: theirix Date: Sun, 19 Oct 2025 22:17:47 +0100 Subject: [PATCH 05/10] Provide unscaled Decimal from Arrow math operations --- datafusion/functions/src/math/power.rs | 127 +++++++++++++++++-------- 1 file changed, 85 insertions(+), 42 deletions(-) diff --git a/datafusion/functions/src/math/power.rs b/datafusion/functions/src/math/power.rs index 848b421e453d..da7a49edb0f5 100644 --- a/datafusion/functions/src/math/power.rs +++ b/datafusion/functions/src/math/power.rs @@ -22,10 +22,10 @@ use std::sync::Arc; use super::log::LogFunc; use crate::utils::{calculate_binary_math, decimal128_to_i128, decimal256_to_i256}; -use arrow::array::ArrayRef; +use arrow::array::{Array, ArrayRef, PrimitiveArray}; use arrow::datatypes::{ - ArrowNativeTypeOp, DataType, Decimal128Type, Decimal256Type, Float32Type, - Float64Type, Int32Type, Int64Type, + ArrowNativeTypeOp, DataType, Decimal128Type, Decimal256Type, DecimalType, + Float32Type, Float64Type, Int32Type, Int64Type, DECIMAL128_MAX_PRECISION, }; use arrow::error::ArrowError; use arrow_buffer::i256; @@ -148,6 +148,17 @@ fn pow_decimal256_int(base: i256, scale: i8, exp: i64) -> Result(array: &PrimitiveArray) -> Result> +where + T: DecimalType, +{ + let precision = array.precision(); + Ok(array.clone().with_precision_and_scale(precision, 0)?) +} + impl ScalarUDFImpl for PowerFunc { fn as_any(&self) -> &dyn Any { self @@ -228,44 +239,62 @@ impl ScalarUDFImpl for PowerFunc { }, )? } - DataType::Decimal128(_precision, scale) => match exponent.data_type() { - DataType::Int64 => { - calculate_binary_math::( - &base, - &exponent, - |b, e| pow_decimal128_int(b, *scale, e), - )? - } - DataType::Float64 => { - calculate_binary_math::( - &base, - &exponent, - |b, e| pow_decimal128_float(b, *scale, e), - )? - } - other => { - return exec_err!("Unsupported data type {other:?} for exponent") - } - }, - DataType::Decimal256(_precision, scale) => match exponent.data_type() { - DataType::Int64 => { - calculate_binary_math::( - &base, - &exponent, - |b, e| pow_decimal256_int(b, *scale, e), - )? - } - DataType::Float64 => { - calculate_binary_math::( - &base, - &exponent, - |b, e| pow_decimal256_float(b, *scale, e), - )? - } - other => { - return exec_err!("Unsupported data type {other:?} for exponent") - } - }, + DataType::Decimal128(_precision, scale) => { + let array = match exponent.data_type() { + DataType::Int64 => { + calculate_binary_math::< + Decimal128Type, + Int64Type, + Decimal128Type, + _, + >(&base, &exponent, |b, e| { + pow_decimal128_int(b, *scale, e) + })? + } + DataType::Float64 => { + calculate_binary_math::< + Decimal128Type, + Float64Type, + Decimal128Type, + _, + >(&base, &exponent, |b, e| { + pow_decimal128_float(b, *scale, e) + })? + } + other => { + return exec_err!("Unsupported data type {other:?} for exponent") + } + }; + Arc::new(reset_decimal_scale(array.as_ref())?) + } + DataType::Decimal256(_precision, scale) => { + let array = match exponent.data_type() { + DataType::Int64 => { + calculate_binary_math::< + Decimal256Type, + Int64Type, + Decimal256Type, + _, + >(&base, &exponent, |b, e| { + pow_decimal256_int(b, *scale, e) + })? + } + DataType::Float64 => { + calculate_binary_math::< + Decimal256Type, + Float64Type, + Decimal256Type, + _, + >(&base, &exponent, |b, e| { + pow_decimal256_float(b, *scale, e) + })? + } + other => { + return exec_err!("Unsupported data type {other:?} for exponent") + } + }; + Arc::new(reset_decimal_scale(array.as_ref())?) + } other => { return exec_err!( "Unsupported data type {other:?} for function {}", @@ -329,7 +358,7 @@ fn is_log(func: &ScalarUDF) -> bool { #[cfg(test)] mod tests { use super::*; - use arrow::array::{Decimal128Array, Float64Array, Int64Array}; + use arrow::array::{Array, Decimal128Array, Float64Array, Int64Array}; use arrow::datatypes::{Field, DECIMAL128_MAX_SCALE, DECIMAL256_MAX_SCALE}; use datafusion_common::cast::{ as_decimal128_array, as_decimal256_array, as_float64_array, as_int64_array, @@ -507,6 +536,13 @@ mod tests { assert_eq!(ints.len(), 1); assert_eq!(ints.value(0), i128::from(8)); + + // Value is the same as expected, but scale should be 0 + if let DataType::Decimal128(_precision, scale) = arr.data_type() { + assert_eq!(*scale, 0); + } else { + panic!("Expected Decimal256 result") + } } ColumnarValue::Scalar(_) => { panic!("Expected an array value") @@ -650,6 +686,13 @@ mod tests { assert_eq!(ints.len(), 1); assert_eq!(ints.value(0), i256::from(8)); + + // Value is the same as expected, but scale should be 0 + if let DataType::Decimal256(_precision, scale) = arr.data_type() { + assert_eq!(*scale, 0); + } else { + panic!("Expected Decimal256 result") + } } ColumnarValue::Scalar(_) => { panic!("Expected an array value") From 8520b846f7fae77b99e4fe3420d9702421c1b876 Mon Sep 17 00:00:00 2001 From: theirix Date: Sun, 19 Oct 2025 22:36:41 +0100 Subject: [PATCH 06/10] Fixup SLT for power --- .../sqllogictest/test_files/decimal.slt | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/datafusion/sqllogictest/test_files/decimal.slt b/datafusion/sqllogictest/test_files/decimal.slt index 50b18f1631d6..1440d2286e37 100644 --- a/datafusion/sqllogictest/test_files/decimal.slt +++ b/datafusion/sqllogictest/test_files/decimal.slt @@ -886,17 +886,15 @@ NULL # power with decimals -# TODO: Result should be decimal query RT -SELECT power(2::decimal(12, 0), 4), arrow_typeof(power(2::decimal(12, 0), 4)); +SELECT power(2::decimal(38, 0), 4), arrow_typeof(power(2::decimal(38, 0), 4)); ---- -16 Float64 +16 Decimal128(38, 0) -query RR -SELECT power(2::decimal(39, 0), 4), power(2::decimal(39, 0), 0); +query RT +SELECT power(10000000000::decimal(38, 0), 2), arrow_typeof(power(10000000000::decimal(38, 0), 2)); ---- -16 1 - +100000000000000000000 Decimal128(38, 0) # Set parse_float_as_decimal to false to test float parsing statement ok @@ -922,15 +920,14 @@ select log(100000000000000000000000000000000000::decimal(38,0)) ---- 34 -# power with decimals-as-floats - +# Result is decimal since argument is decimal regardless decimals-as-floats parsing query R -SELECT power(2::decimal(39, 0), 4); +SELECT power(10000000000::decimal(38, 0), 2); ---- -16 +100000000000000000000 -# Result is float query RT -SELECT power(2::decimal(12, 0), 4), arrow_typeof(power(2::decimal(12, 0), 4)); +SELECT power(10000000000::decimal(38, 0), 2), + arrow_typeof(power(10000000000::decimal(38, 0), 2)); ---- -16 Float64 +100000000000000000000 Decimal128(38, 0) From 09b5b99c37a1de3ad63705a7944a99813fc622c5 Mon Sep 17 00:00:00 2001 From: theirix Date: Sun, 19 Oct 2025 22:37:20 +0100 Subject: [PATCH 07/10] Add extra signature to return decimal --- datafusion/functions/src/math/power.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/datafusion/functions/src/math/power.rs b/datafusion/functions/src/math/power.rs index da7a49edb0f5..402609f8a449 100644 --- a/datafusion/functions/src/math/power.rs +++ b/datafusion/functions/src/math/power.rs @@ -25,7 +25,7 @@ use crate::utils::{calculate_binary_math, decimal128_to_i128, decimal256_to_i256 use arrow::array::{Array, ArrayRef, PrimitiveArray}; use arrow::datatypes::{ ArrowNativeTypeOp, DataType, Decimal128Type, Decimal256Type, DecimalType, - Float32Type, Float64Type, Int32Type, Int64Type, DECIMAL128_MAX_PRECISION, + Float32Type, Float64Type, Int32Type, Int64Type, }; use arrow::error::ArrowError; use arrow_buffer::i256; @@ -76,6 +76,15 @@ impl PowerFunc { TypeSignature::Exact(vec![Int64, Int64]), TypeSignature::Exact(vec![Float32, Float32]), TypeSignature::Exact(vec![Float64, Float64]), + // Extra signatures for decimals to avoid casting them to floats + TypeSignature::Exact(vec![ + Decimal128(DECIMAL128_MAX_PRECISION, 0), + Int64, + ]), + TypeSignature::Exact(vec![ + Decimal128(DECIMAL128_MAX_PRECISION, 0), + Float64, + ]), Numeric(2), // Catch-all for all decimals ], Volatility::Immutable, From 043798e53ab7b6c437139e28d994ecd3a7e69314 Mon Sep 17 00:00:00 2001 From: theirix Date: Mon, 20 Oct 2025 08:43:09 +0100 Subject: [PATCH 08/10] Add missing import --- datafusion/functions/src/math/power.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datafusion/functions/src/math/power.rs b/datafusion/functions/src/math/power.rs index 402609f8a449..aa83281e1612 100644 --- a/datafusion/functions/src/math/power.rs +++ b/datafusion/functions/src/math/power.rs @@ -25,7 +25,7 @@ use crate::utils::{calculate_binary_math, decimal128_to_i128, decimal256_to_i256 use arrow::array::{Array, ArrayRef, PrimitiveArray}; use arrow::datatypes::{ ArrowNativeTypeOp, DataType, Decimal128Type, Decimal256Type, DecimalType, - Float32Type, Float64Type, Int32Type, Int64Type, + Float32Type, Float64Type, Int32Type, Int64Type, DECIMAL128_MAX_PRECISION, }; use arrow::error::ArrowError; use arrow_buffer::i256; From ed98631aad9ed35b51c5af50197b6c365977f1b6 Mon Sep 17 00:00:00 2001 From: theirix Date: Mon, 20 Oct 2025 21:28:30 +0100 Subject: [PATCH 09/10] Support Decimal32 and Decimal64 with macro --- datafusion/functions/src/math/power.rs | 326 +++++++++++++++++++------ datafusion/functions/src/utils.rs | 66 ++--- 2 files changed, 284 insertions(+), 108 deletions(-) diff --git a/datafusion/functions/src/math/power.rs b/datafusion/functions/src/math/power.rs index aa83281e1612..8daaad267751 100644 --- a/datafusion/functions/src/math/power.rs +++ b/datafusion/functions/src/math/power.rs @@ -21,11 +21,16 @@ use std::sync::Arc; use super::log::LogFunc; -use crate::utils::{calculate_binary_math, decimal128_to_i128, decimal256_to_i256}; -use arrow::array::{Array, ArrayRef, PrimitiveArray}; +use crate::utils::{ + calculate_binary_math, decimal128_to_i128, decimal256_to_i256, decimal32_to_i32, + decimal64_to_i64, reset_decimal_scale, +}; +use arrow::array::{Array, ArrayRef}; use arrow::datatypes::{ - ArrowNativeTypeOp, DataType, Decimal128Type, Decimal256Type, DecimalType, - Float32Type, Float64Type, Int32Type, Int64Type, DECIMAL128_MAX_PRECISION, + ArrowNativeTypeOp, DataType, Decimal128Type, Decimal256Type, Decimal32Type, + Decimal64Type, Float32Type, Float64Type, Int32Type, Int64Type, + DECIMAL128_MAX_PRECISION, DECIMAL256_MAX_PRECISION, DECIMAL32_MAX_PRECISION, + DECIMAL64_MAX_PRECISION, }; use arrow::error::ArrowError; use arrow_buffer::i256; @@ -77,6 +82,22 @@ impl PowerFunc { TypeSignature::Exact(vec![Float32, Float32]), TypeSignature::Exact(vec![Float64, Float64]), // Extra signatures for decimals to avoid casting them to floats + TypeSignature::Exact(vec![ + Decimal32(DECIMAL32_MAX_PRECISION, 0), + Int64, + ]), + TypeSignature::Exact(vec![ + Decimal32(DECIMAL32_MAX_PRECISION, 0), + Float64, + ]), + TypeSignature::Exact(vec![ + Decimal32(DECIMAL64_MAX_PRECISION, 0), + Int64, + ]), + TypeSignature::Exact(vec![ + Decimal32(DECIMAL64_MAX_PRECISION, 0), + Float64, + ]), TypeSignature::Exact(vec![ Decimal128(DECIMAL128_MAX_PRECISION, 0), Int64, @@ -85,6 +106,14 @@ impl PowerFunc { Decimal128(DECIMAL128_MAX_PRECISION, 0), Float64, ]), + TypeSignature::Exact(vec![ + Decimal256(DECIMAL256_MAX_PRECISION, 0), + Int64, + ]), + TypeSignature::Exact(vec![ + Decimal256(DECIMAL256_MAX_PRECISION, 0), + Float64, + ]), Numeric(2), // Catch-all for all decimals ], Volatility::Immutable, @@ -94,79 +123,63 @@ impl PowerFunc { } } -fn pow_decimal128_helper(base: i128, scale: i8, exp: u32) -> Result { - decimal128_to_i128(base, scale)?.pow_checked(exp) -} - -/// Binary function to calculate a math power -/// Returns error if base is invalid -fn pow_decimal128_float(base: i128, scale: i8, exp: f64) -> Result { - if !exp.is_finite() || exp.trunc() != exp { - return Err(ArrowError::ComputeError(format!( - "Cannot use non-integer exp: {exp}" - ))); - } - if exp < 0f64 || exp >= u32::MAX as f64 { - return Err(ArrowError::ArithmeticOverflow(format!( - "Unsupported exp value: {exp}" - ))); - } - - pow_decimal128_helper(base, scale, exp as u32) -} - -/// Binary function to calculate a math power -fn pow_decimal128_int(base: i128, scale: i8, exp: i64) -> Result { - match exp.try_into() { - Ok(exp) => pow_decimal128_helper(base, scale, exp), - Err(_) => Err(ArrowError::ArithmeticOverflow(format!( - "Cannot use non-positive exp: {exp}" - ))), - } -} - -fn pow_decimal256_helper(base: i256, scale: i8, exp: u32) -> Result { - decimal256_to_i256(base, scale)?.pow_checked(exp) -} - -/// Binary function to calculate a math power -/// Returns error if base is invalid -fn pow_decimal256_float(base: i256, scale: i8, exp: f64) -> Result { - if !exp.is_finite() || exp.trunc() != exp { - return Err(ArrowError::ComputeError(format!( - "Cannot use non-integer exp: {exp}" - ))); - } - if exp < 0f64 || exp >= u32::MAX as f64 { - return Err(ArrowError::ArithmeticOverflow(format!( - "Unsupported exp value: {exp}" - ))); - } +macro_rules! make_pow_fn { + ($t:ty, $name:ident, $name_int:ident, $name_float:ident) => { + /// Binary function to calculate a math power to integer exponent + /// Returns error if base is invalid + fn $name_int(base: $t, scale: i8, exp: i64) -> Result<$t, ArrowError> { + match exp.try_into() { + Ok(exp) => $name(base, scale)?.pow_checked(exp), + Err(_) => Err(ArrowError::ArithmeticOverflow(format!( + "Unsupported exp value: {exp}" + ))), + } + } - pow_decimal256_helper(base, scale, exp as u32) -} + /// Binary function to calculate a math power to float exponent + /// Returns error if base is negative or non-integer + fn $name_float(base: $t, scale: i8, exp: f64) -> Result<$t, ArrowError> { + if !exp.is_finite() || exp.trunc() != exp { + return Err(ArrowError::ComputeError(format!( + "Cannot use non-integer exp: {exp}" + ))); + } + if exp < 0f64 || exp >= u32::MAX as f64 { + return Err(ArrowError::ArithmeticOverflow(format!( + "Unsupported exp value: {exp}" + ))); + } -/// Binary function to calculate a math power -/// Returns error if base is invalid -fn pow_decimal256_int(base: i256, scale: i8, exp: i64) -> Result { - match exp.try_into() { - Ok(exp) => pow_decimal256_helper(base, scale, exp), - Err(_) => Err(ArrowError::ArithmeticOverflow(format!( - "Unsupported exp value: {exp}" - ))), - } + $name(base, scale)?.pow_checked(exp as u32) + } + }; } -/// Change scale of decimal array to 0 instead of default scale 10 -/// It is required to reset scale for decimals. automatically constructed from -/// Apache Arrow operations on i128/i256 type -fn reset_decimal_scale(array: &PrimitiveArray) -> Result> -where - T: DecimalType, -{ - let precision = array.precision(); - Ok(array.clone().with_precision_and_scale(precision, 0)?) -} +// Generate functions for numeric types +make_pow_fn!( + i32, + decimal32_to_i32, + pow_decimal32_int, + pow_decimal32_float +); +make_pow_fn!( + i64, + decimal64_to_i64, + pow_decimal64_int, + pow_decimal64_float +); +make_pow_fn!( + i128, + decimal128_to_i128, + pow_decimal128_int, + pow_decimal128_float +); +make_pow_fn!( + i256, + decimal256_to_i256, + pow_decimal256_int, + pow_decimal256_float +); impl ScalarUDFImpl for PowerFunc { fn as_any(&self) -> &dyn Any { @@ -186,6 +199,12 @@ impl ScalarUDFImpl for PowerFunc { DataType::Int64 => Ok(DataType::Int64), DataType::Float32 => Ok(DataType::Float32), DataType::Float64 => Ok(DataType::Float64), + DataType::Decimal32(precision, scale) => { + Ok(DataType::Decimal32(precision, scale)) + } + DataType::Decimal64(precision, scale) => { + Ok(DataType::Decimal64(precision, scale)) + } DataType::Decimal128(precision, scale) => { Ok(DataType::Decimal128(precision, scale)) } @@ -248,6 +267,56 @@ impl ScalarUDFImpl for PowerFunc { }, )? } + DataType::Decimal32(_precision, scale) => { + let array = match exponent.data_type() { + DataType::Int64 => { + calculate_binary_math::( + &base, + &exponent, + |b, e| pow_decimal32_int(b, *scale, e), + )? + } + DataType::Float64 => { + calculate_binary_math::< + Decimal32Type, + Float64Type, + Decimal32Type, + _, + >(&base, &exponent, |b, e| { + pow_decimal32_float(b, *scale, e) + })? + } + other => { + return exec_err!("Unsupported data type {other:?} for exponent") + } + }; + Arc::new(reset_decimal_scale(array.as_ref())?) + } + DataType::Decimal64(_precision, scale) => { + let array = match exponent.data_type() { + DataType::Int64 => { + calculate_binary_math::( + &base, + &exponent, + |b, e| pow_decimal64_int(b, *scale, e), + )? + } + DataType::Float64 => { + calculate_binary_math::< + Decimal64Type, + Float64Type, + Decimal64Type, + _, + >(&base, &exponent, |b, e| { + pow_decimal64_float(b, *scale, e) + })? + } + other => { + return exec_err!("Unsupported data type {other:?} for exponent") + } + }; + Arc::new(reset_decimal_scale(array.as_ref())?) + } DataType::Decimal128(_precision, scale) => { let array = match exponent.data_type() { DataType::Int64 => { @@ -311,7 +380,6 @@ impl ScalarUDFImpl for PowerFunc { ) } }; - Ok(ColumnarValue::Array(arr)) } @@ -368,9 +436,13 @@ fn is_log(func: &ScalarUDF) -> bool { mod tests { use super::*; use arrow::array::{Array, Decimal128Array, Float64Array, Int64Array}; - use arrow::datatypes::{Field, DECIMAL128_MAX_SCALE, DECIMAL256_MAX_SCALE}; + use arrow::datatypes::{ + Field, DECIMAL128_MAX_SCALE, DECIMAL256_MAX_SCALE, DECIMAL32_MAX_SCALE, + DECIMAL64_MAX_SCALE, + }; use datafusion_common::cast::{ - as_decimal128_array, as_decimal256_array, as_float64_array, as_int64_array, + as_decimal128_array, as_decimal256_array, as_decimal32_array, as_decimal64_array, + as_float64_array, as_int64_array, }; use datafusion_common::config::ConfigOptions; @@ -708,4 +780,106 @@ mod tests { } } } + + #[test] + fn test_power_i32_exp_int_scalar() { + let arg_fields = vec![ + Field::new("a", DataType::Decimal32(DECIMAL32_MAX_SCALE as u8, 0), true) + .into(), + Field::new("a", DataType::Int64, true).into(), + ]; + let args = ScalarFunctionArgs { + args: vec![ + ColumnarValue::Scalar(ScalarValue::Decimal32( + Some(i32::from(2)), + DECIMAL32_MAX_SCALE as u8, + 0, + )), // base + ColumnarValue::Scalar(ScalarValue::Int64(Some(3))), // exponent + ], + arg_fields, + number_rows: 1, + return_field: Field::new( + "f", + DataType::Decimal32(DECIMAL32_MAX_SCALE as u8, 0), + true, + ) + .into(), + config_options: Arc::new(ConfigOptions::default()), + }; + let result = PowerFunc::new() + .invoke_with_args(args) + .expect("failed to initialize function power"); + + match result { + ColumnarValue::Array(arr) => { + let ints = as_decimal32_array(&arr) + .expect("failed to convert result to an array"); + + assert_eq!(ints.len(), 1); + assert_eq!(ints.value(0), i32::from(8)); + + // Value is the same as expected, but scale should be 0 + if let DataType::Decimal32(_precision, scale) = arr.data_type() { + assert_eq!(*scale, 0); + } else { + panic!("Expected Decimal32 result") + } + } + ColumnarValue::Scalar(_) => { + panic!("Expected an array value") + } + } + } + + #[test] + fn test_power_i64_exp_int_scalar() { + let arg_fields = vec![ + Field::new("a", DataType::Decimal64(DECIMAL64_MAX_SCALE as u8, 0), true) + .into(), + Field::new("a", DataType::Int64, true).into(), + ]; + let args = ScalarFunctionArgs { + args: vec![ + ColumnarValue::Scalar(ScalarValue::Decimal64( + Some(i64::from(2)), + DECIMAL64_MAX_SCALE as u8, + 0, + )), // base + ColumnarValue::Scalar(ScalarValue::Int64(Some(3))), // exponent + ], + arg_fields, + number_rows: 1, + return_field: Field::new( + "f", + DataType::Decimal64(DECIMAL64_MAX_SCALE as u8, 0), + true, + ) + .into(), + config_options: Arc::new(ConfigOptions::default()), + }; + let result = PowerFunc::new() + .invoke_with_args(args) + .expect("failed to initialize function power"); + + match result { + ColumnarValue::Array(arr) => { + let ints = as_decimal64_array(&arr) + .expect("failed to convert result to an array"); + + assert_eq!(ints.len(), 1); + assert_eq!(ints.value(0), i64::from(8)); + + // Value is the same as expected, but scale should be 0 + if let DataType::Decimal64(_precision, scale) = arr.data_type() { + assert_eq!(*scale, 0); + } else { + panic!("Expected Decimal64 result") + } + } + ColumnarValue::Scalar(_) => { + panic!("Expected an array value") + } + } + } } diff --git a/datafusion/functions/src/utils.rs b/datafusion/functions/src/utils.rs index 4a80f1e5f965..dda5f3544014 100644 --- a/datafusion/functions/src/utils.rs +++ b/datafusion/functions/src/utils.rs @@ -17,7 +17,7 @@ use arrow::array::{Array, ArrayRef, ArrowPrimitiveType, AsArray, PrimitiveArray}; use arrow::compute::try_binary; -use arrow::datatypes::DataType; +use arrow::datatypes::{DataType, DecimalType}; use arrow::error::ArrowError; use arrow_buffer::i256; use datafusion_common::{DataFusionError, Result, ScalarValue}; @@ -175,40 +175,42 @@ where }) } -/// Converts Decimal128 components (value and scale) to an unscaled i128 -pub fn decimal128_to_i128(value: i128, scale: i8) -> Result { - if scale < 0 { - Err(ArrowError::ComputeError( - "Negative scale is not supported".into(), - )) - } else if scale == 0 { - Ok(value) - } else { - match i128::from(10).checked_pow(scale as u32) { - Some(divisor) => Ok(value / divisor), - None => Err(ArrowError::ComputeError(format!( - "Cannot get a power of {scale}" - ))), +macro_rules! make_decimal_unscale_fn { + ($t:ty, $name:ident) => { + /// Converts Decimal components (value and scale) to an unscaled underlying integer value + pub fn $name(value: $t, scale: i8) -> Result<$t, ArrowError> { + if scale < 0 { + Err(ArrowError::ComputeError( + "Negative scale is not supported".into(), + )) + } else if scale == 0 { + Ok(value) + } else { + match <$t>::from(10).checked_pow(scale as u32) { + Some(divisor) => Ok(value / divisor), + None => Err(ArrowError::ComputeError(format!( + "Cannot get a power of {scale}" + ))), + } + } } - } + }; } -/// Converts Decimal256 components (value and scale) to an unscaled i256 -pub fn decimal256_to_i256(value: i256, scale: i8) -> Result { - if scale < 0 { - Err(ArrowError::ComputeError( - "Negative scale is not supported".into(), - )) - } else if scale == 0 { - Ok(value) - } else { - match i256::from(10).checked_pow(scale as u32) { - Some(divisor) => Ok(value / divisor), - None => Err(ArrowError::ComputeError(format!( - "Cannot get a power of {scale}" - ))), - } - } +make_decimal_unscale_fn!(i32, decimal32_to_i32); +make_decimal_unscale_fn!(i64, decimal64_to_i64); +make_decimal_unscale_fn!(i128, decimal128_to_i128); +make_decimal_unscale_fn!(i256, decimal256_to_i256); + +/// Change scale of decimal array to 0 instead of default scale 10 +/// It is required to reset scale for decimals. automatically constructed from +/// Apache Arrow operations on underlying integer type +pub fn reset_decimal_scale(array: &PrimitiveArray) -> Result> +where + T: DecimalType, +{ + let precision = array.precision(); + Ok(array.clone().with_precision_and_scale(precision, 0)?) } #[cfg(test)] From 791635fd845be0e32d5833d78e77d6df77feb013 Mon Sep 17 00:00:00 2001 From: theirix Date: Mon, 20 Oct 2025 22:16:40 +0100 Subject: [PATCH 10/10] Fix clippy lints --- datafusion/functions/src/math/power.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datafusion/functions/src/math/power.rs b/datafusion/functions/src/math/power.rs index 8daaad267751..074b3f5f21f2 100644 --- a/datafusion/functions/src/math/power.rs +++ b/datafusion/functions/src/math/power.rs @@ -791,7 +791,7 @@ mod tests { let args = ScalarFunctionArgs { args: vec![ ColumnarValue::Scalar(ScalarValue::Decimal32( - Some(i32::from(2)), + Some(2), DECIMAL32_MAX_SCALE as u8, 0, )), // base @@ -817,7 +817,7 @@ mod tests { .expect("failed to convert result to an array"); assert_eq!(ints.len(), 1); - assert_eq!(ints.value(0), i32::from(8)); + assert_eq!(ints.value(0), 8); // Value is the same as expected, but scale should be 0 if let DataType::Decimal32(_precision, scale) = arr.data_type() {