| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| //! Math function: `log()`. |
| |
| use std::any::Any; |
| |
| use super::power::PowerFunc; |
| |
| use crate::utils::{calculate_binary_math, decimal128_to_i128}; |
| use arrow::array::{Array, ArrayRef}; |
| use arrow::compute::kernels::cast; |
| use arrow::datatypes::{ |
| DataType, Decimal128Type, Decimal256Type, Float16Type, Float32Type, Float64Type, |
| }; |
| use arrow::error::ArrowError; |
| use arrow_buffer::i256; |
| use datafusion_common::types::NativeType; |
| use datafusion_common::{ |
| exec_err, internal_err, plan_datafusion_err, plan_err, Result, ScalarValue, |
| }; |
| use datafusion_expr::expr::ScalarFunction; |
| use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyInfo}; |
| use datafusion_expr::sort_properties::{ExprProperties, SortProperties}; |
| use datafusion_expr::{ |
| lit, Coercion, ColumnarValue, Documentation, Expr, ScalarFunctionArgs, ScalarUDF, |
| TypeSignature, TypeSignatureClass, |
| }; |
| use datafusion_expr::{ScalarUDFImpl, Signature, Volatility}; |
| use datafusion_macros::user_doc; |
| use num_traits::Float; |
| |
| #[user_doc( |
| doc_section(label = "Math Functions"), |
| description = "Returns the base-x logarithm of a number. Can either provide a specified base, or if omitted then takes the base-10 of a number.", |
| syntax_example = r#"log(base, numeric_expression) |
| log(numeric_expression)"#, |
| sql_example = r#"```sql |
| > SELECT log(10); |
| +---------+ |
| | log(10) | |
| +---------+ |
| | 1.0 | |
| +---------+ |
| ```"#, |
| standard_argument(name = "base", prefix = "Base numeric"), |
| standard_argument(name = "numeric_expression", prefix = "Numeric") |
| )] |
| #[derive(Debug, PartialEq, Eq, Hash)] |
| pub struct LogFunc { |
| signature: Signature, |
| } |
| |
| impl Default for LogFunc { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| impl LogFunc { |
| pub fn new() -> Self { |
| // Converts decimals & integers to float64, accepting other floats as is |
| let as_float = Coercion::new_implicit( |
| TypeSignatureClass::Float, |
| vec![TypeSignatureClass::Numeric], |
| NativeType::Float64, |
| ); |
| Self { |
| signature: Signature::one_of( |
| // Ensure decimals have precedence over floats since we have |
| // a native decimal implementation for log |
| vec![ |
| // log(value) |
| TypeSignature::Coercible(vec![Coercion::new_exact( |
| TypeSignatureClass::Decimal, |
| )]), |
| TypeSignature::Coercible(vec![as_float.clone()]), |
| // log(base, value) |
| TypeSignature::Coercible(vec![ |
| as_float.clone(), |
| Coercion::new_exact(TypeSignatureClass::Decimal), |
| ]), |
| TypeSignature::Coercible(vec![as_float.clone(), as_float.clone()]), |
| ], |
| Volatility::Immutable, |
| ), |
| } |
| } |
| } |
| |
| /// Binary function to calculate an integer logarithm of Decimal128 `value` using `base` base |
| /// Returns error if base is invalid |
| fn log_decimal128(value: i128, scale: i8, base: f64) -> Result<f64, ArrowError> { |
| if !base.is_finite() || base.trunc() != base { |
| return Err(ArrowError::ComputeError(format!( |
| "Log cannot use non-integer base: {base}" |
| ))); |
| } |
| if (base as u32) < 2 { |
| return Err(ArrowError::ComputeError(format!( |
| "Log base must be greater than 1: {base}" |
| ))); |
| } |
| |
| let unscaled_value = decimal128_to_i128(value, scale)?; |
| if unscaled_value > 0 { |
| let log_value: u32 = unscaled_value.ilog(base as i128); |
| Ok(log_value as f64) |
| } else { |
| // Reflect f64::log behaviour |
| Ok(f64::NAN) |
| } |
| } |
| |
| /// Binary function to calculate an integer logarithm of Decimal128 `value` using `base` base |
| /// Returns error if base is invalid or if value is out of bounds of Decimal128 |
| fn log_decimal256(value: i256, scale: i8, base: f64) -> Result<f64, ArrowError> { |
| match value.to_i128() { |
| Some(value) => log_decimal128(value, scale, base), |
| None => Err(ArrowError::NotYetImplemented(format!( |
| "Log of Decimal256 larger than Decimal128 is not yet supported: {value}" |
| ))), |
| } |
| } |
| |
| impl ScalarUDFImpl for LogFunc { |
| fn as_any(&self) -> &dyn Any { |
| self |
| } |
| fn name(&self) -> &str { |
| "log" |
| } |
| |
| fn signature(&self) -> &Signature { |
| &self.signature |
| } |
| |
| fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> { |
| // Check last argument (value) |
| match &arg_types.last().ok_or(plan_datafusion_err!("No args"))? { |
| DataType::Float16 => Ok(DataType::Float16), |
| DataType::Float32 => Ok(DataType::Float32), |
| _ => Ok(DataType::Float64), |
| } |
| } |
| |
| fn output_ordering(&self, input: &[ExprProperties]) -> Result<SortProperties> { |
| let (base_sort_properties, num_sort_properties) = if input.len() == 1 { |
| // log(x) defaults to log(10, x) |
| (SortProperties::Singleton, input[0].sort_properties) |
| } else { |
| (input[0].sort_properties, input[1].sort_properties) |
| }; |
| match (num_sort_properties, base_sort_properties) { |
| (first @ SortProperties::Ordered(num), SortProperties::Ordered(base)) |
| if num.descending != base.descending |
| && num.nulls_first == base.nulls_first => |
| { |
| Ok(first) |
| } |
| ( |
| first @ (SortProperties::Ordered(_) | SortProperties::Singleton), |
| SortProperties::Singleton, |
| ) => Ok(first), |
| (SortProperties::Singleton, second @ SortProperties::Ordered(_)) => { |
| Ok(-second) |
| } |
| _ => Ok(SortProperties::Unordered), |
| } |
| } |
| |
| // Support overloaded log(base, x) and log(x) which defaults to log(10, x) |
| fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> { |
| if args.arg_fields.iter().any(|a| a.data_type().is_null()) { |
| return ColumnarValue::Scalar(ScalarValue::Null) |
| .cast_to(args.return_type(), None); |
| } |
| |
| let (base, value) = if args.args.len() == 2 { |
| (args.args[0].clone(), &args.args[1]) |
| } else { |
| // no base specified, default to 10 |
| ( |
| ColumnarValue::Scalar(ScalarValue::new_ten(args.return_type())?), |
| &args.args[0], |
| ) |
| }; |
| let value = value.to_array(args.number_rows)?; |
| |
| let output: ArrayRef = match value.data_type() { |
| DataType::Float16 => { |
| calculate_binary_math::<Float16Type, Float16Type, Float16Type, _>( |
| &value, |
| &base, |
| |value, base| Ok(value.log(base)), |
| )? |
| } |
| DataType::Float32 => { |
| calculate_binary_math::<Float32Type, Float32Type, Float32Type, _>( |
| &value, |
| &base, |
| |value, base| Ok(value.log(base)), |
| )? |
| } |
| DataType::Float64 => { |
| calculate_binary_math::<Float64Type, Float64Type, Float64Type, _>( |
| &value, |
| &base, |
| |value, base| Ok(value.log(base)), |
| )? |
| } |
| // TODO: native log support for decimal 32 & 64; right now upcast |
| // to decimal128 to calculate |
| // https://github.com/apache/datafusion/issues/17555 |
| DataType::Decimal32(precision, scale) |
| | DataType::Decimal64(precision, scale) => { |
| calculate_binary_math::<Decimal128Type, Float64Type, Float64Type, _>( |
| &cast(&value, &DataType::Decimal128(*precision, *scale))?, |
| &base, |
| |value, base| log_decimal128(value, *scale, base), |
| )? |
| } |
| DataType::Decimal128(_, scale) => { |
| calculate_binary_math::<Decimal128Type, Float64Type, Float64Type, _>( |
| &value, |
| &base, |
| |value, base| log_decimal128(value, *scale, base), |
| )? |
| } |
| DataType::Decimal256(_, scale) => { |
| calculate_binary_math::<Decimal256Type, Float64Type, Float64Type, _>( |
| &value, |
| &base, |
| |value, base| log_decimal256(value, *scale, base), |
| )? |
| } |
| other => { |
| return exec_err!("Unsupported data type {other:?} for function log") |
| } |
| }; |
| |
| Ok(ColumnarValue::Array(output)) |
| } |
| |
| fn documentation(&self) -> Option<&Documentation> { |
| self.doc() |
| } |
| |
| /// Simplify the `log` function by the relevant rules: |
| /// 1. Log(a, 1) ===> 0 |
| /// 2. Log(a, Power(a, b)) ===> b |
| /// 3. Log(a, a) ===> 1 |
| fn simplify( |
| &self, |
| mut args: Vec<Expr>, |
| info: &dyn SimplifyInfo, |
| ) -> Result<ExprSimplifyResult> { |
| let mut arg_types = args |
| .iter() |
| .map(|arg| info.get_data_type(arg)) |
| .collect::<Result<Vec<_>>>()?; |
| let return_type = self.return_type(&arg_types)?; |
| |
| // Null propagation |
| if arg_types.iter().any(|dt| dt.is_null()) { |
| return Ok(ExprSimplifyResult::Simplified(lit( |
| ScalarValue::Null.cast_to(&return_type)? |
| ))); |
| } |
| |
| // Args are either |
| // log(number) |
| // log(base, number) |
| let num_args = args.len(); |
| if num_args != 1 && num_args != 2 { |
| return plan_err!("Expected log to have 1 or 2 arguments, got {num_args}"); |
| } |
| let number = args.pop().unwrap(); |
| let number_datatype = arg_types.pop().unwrap(); |
| // default to base 10 |
| let base = if let Some(base) = args.pop() { |
| base |
| } else { |
| lit(ScalarValue::new_ten(&number_datatype)?) |
| }; |
| |
| match number { |
| Expr::Literal(value, _) |
| if value == ScalarValue::new_one(&number_datatype)? => |
| { |
| Ok(ExprSimplifyResult::Simplified(lit(ScalarValue::new_zero( |
| &info.get_data_type(&base)?, |
| )?))) |
| } |
| Expr::ScalarFunction(ScalarFunction { func, mut args }) |
| if is_pow(&func) && args.len() == 2 && base == args[0] => |
| { |
| let b = args.pop().unwrap(); // length checked above |
| Ok(ExprSimplifyResult::Simplified(b)) |
| } |
| number => { |
| if number == base { |
| Ok(ExprSimplifyResult::Simplified(lit(ScalarValue::new_one( |
| &number_datatype, |
| )?))) |
| } else { |
| let args = match num_args { |
| 1 => vec![number], |
| 2 => vec![base, number], |
| _ => { |
| return internal_err!( |
| "Unexpected number of arguments in log::simplify" |
| ) |
| } |
| }; |
| Ok(ExprSimplifyResult::Original(args)) |
| } |
| } |
| } |
| } |
| } |
| |
| /// Returns true if the function is `PowerFunc` |
| fn is_pow(func: &ScalarUDF) -> bool { |
| func.inner().as_any().downcast_ref::<PowerFunc>().is_some() |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use std::collections::HashMap; |
| use std::sync::Arc; |
| |
| use super::*; |
| |
| use arrow::array::{ |
| Date32Array, Decimal128Array, Decimal256Array, Float32Array, Float64Array, |
| }; |
| use arrow::compute::SortOptions; |
| use arrow::datatypes::{Field, DECIMAL256_MAX_PRECISION}; |
| use datafusion_common::cast::{as_float32_array, as_float64_array}; |
| use datafusion_common::config::ConfigOptions; |
| use datafusion_common::DFSchema; |
| use datafusion_expr::execution_props::ExecutionProps; |
| use datafusion_expr::simplify::SimplifyContext; |
| |
| #[test] |
| fn test_log_decimal_native() { |
| let value = 10_i128.pow(35); |
| assert_eq!((value as f64).log2(), 116.26748332105768); |
| assert_eq!( |
| log_decimal128(value, 0, 2.0).unwrap(), |
| // TODO: see we're losing our decimal points compared to above |
| // https://github.com/apache/datafusion/issues/18524 |
| 116.0 |
| ); |
| } |
| |
| #[test] |
| fn test_log_invalid_base_type() { |
| let arg_fields = vec![ |
| Field::new("b", DataType::Date32, false).into(), |
| Field::new("n", DataType::Float64, false).into(), |
| ]; |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Array(Arc::new(Date32Array::from(vec![5, 10, 15, 20]))), // base |
| ColumnarValue::Array(Arc::new(Float64Array::from(vec![ |
| 10.0, 100.0, 1000.0, 10000.0, |
| ]))), // num |
| ], |
| arg_fields, |
| number_rows: 4, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new().invoke_with_args(args); |
| assert!(result.is_err()); |
| assert_eq!( |
| result.unwrap_err().to_string().lines().next().unwrap(), |
| "Arrow error: Cast error: Casting from Date32 to Float64 not supported" |
| ); |
| } |
| |
| #[test] |
| fn test_log_invalid_value() { |
| let arg_field = Field::new("a", DataType::Date32, false).into(); |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Array(Arc::new(Date32Array::from(vec![10]))), // num |
| ], |
| arg_fields: vec![arg_field], |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| |
| let result = LogFunc::new().invoke_with_args(args); |
| result.expect_err("expected error"); |
| } |
| |
| #[test] |
| fn test_log_scalar_f32_unary() { |
| let arg_field = Field::new("a", DataType::Float32, false).into(); |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Scalar(ScalarValue::Float32(Some(10.0))), // num |
| ], |
| arg_fields: vec![arg_field], |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Float32, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float32_array(&arr) |
| .expect("failed to convert result to a Float32Array"); |
| |
| assert_eq!(floats.len(), 1); |
| assert!((floats.value(0) - 1.0).abs() < 1e-10); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_scalar_f64_unary() { |
| let arg_field = Field::new("a", DataType::Float64, false).into(); |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Scalar(ScalarValue::Float64(Some(10.0))), // num |
| ], |
| arg_fields: vec![arg_field], |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float64_array(&arr) |
| .expect("failed to convert result to a Float64Array"); |
| |
| assert_eq!(floats.len(), 1); |
| assert!((floats.value(0) - 1.0).abs() < 1e-10); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_scalar_f32() { |
| let arg_fields = vec![ |
| Field::new("a", DataType::Float32, false).into(), |
| Field::new("a", DataType::Float32, false).into(), |
| ]; |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Scalar(ScalarValue::Float32(Some(2.0))), // base |
| ColumnarValue::Scalar(ScalarValue::Float32(Some(32.0))), // num |
| ], |
| arg_fields, |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Float32, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float32_array(&arr) |
| .expect("failed to convert result to a Float32Array"); |
| |
| assert_eq!(floats.len(), 1); |
| assert!((floats.value(0) - 5.0).abs() < 1e-10); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_scalar_f64() { |
| let arg_fields = vec![ |
| Field::new("a", DataType::Float64, false).into(), |
| Field::new("a", DataType::Float64, false).into(), |
| ]; |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Scalar(ScalarValue::Float64(Some(2.0))), // base |
| ColumnarValue::Scalar(ScalarValue::Float64(Some(64.0))), // num |
| ], |
| arg_fields, |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float64_array(&arr) |
| .expect("failed to convert result to a Float64Array"); |
| |
| assert_eq!(floats.len(), 1); |
| assert!((floats.value(0) - 6.0).abs() < 1e-10); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_f64_unary() { |
| let arg_field = Field::new("a", DataType::Float64, false).into(); |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Array(Arc::new(Float64Array::from(vec![ |
| 10.0, 100.0, 1000.0, 10000.0, |
| ]))), // num |
| ], |
| arg_fields: vec![arg_field], |
| number_rows: 4, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float64_array(&arr) |
| .expect("failed to convert result to a Float64Array"); |
| |
| assert_eq!(floats.len(), 4); |
| assert!((floats.value(0) - 1.0).abs() < 1e-10); |
| assert!((floats.value(1) - 2.0).abs() < 1e-10); |
| assert!((floats.value(2) - 3.0).abs() < 1e-10); |
| assert!((floats.value(3) - 4.0).abs() < 1e-10); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_f32_unary() { |
| let arg_field = Field::new("a", DataType::Float32, false).into(); |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Array(Arc::new(Float32Array::from(vec![ |
| 10.0, 100.0, 1000.0, 10000.0, |
| ]))), // num |
| ], |
| arg_fields: vec![arg_field], |
| number_rows: 4, |
| return_field: Field::new("f", DataType::Float32, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float32_array(&arr) |
| .expect("failed to convert result to a Float64Array"); |
| |
| assert_eq!(floats.len(), 4); |
| assert!((floats.value(0) - 1.0).abs() < 1e-10); |
| assert!((floats.value(1) - 2.0).abs() < 1e-10); |
| assert!((floats.value(2) - 3.0).abs() < 1e-10); |
| assert!((floats.value(3) - 4.0).abs() < 1e-10); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_f64() { |
| let arg_fields = vec![ |
| Field::new("a", DataType::Float64, false).into(), |
| Field::new("a", DataType::Float64, false).into(), |
| ]; |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Array(Arc::new(Float64Array::from(vec![ |
| 2.0, 2.0, 3.0, 5.0, 5.0, |
| ]))), // base |
| ColumnarValue::Array(Arc::new(Float64Array::from(vec![ |
| 8.0, 4.0, 81.0, 625.0, -123.0, |
| ]))), // num |
| ], |
| arg_fields, |
| number_rows: 5, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float64_array(&arr) |
| .expect("failed to convert result to a Float64Array"); |
| |
| assert_eq!(floats.len(), 5); |
| assert!((floats.value(0) - 3.0).abs() < 1e-10); |
| assert!((floats.value(1) - 2.0).abs() < 1e-10); |
| assert!((floats.value(2) - 4.0).abs() < 1e-10); |
| assert!((floats.value(3) - 4.0).abs() < 1e-10); |
| assert!(floats.value(4).is_nan()); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_f32() { |
| let arg_fields = vec![ |
| Field::new("a", DataType::Float32, false).into(), |
| Field::new("a", DataType::Float32, false).into(), |
| ]; |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Array(Arc::new(Float32Array::from(vec![ |
| 2.0, 2.0, 3.0, 5.0, |
| ]))), // base |
| ColumnarValue::Array(Arc::new(Float32Array::from(vec![ |
| 8.0, 4.0, 81.0, 625.0, |
| ]))), // num |
| ], |
| arg_fields, |
| number_rows: 4, |
| return_field: Field::new("f", DataType::Float32, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float32_array(&arr) |
| .expect("failed to convert result to a Float32Array"); |
| |
| assert_eq!(floats.len(), 4); |
| assert!((floats.value(0) - 3.0).abs() < f32::EPSILON); |
| assert!((floats.value(1) - 2.0).abs() < f32::EPSILON); |
| assert!((floats.value(2) - 4.0).abs() < f32::EPSILON); |
| assert!((floats.value(3) - 4.0).abs() < f32::EPSILON); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| #[test] |
| // Test log() simplification errors |
| fn test_log_simplify_errors() { |
| let props = ExecutionProps::new(); |
| let schema = |
| Arc::new(DFSchema::new_with_metadata(vec![], HashMap::new()).unwrap()); |
| let context = SimplifyContext::new(&props).with_schema(schema); |
| // Expect 0 args to error |
| let _ = LogFunc::new().simplify(vec![], &context).unwrap_err(); |
| // Expect 3 args to error |
| let _ = LogFunc::new() |
| .simplify(vec![lit(1), lit(2), lit(3)], &context) |
| .unwrap_err(); |
| } |
| |
| #[test] |
| // Test that non-simplifiable log() expressions are unchanged after simplification |
| fn test_log_simplify_original() { |
| let props = ExecutionProps::new(); |
| let schema = |
| Arc::new(DFSchema::new_with_metadata(vec![], HashMap::new()).unwrap()); |
| let context = SimplifyContext::new(&props).with_schema(schema); |
| // One argument with no simplifications |
| let result = LogFunc::new().simplify(vec![lit(2)], &context).unwrap(); |
| let ExprSimplifyResult::Original(args) = result else { |
| panic!("Expected ExprSimplifyResult::Original") |
| }; |
| assert_eq!(args.len(), 1); |
| assert_eq!(args[0], lit(2)); |
| // Two arguments with no simplifications |
| let result = LogFunc::new() |
| .simplify(vec![lit(2), lit(3)], &context) |
| .unwrap(); |
| let ExprSimplifyResult::Original(args) = result else { |
| panic!("Expected ExprSimplifyResult::Original") |
| }; |
| assert_eq!(args.len(), 2); |
| assert_eq!(args[0], lit(2)); |
| assert_eq!(args[1], lit(3)); |
| } |
| |
| #[test] |
| fn test_log_output_ordering() { |
| // [Unordered, Ascending, Descending, Literal] |
| let orders = [ |
| ExprProperties::new_unknown(), |
| ExprProperties::new_unknown().with_order(SortProperties::Ordered( |
| SortOptions { |
| descending: false, |
| nulls_first: true, |
| }, |
| )), |
| ExprProperties::new_unknown().with_order(SortProperties::Ordered( |
| SortOptions { |
| descending: true, |
| nulls_first: true, |
| }, |
| )), |
| ExprProperties::new_unknown().with_order(SortProperties::Singleton), |
| ]; |
| |
| let log = LogFunc::new(); |
| |
| // Test log(num) |
| for order in orders.iter().cloned() { |
| let result = log.output_ordering(std::slice::from_ref(&order)).unwrap(); |
| assert_eq!(result, order.sort_properties); |
| } |
| |
| // Test log(base, num), where `nulls_first` is the same |
| let mut results = Vec::with_capacity(orders.len() * orders.len()); |
| for base_order in orders.iter() { |
| for num_order in orders.iter().cloned() { |
| let result = log |
| .output_ordering(&[base_order.clone(), num_order]) |
| .unwrap(); |
| results.push(result); |
| } |
| } |
| let expected = [ |
| // base: Unordered |
| SortProperties::Unordered, |
| SortProperties::Unordered, |
| SortProperties::Unordered, |
| SortProperties::Unordered, |
| // base: Ascending, num: Unordered |
| SortProperties::Unordered, |
| // base: Ascending, num: Ascending |
| SortProperties::Unordered, |
| // base: Ascending, num: Descending |
| SortProperties::Ordered(SortOptions { |
| descending: true, |
| nulls_first: true, |
| }), |
| // base: Ascending, num: Literal |
| SortProperties::Ordered(SortOptions { |
| descending: true, |
| nulls_first: true, |
| }), |
| // base: Descending, num: Unordered |
| SortProperties::Unordered, |
| // base: Descending, num: Ascending |
| SortProperties::Ordered(SortOptions { |
| descending: false, |
| nulls_first: true, |
| }), |
| // base: Descending, num: Descending |
| SortProperties::Unordered, |
| // base: Descending, num: Literal |
| SortProperties::Ordered(SortOptions { |
| descending: false, |
| nulls_first: true, |
| }), |
| // base: Literal, num: Unordered |
| SortProperties::Unordered, |
| // base: Literal, num: Ascending |
| SortProperties::Ordered(SortOptions { |
| descending: false, |
| nulls_first: true, |
| }), |
| // base: Literal, num: Descending |
| SortProperties::Ordered(SortOptions { |
| descending: true, |
| nulls_first: true, |
| }), |
| // base: Literal, num: Literal |
| SortProperties::Singleton, |
| ]; |
| assert_eq!(results, expected); |
| |
| // Test with different `nulls_first` |
| let base_order = ExprProperties::new_unknown().with_order( |
| SortProperties::Ordered(SortOptions { |
| descending: true, |
| nulls_first: true, |
| }), |
| ); |
| let num_order = ExprProperties::new_unknown().with_order( |
| SortProperties::Ordered(SortOptions { |
| descending: false, |
| nulls_first: false, |
| }), |
| ); |
| assert_eq!( |
| log.output_ordering(&[base_order, num_order]).unwrap(), |
| SortProperties::Unordered |
| ); |
| } |
| |
| #[test] |
| fn test_log_scalar_decimal128_unary() { |
| let arg_field = Field::new("a", DataType::Decimal128(38, 0), false).into(); |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Scalar(ScalarValue::Decimal128(Some(10), 38, 0)), // num |
| ], |
| arg_fields: vec![arg_field], |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Decimal128(38, 0), true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float64_array(&arr) |
| .expect("failed to convert result to a Decimal128Array"); |
| assert_eq!(floats.len(), 1); |
| assert!((floats.value(0) - 1.0).abs() < 1e-10); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_scalar_decimal128() { |
| let arg_fields = vec![ |
| Field::new("b", DataType::Float64, false).into(), |
| Field::new("x", DataType::Decimal128(38, 0), false).into(), |
| ]; |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Scalar(ScalarValue::Float64(Some(2.0))), // base |
| ColumnarValue::Scalar(ScalarValue::Decimal128(Some(64), 38, 0)), // num |
| ], |
| arg_fields, |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float64_array(&arr) |
| .expect("failed to convert result to a Float64Array"); |
| |
| assert_eq!(floats.len(), 1); |
| assert!((floats.value(0) - 6.0).abs() < 1e-10); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_decimal128_unary() { |
| let arg_field = Field::new("a", DataType::Decimal128(38, 0), false).into(); |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Array(Arc::new( |
| Decimal128Array::from(vec![10, 100, 1000, 10000, 12600, -123]) |
| .with_precision_and_scale(38, 0) |
| .unwrap(), |
| )), // num |
| ], |
| arg_fields: vec![arg_field], |
| number_rows: 6, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float64_array(&arr) |
| .expect("failed to convert result to a Float64Array"); |
| |
| assert_eq!(floats.len(), 6); |
| assert!((floats.value(0) - 1.0).abs() < 1e-10); |
| assert!((floats.value(1) - 2.0).abs() < 1e-10); |
| assert!((floats.value(2) - 3.0).abs() < 1e-10); |
| assert!((floats.value(3) - 4.0).abs() < 1e-10); |
| assert!((floats.value(4) - 4.0).abs() < 1e-10); // Integer rounding |
| assert!(floats.value(5).is_nan()); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_decimal128_base_decimal() { |
| // Base stays 2 despite scaling |
| for base in [ |
| ScalarValue::Decimal128(Some(i128::from(2)), 38, 0), |
| ScalarValue::Decimal128(Some(i128::from(2000)), 38, 3), |
| ] { |
| let arg_fields = vec![ |
| Field::new("b", DataType::Decimal128(38, 0), false).into(), |
| Field::new("x", DataType::Decimal128(38, 0), false).into(), |
| ]; |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Scalar(base), // base |
| ColumnarValue::Scalar(ScalarValue::Decimal128(Some(64), 38, 0)), // num |
| ], |
| arg_fields, |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float64_array(&arr) |
| .expect("failed to convert result to a Float64Array"); |
| |
| assert_eq!(floats.len(), 1); |
| assert!((floats.value(0) - 6.0).abs() < 1e-10); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_decimal128_value_scale() { |
| // Value stays 1000 despite scaling |
| for value in [ |
| ScalarValue::Decimal128(Some(i128::from(1000)), 38, 0), |
| ScalarValue::Decimal128(Some(i128::from(10000)), 38, 1), |
| ScalarValue::Decimal128(Some(i128::from(1000000)), 38, 3), |
| ] { |
| let arg_fields = vec![ |
| Field::new("b", DataType::Decimal128(38, 0), false).into(), |
| Field::new("x", DataType::Decimal128(38, 0), false).into(), |
| ]; |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Scalar(value), // base |
| ], |
| arg_fields, |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float64_array(&arr) |
| .expect("failed to convert result to a Float64Array"); |
| |
| assert_eq!(floats.len(), 1); |
| assert!((floats.value(0) - 3.0).abs() < 1e-10); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_decimal256_unary() { |
| let arg_field = Field::new( |
| "a", |
| DataType::Decimal256(DECIMAL256_MAX_PRECISION, 0), |
| false, |
| ) |
| .into(); |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Array(Arc::new( |
| Decimal256Array::from(vec![ |
| Some(i256::from(10)), |
| Some(i256::from(100)), |
| Some(i256::from(1000)), |
| Some(i256::from(10000)), |
| Some(i256::from(12600)), |
| // Slightly lower than i128 max - can calculate |
| Some(i256::from_i128(i128::MAX) - i256::from(1000)), |
| // Give NaN for incorrect inputs, as in f64::log |
| Some(i256::from(-123)), |
| ]) |
| .with_precision_and_scale(DECIMAL256_MAX_PRECISION, 0) |
| .unwrap(), |
| )), // num |
| ], |
| arg_fields: vec![arg_field], |
| number_rows: 7, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new() |
| .invoke_with_args(args) |
| .expect("failed to initialize function log"); |
| |
| match result { |
| ColumnarValue::Array(arr) => { |
| let floats = as_float64_array(&arr) |
| .expect("failed to convert result to a Float64Array"); |
| |
| assert_eq!(floats.len(), 7); |
| eprintln!("floats {:?}", &floats); |
| assert!((floats.value(0) - 1.0).abs() < 1e-10); |
| assert!((floats.value(1) - 2.0).abs() < 1e-10); |
| assert!((floats.value(2) - 3.0).abs() < 1e-10); |
| assert!((floats.value(3) - 4.0).abs() < 1e-10); |
| assert!((floats.value(4) - 4.0).abs() < 1e-10); // Integer rounding for float log |
| assert!((floats.value(5) - 38.0).abs() < 1e-10); |
| assert!(floats.value(6).is_nan()); |
| } |
| ColumnarValue::Scalar(_) => { |
| panic!("Expected an array value") |
| } |
| } |
| } |
| |
| #[test] |
| fn test_log_decimal128_wrong_base() { |
| let arg_fields = vec![ |
| Field::new("b", DataType::Float64, false).into(), |
| Field::new("x", DataType::Decimal128(38, 0), false).into(), |
| ]; |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Scalar(ScalarValue::Float64(Some(-2.0))), // base |
| ColumnarValue::Scalar(ScalarValue::Decimal128(Some(64), 38, 0)), // num |
| ], |
| arg_fields, |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new().invoke_with_args(args); |
| assert!(result.is_err()); |
| assert_eq!( |
| "Arrow error: Compute error: Log base must be greater than 1: -2", |
| result.unwrap_err().to_string().lines().next().unwrap() |
| ); |
| } |
| |
| #[test] |
| fn test_log_decimal256_error() { |
| let arg_field = Field::new("a", DataType::Decimal256(38, 0), false).into(); |
| let args = ScalarFunctionArgs { |
| args: vec![ |
| ColumnarValue::Array(Arc::new(Decimal256Array::from(vec![ |
| // Slightly larger than i128 |
| Some(i256::from_i128(i128::MAX) + i256::from(1000)), |
| ]))), // num |
| ], |
| arg_fields: vec![arg_field], |
| number_rows: 1, |
| return_field: Field::new("f", DataType::Float64, true).into(), |
| config_options: Arc::new(ConfigOptions::default()), |
| }; |
| let result = LogFunc::new().invoke_with_args(args); |
| assert!(result.is_err()); |
| assert_eq!(result.unwrap_err().to_string().lines().next().unwrap(), |
| "Arrow error: Not yet implemented: Log of Decimal256 larger than Decimal128 is not yet supported: 170141183460469231731687303715884106727" |
| ); |
| } |
| } |