From dcb35001adfd0617252c5e3703e21a16e75847e0 Mon Sep 17 00:00:00 2001
From: Jefffrey <jeffrey.vo.australia@gmail.com>
Date: Fri, 7 Nov 2025 01:29:21 +0900
Subject: [PATCH 1/5] Refactor `log()` signature to use coercion API + fixes

---
 Cargo.lock                                    |   1 +
 datafusion/common/src/scalar/mod.rs           |   1 +
 datafusion/common/src/types/native.rs         |  27 ++-
 datafusion/expr-common/src/signature.rs       |  22 ++-
 datafusion/functions/Cargo.toml               |   1 +
 datafusion/functions/src/math/log.rs          | 160 +++++++++---------
 datafusion/functions/src/utils.rs             |   5 +-
 .../sqllogictest/test_files/decimal.slt       |   3 +-
 datafusion/sqllogictest/test_files/order.slt  |  32 ++--
 9 files changed, 131 insertions(+), 121 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index f500265108ff..eb4605bb9188 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2323,6 +2323,7 @@ dependencies = [
  "datafusion-expr-common",
  "datafusion-macros",
  "env_logger",
+ "half",
  "hex",
  "itertools 0.14.0",
  "log",
diff --git a/datafusion/common/src/scalar/mod.rs b/datafusion/common/src/scalar/mod.rs
index 188a169a3dd2..3b752eee2038 100644
--- a/datafusion/common/src/scalar/mod.rs
+++ b/datafusion/common/src/scalar/mod.rs
@@ -4563,6 +4563,7 @@ impl_try_from!(UInt8, u8);
 impl_try_from!(UInt16, u16);
 impl_try_from!(UInt32, u32);
 impl_try_from!(UInt64, u64);
+impl_try_from!(Float16, f16);
 impl_try_from!(Float32, f32);
 impl_try_from!(Float64, f64);
 impl_try_from!(Boolean, bool);
diff --git a/datafusion/common/src/types/native.rs b/datafusion/common/src/types/native.rs
index 8c41701ae576..a1495b779ac9 100644
--- a/datafusion/common/src/types/native.rs
+++ b/datafusion/common/src/types/native.rs
@@ -430,22 +430,7 @@ impl From<DataType> for NativeType {
 impl NativeType {
     #[inline]
     pub fn is_numeric(&self) -> bool {
-        use NativeType::*;
-        matches!(
-            self,
-            UInt8
-                | UInt16
-                | UInt32
-                | UInt64
-                | Int8
-                | Int16
-                | Int32
-                | Int64
-                | Float16
-                | Float32
-                | Float64
-                | Decimal(_, _)
-        )
+        self.is_integer() || self.is_float() || self.is_decimal()
     }
 
     #[inline]
@@ -491,4 +476,14 @@ impl NativeType {
     pub fn is_null(&self) -> bool {
         matches!(self, NativeType::Null)
     }
+
+    #[inline]
+    pub fn is_decimal(&self) -> bool {
+        matches!(self, Self::Decimal(_, _))
+    }
+
+    #[inline]
+    pub fn is_float(&self) -> bool {
+        matches!(self, Self::Float16 | Self::Float32 | Self::Float64)
+    }
 }
diff --git a/datafusion/expr-common/src/signature.rs b/datafusion/expr-common/src/signature.rs
index 2bf7092dd222..d059df22e65f 100644
--- a/datafusion/expr-common/src/signature.rs
+++ b/datafusion/expr-common/src/signature.rs
@@ -21,7 +21,7 @@ use std::fmt::Display;
 use std::hash::Hash;
 
 use crate::type_coercion::aggregates::NUMERICS;
-use arrow::datatypes::{DataType, IntervalUnit, TimeUnit};
+use arrow::datatypes::{DataType, Decimal128Type, DecimalType, IntervalUnit, TimeUnit};
 use datafusion_common::types::{LogicalType, LogicalTypeRef, NativeType};
 use datafusion_common::utils::ListCoercion;
 use datafusion_common::{internal_err, plan_err, Result};
@@ -333,9 +333,10 @@ pub enum TypeSignatureClass {
     Interval,
     Duration,
     Native(LogicalTypeRef),
-    // TODO:
-    // Numeric
     Integer,
+    Float,
+    Decimal,
+    Numeric,
     /// Encompasses both the native Binary as well as arbitrarily sized FixedSizeBinary types
     Binary,
 }
@@ -378,6 +379,9 @@ impl TypeSignatureClass {
             TypeSignatureClass::Binary => {
                 vec![DataType::Binary]
             }
+            TypeSignatureClass::Decimal => vec![Decimal128Type::DEFAULT_TYPE],
+            TypeSignatureClass::Float => vec![DataType::Float64],
+            TypeSignatureClass::Numeric => vec![DataType::Float64],
         }
     }
 
@@ -395,6 +399,9 @@ impl TypeSignatureClass {
             TypeSignatureClass::Duration if logical_type.is_duration() => true,
             TypeSignatureClass::Integer if logical_type.is_integer() => true,
             TypeSignatureClass::Binary if logical_type.is_binary() => true,
+            TypeSignatureClass::Decimal if logical_type.is_decimal() => true,
+            TypeSignatureClass::Float if logical_type.is_float() => true,
+            TypeSignatureClass::Numeric if logical_type.is_numeric() => true,
             _ => false,
         }
     }
@@ -428,6 +435,15 @@ impl TypeSignatureClass {
             TypeSignatureClass::Binary if native_type.is_binary() => {
                 Ok(origin_type.to_owned())
             }
+            TypeSignatureClass::Decimal if native_type.is_decimal() => {
+                Ok(origin_type.to_owned())
+            }
+            TypeSignatureClass::Float if native_type.is_float() => {
+                Ok(origin_type.to_owned())
+            }
+            TypeSignatureClass::Numeric if native_type.is_numeric() => {
+                Ok(origin_type.to_owned())
+            }
             _ if native_type.is_null() => Ok(origin_type.to_owned()),
             _ => internal_err!("May miss the matching logic in `matches_native_type`"),
         }
diff --git a/datafusion/functions/Cargo.toml b/datafusion/functions/Cargo.toml
index 1dbeee7159fd..262c5c95a0df 100644
--- a/datafusion/functions/Cargo.toml
+++ b/datafusion/functions/Cargo.toml
@@ -74,6 +74,7 @@ datafusion-execution = { workspace = true }
 datafusion-expr = { workspace = true }
 datafusion-expr-common = { workspace = true }
 datafusion-macros = { workspace = true }
+half = { workspace = true }
 hex = { version = "0.4", optional = true }
 itertools = { workspace = true }
 log = { workspace = true }
diff --git a/datafusion/functions/src/math/log.rs b/datafusion/functions/src/math/log.rs
index f66f6fcfc1f8..6cbe3464a2d4 100644
--- a/datafusion/functions/src/math/log.rs
+++ b/datafusion/functions/src/math/log.rs
@@ -18,18 +18,17 @@
 //! Math function: `log()`.
 
 use std::any::Any;
-use std::sync::Arc;
 
 use super::power::PowerFunc;
 
 use crate::utils::{calculate_binary_math, decimal128_to_i128};
 use arrow::array::{Array, ArrayRef};
 use arrow::datatypes::{
-    DataType, Decimal128Type, Decimal256Type, Float32Type, Float64Type, Int32Type,
-    Int64Type, DECIMAL128_MAX_PRECISION, DECIMAL256_MAX_PRECISION,
+    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,
 };
@@ -37,11 +36,12 @@ use datafusion_expr::expr::ScalarFunction;
 use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyInfo};
 use datafusion_expr::sort_properties::{ExprProperties, SortProperties};
 use datafusion_expr::{
-    lit, ColumnarValue, Documentation, Expr, ScalarFunctionArgs, ScalarUDF,
-    TypeSignature::*,
+    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"),
@@ -72,37 +72,28 @@ impl Default for LogFunc {
 
 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 precendence over floats since we have
+                // a native decimal implementation for log
                 vec![
-                    Numeric(1),
-                    Numeric(2),
-                    Exact(vec![DataType::Float32, DataType::Float32]),
-                    Exact(vec![DataType::Float64, DataType::Float64]),
-                    Exact(vec![
-                        DataType::Int64,
-                        DataType::Decimal128(DECIMAL128_MAX_PRECISION, 0),
-                    ]),
-                    Exact(vec![
-                        DataType::Float32,
-                        DataType::Decimal128(DECIMAL128_MAX_PRECISION, 0),
-                    ]),
-                    Exact(vec![
-                        DataType::Float64,
-                        DataType::Decimal128(DECIMAL128_MAX_PRECISION, 0),
-                    ]),
-                    Exact(vec![
-                        DataType::Int64,
-                        DataType::Decimal256(DECIMAL256_MAX_PRECISION, 0),
-                    ]),
-                    Exact(vec![
-                        DataType::Float32,
-                        DataType::Decimal256(DECIMAL256_MAX_PRECISION, 0),
-                    ]),
-                    Exact(vec![
-                        DataType::Float64,
-                        DataType::Decimal256(DECIMAL256_MAX_PRECISION, 0),
+                    // 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,
             ),
@@ -160,6 +151,7 @@ impl ScalarUDFImpl for LogFunc {
     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),
         }
@@ -192,68 +184,60 @@ impl ScalarUDFImpl for LogFunc {
 
     // Support overloaded log(base, x) and log(x) which defaults to log(10, x)
     fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
-        let args = ColumnarValue::values_to_arrays(&args.args)?;
+        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.len() == 2 {
-            // note in f64::log params order is different than in sql. e.g in sql log(base, x) == f64::log(x, base)
-            (ColumnarValue::Array(Arc::clone(&args[0])), &args[1])
+        let (base, value) = if args.args.len() == 2 {
+            (args.args[0].clone(), &args.args[1])
         } else {
-            // log(num) - assume base is 10
-            let ret_type = if args[0].data_type().is_null() {
-                &DataType::Float64
-            } else {
-                args[0].data_type()
-            };
+            // no base specified, default to 10
             (
-                ColumnarValue::Array(
-                    ScalarValue::new_ten(ret_type)?.to_array_of_size(args[0].len())?,
-                ),
-                &args[0],
+                ColumnarValue::Scalar(ScalarValue::new_ten(args.return_type())?),
+                &args.args[0],
             )
         };
+        let value = value.to_array(args.number_rows)?;
 
-        // All log functors have format 'log(value, base)'
-        // Therefore, for `calculate_binary_math` the first type means a type of main array
-        // The second type is the type of the base array (even if derived from main)
-        let arr: ArrayRef = match value.data_type() {
-            DataType::Float32 => calculate_binary_math::<
-                Float32Type,
-                Float32Type,
-                Float32Type,
-                _,
-            >(value, &base, |x, b| Ok(f32::log(x, b)))?,
-            DataType::Float64 => calculate_binary_math::<
-                Float64Type,
-                Float64Type,
-                Float64Type,
-                _,
-            >(value, &base, |x, b| Ok(f64::log(x, b)))?,
-            DataType::Int32 => {
-                calculate_binary_math::<Int32Type, Float64Type, Float64Type, _>(
-                    value,
+        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,
-                    |x, b| Ok(f64::log(x as f64, b)),
+                    |value, base| Ok(value.log(base)),
                 )?
             }
-            DataType::Int64 => {
-                calculate_binary_math::<Int64Type, Float64Type, Float64Type, _>(
-                    value,
+            DataType::Float64 => {
+                calculate_binary_math::<Float64Type, Float64Type, Float64Type, _>(
+                    &value,
                     &base,
-                    |x, b| Ok(f64::log(x as f64, b)),
+                    |value, base| Ok(value.log(base)),
                 )?
             }
-            DataType::Decimal128(_precision, scale) => {
+            // TODO: native log support for decimal 32 & 64; right now upcast
+            //       to decimal128 to calculate
+            DataType::Decimal32(_, scale)
+            | DataType::Decimal64(_, scale)
+            | DataType::Decimal128(_, scale) => {
                 calculate_binary_math::<Decimal128Type, Float64Type, Float64Type, _>(
-                    value,
+                    &value,
                     &base,
-                    |x, b| log_decimal128(x, *scale, b),
+                    |value, base| log_decimal128(value, *scale, base),
                 )?
             }
-            DataType::Decimal256(_precision, scale) => {
+            DataType::Decimal256(_, scale) => {
                 calculate_binary_math::<Decimal256Type, Float64Type, Float64Type, _>(
-                    value,
+                    &value,
                     &base,
-                    |x, b| log_decimal256(x, *scale, b),
+                    |value, base| log_decimal256(value, *scale, base),
                 )?
             }
             other => {
@@ -261,7 +245,7 @@ impl ScalarUDFImpl for LogFunc {
             }
         };
 
-        Ok(ColumnarValue::Array(arr))
+        Ok(ColumnarValue::Array(output))
     }
 
     fn documentation(&self) -> Option<&Documentation> {
@@ -277,17 +261,28 @@ impl ScalarUDFImpl for LogFunc {
         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 > 2 {
+        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().ok_or_else(|| {
-            plan_datafusion_err!("Expected log to have 1 or 2 arguments, got 0")
-        })?;
-        let number_datatype = info.get_data_type(&number)?;
+        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
@@ -339,6 +334,7 @@ fn is_pow(func: &ScalarUDF) -> bool {
 #[cfg(test)]
 mod tests {
     use std::collections::HashMap;
+    use std::sync::Arc;
 
     use super::*;
 
diff --git a/datafusion/functions/src/utils.rs b/datafusion/functions/src/utils.rs
index 932d61e8007c..c27b2404e6be 100644
--- a/datafusion/functions/src/utils.rs
+++ b/datafusion/functions/src/utils.rs
@@ -140,6 +140,7 @@ where
     F: Fn(L::Native, R::Native) -> Result<O::Native, ArrowError>,
     R::Native: TryFrom<ScalarValue>,
 {
+    let right = right.cast_to(&R::DATA_TYPE, None)?;
     Ok(match right {
         ColumnarValue::Scalar(scalar) => {
             let right_value: R::Native =
@@ -151,14 +152,12 @@ where
                     ))
                 })?;
             let left_array = left.as_primitive::<L>();
-            // Bind right value
             let result =
                 left_array.try_unary::<_, O, _>(|lvalue| fun(lvalue, right_value))?;
             Arc::new(result) as _
         }
         ColumnarValue::Array(right) => {
-            let right_casted = arrow::compute::cast(&right, &R::DATA_TYPE)?;
-            let right_array = right_casted.as_primitive::<R>();
+            let right_array = right.as_primitive::<R>();
 
             // Types are compatible even they are decimals with different scale or precision
             let result = if PrimitiveArray::<L>::is_compatible(&L::DATA_TYPE) {
diff --git a/datafusion/sqllogictest/test_files/decimal.slt b/datafusion/sqllogictest/test_files/decimal.slt
index 502821fcc304..4e7eb42e4582 100644
--- a/datafusion/sqllogictest/test_files/decimal.slt
+++ b/datafusion/sqllogictest/test_files/decimal.slt
@@ -858,10 +858,11 @@ select log(2, 100000000000000000000000000000000000::decimal(38,0));
 116
 
 # log(10^35) for decimal128 with another base
+# TODO: this should be 116.267483321058, error with native decimal log impl
 query R
 select log(2.0, 100000000000000000000000000000000000::decimal(38,0));
 ----
-116.267483321058
+116
 
 # null cases
 query R
diff --git a/datafusion/sqllogictest/test_files/order.slt b/datafusion/sqllogictest/test_files/order.slt
index 04a7615c764b..a73f56079e3f 100644
--- a/datafusion/sqllogictest/test_files/order.slt
+++ b/datafusion/sqllogictest/test_files/order.slt
@@ -675,11 +675,11 @@ query TT
 ----
 logical_plan
 01)Sort: log_c11_base_c12 ASC NULLS LAST
-02)--Projection: log(aggregate_test_100.c12, CAST(aggregate_test_100.c11 AS Float64)) AS log_c11_base_c12
+02)--Projection: log(aggregate_test_100.c12, aggregate_test_100.c11) AS log_c11_base_c12
 03)----TableScan: aggregate_test_100 projection=[c11, c12]
 physical_plan
 01)SortPreservingMergeExec: [log_c11_base_c12@0 ASC NULLS LAST]
-02)--ProjectionExec: expr=[log(c12@1, CAST(c11@0 AS Float64)) as log_c11_base_c12]
+02)--ProjectionExec: expr=[log(c12@1, c11@0) as log_c11_base_c12]
 03)----RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=1
 04)------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/testing/data/csv/aggregate_test_100.csv]]}, projection=[c11, c12], output_orderings=[[c11@0 ASC NULLS LAST], [c12@1 DESC NULLS LAST]], file_type=csv, has_header=true
 
@@ -690,11 +690,11 @@ ORDER BY log_c12_base_c11 DESC NULLS LAST;
 ----
 logical_plan
 01)Sort: log_c12_base_c11 DESC NULLS LAST
-02)--Projection: log(CAST(aggregate_test_100.c11 AS Float64), aggregate_test_100.c12) AS log_c12_base_c11
+02)--Projection: log(aggregate_test_100.c11, aggregate_test_100.c12) AS log_c12_base_c11
 03)----TableScan: aggregate_test_100 projection=[c11, c12]
 physical_plan
 01)SortPreservingMergeExec: [log_c12_base_c11@0 DESC NULLS LAST]
-02)--ProjectionExec: expr=[log(CAST(c11@0 AS Float64), c12@1) as log_c12_base_c11]
+02)--ProjectionExec: expr=[log(c11@0, c12@1) as log_c12_base_c11]
 03)----RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=1
 04)------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/testing/data/csv/aggregate_test_100.csv]]}, projection=[c11, c12], output_orderings=[[c11@0 ASC NULLS LAST], [c12@1 DESC NULLS LAST]], file_type=csv, has_header=true
 
@@ -1024,10 +1024,10 @@ ORDER BY SUM(column1)
 
 # ORDER BY with a GROUP BY clause
 query I
-SELECT SUM(column1) 
-  FROM foo 
-GROUP BY column2 
-ORDER BY SUM(column1) 
+SELECT SUM(column1)
+  FROM foo
+GROUP BY column2
+ORDER BY SUM(column1)
 ----
 0
 2
@@ -1039,12 +1039,12 @@ ORDER BY SUM(column1)
 
 # ORDER BY with a GROUP BY clause and a HAVING clause
 query I
-SELECT 
-  SUM(column1) 
-FROM foo 
-GROUP BY column2 
-HAVING SUM(column1) < 3 
-ORDER BY SUM(column1) 
+SELECT
+  SUM(column1)
+FROM foo
+GROUP BY column2
+HAVING SUM(column1) < 3
+ORDER BY SUM(column1)
 ----
 0
 2
@@ -1179,7 +1179,7 @@ physical_plan
 04)------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/core/tests/data/window_2.csv]]}, projection=[c], output_ordering=[c@0 ASC NULLS LAST], file_type=csv, has_header=true
 
 statement ok
-drop table ordered_table; 
+drop table ordered_table;
 
 
 # ABS(x) breaks the ordering if x's range contains both negative and positive values.
@@ -1215,7 +1215,7 @@ physical_plan
 05)--------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/core/tests/data/window_2.csv]]}, projection=[c], output_ordering=[c@0 ASC NULLS LAST], file_type=csv, has_header=true
 
 statement ok
-drop table ordered_table; 
+drop table ordered_table;
 
 # ABS(x) preserves the ordering if x's range falls into positive values.
 # Since x is defined as INT UNSIGNED, its range is assumed to be from 0 to INF.

From 2ce83f057c281eb2e05fbebc4e4d816e2c6cd306 Mon Sep 17 00:00:00 2001
From: Jefffrey <jeffrey.vo.australia@gmail.com>
Date: Fri, 7 Nov 2025 01:48:21 +0900
Subject: [PATCH 2/5] CI checks

---
 Cargo.lock                           | 1 -
 datafusion/functions/Cargo.toml      | 1 -
 datafusion/functions/src/math/log.rs | 2 +-
 3 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index eb4605bb9188..f500265108ff 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2323,7 +2323,6 @@ dependencies = [
  "datafusion-expr-common",
  "datafusion-macros",
  "env_logger",
- "half",
  "hex",
  "itertools 0.14.0",
  "log",
diff --git a/datafusion/functions/Cargo.toml b/datafusion/functions/Cargo.toml
index 262c5c95a0df..1dbeee7159fd 100644
--- a/datafusion/functions/Cargo.toml
+++ b/datafusion/functions/Cargo.toml
@@ -74,7 +74,6 @@ datafusion-execution = { workspace = true }
 datafusion-expr = { workspace = true }
 datafusion-expr-common = { workspace = true }
 datafusion-macros = { workspace = true }
-half = { workspace = true }
 hex = { version = "0.4", optional = true }
 itertools = { workspace = true }
 log = { workspace = true }
diff --git a/datafusion/functions/src/math/log.rs b/datafusion/functions/src/math/log.rs
index 6cbe3464a2d4..295dc771be7e 100644
--- a/datafusion/functions/src/math/log.rs
+++ b/datafusion/functions/src/math/log.rs
@@ -80,7 +80,7 @@ impl LogFunc {
         );
         Self {
             signature: Signature::one_of(
-                // Ensure decimals have precendence over floats since we have
+                // Ensure decimals have precedence over floats since we have
                 // a native decimal implementation for log
                 vec![
                     // log(value)

From 0da79bd8e912bd7272c304fb7dc675b8398ca84a Mon Sep 17 00:00:00 2001
From: Jefffrey <jeffrey.vo.australia@gmail.com>
Date: Fri, 7 Nov 2025 15:21:09 +0900
Subject: [PATCH 3/5] Tests & fix casting dec32/64 to dec128

---
 datafusion/common/src/scalar/mod.rs           |  4 +--
 datafusion/functions/src/math/log.rs          | 24 +++++++++++--
 .../sqllogictest/test_files/decimal.slt       | 36 +++++++++++++++++--
 datafusion/sqllogictest/test_files/math.slt   | 27 ++++++++++++++
 4 files changed, 84 insertions(+), 7 deletions(-)

diff --git a/datafusion/common/src/scalar/mod.rs b/datafusion/common/src/scalar/mod.rs
index 3b752eee2038..034c3cba8ea3 100644
--- a/datafusion/common/src/scalar/mod.rs
+++ b/datafusion/common/src/scalar/mod.rs
@@ -1734,7 +1734,7 @@ impl ScalarValue {
                 ) {
                     return _internal_err!("Invalid precision and scale {err}");
                 }
-                if *scale <= 0 {
+                if *scale < 0 {
                     return _internal_err!("Negative scale is not supported");
                 }
                 match 10_i32.checked_pow((*scale + 1) as u32) {
@@ -1750,7 +1750,7 @@ impl ScalarValue {
                 ) {
                     return _internal_err!("Invalid precision and scale {err}");
                 }
-                if *scale <= 0 {
+                if *scale < 0 {
                     return _internal_err!("Negative scale is not supported");
                 }
                 match i64::from(10).checked_pow((*scale + 1) as u32) {
diff --git a/datafusion/functions/src/math/log.rs b/datafusion/functions/src/math/log.rs
index 295dc771be7e..0fb00ab250e3 100644
--- a/datafusion/functions/src/math/log.rs
+++ b/datafusion/functions/src/math/log.rs
@@ -23,6 +23,7 @@ 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,
 };
@@ -224,9 +225,15 @@ impl ScalarUDFImpl for LogFunc {
             }
             // TODO: native log support for decimal 32 & 64; right now upcast
             //       to decimal128 to calculate
-            DataType::Decimal32(_, scale)
-            | DataType::Decimal64(_, scale)
-            | DataType::Decimal128(_, scale) => {
+            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,
@@ -349,6 +356,17 @@ mod tests {
     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
+            116.0
+        );
+    }
+
     #[test]
     fn test_log_invalid_base_type() {
         let arg_fields = vec![
diff --git a/datafusion/sqllogictest/test_files/decimal.slt b/datafusion/sqllogictest/test_files/decimal.slt
index 4e7eb42e4582..7658ea88f38b 100644
--- a/datafusion/sqllogictest/test_files/decimal.slt
+++ b/datafusion/sqllogictest/test_files/decimal.slt
@@ -794,15 +794,47 @@ select 100000000000000000000000000000000000::decimal(38,0), arrow_typeof(1000000
 ----
 100000000000000000000000000000000000 Decimal128(38, 0)
 
+# log for small decimal32
+query R
+select log(arrow_cast(100, 'Decimal32(9, 0)'));
+----
+2
+
+query R
+select log(arrow_cast(100, 'Decimal32(9, 2)'));
+----
+2
+
+# log for small decimal64
+query R
+select log(arrow_cast(100, 'Decimal64(18, 0)'));
+----
+2
+
+query R
+select log(arrow_cast(100, 'Decimal64(18, 2)'));
+----
+2
+
 # log for small decimal128
 query R
-select log(100::decimal(38,0));
+select log(arrow_cast(100, 'Decimal128(38, 0)'));
+----
+2
+
+query R
+select log(arrow_cast(100, 'Decimal128(38, 2)'));
 ----
 2
 
 # log for small decimal256
 query R
-select log(100::decimal(76,0));
+select log(arrow_cast(100, 'Decimal256(76, 0)'));
+----
+2
+
+query R
+select log(arrow_cast(100, 'Decimal256(76, 2)'));
 ----
 2
 
diff --git a/datafusion/sqllogictest/test_files/math.slt b/datafusion/sqllogictest/test_files/math.slt
index 1cb68b85b2bc..edba5354e001 100644
--- a/datafusion/sqllogictest/test_files/math.slt
+++ b/datafusion/sqllogictest/test_files/math.slt
@@ -705,3 +705,30 @@ select FACTORIAL(350943270);
 
 statement ok
 drop table signed_integers
+
+# Null propagation for log
+query TT
+EXPLAIN SELECT log(NULL, c2) from aggregate_simple;
+----
+logical_plan
+01)Projection: Float64(NULL) AS log(NULL,aggregate_simple.c2)
+02)--TableScan: aggregate_simple projection=[]
+physical_plan
+01)ProjectionExec: expr=[NULL as log(NULL,aggregate_simple.c2)]
+02)--DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/core/tests/data/aggregate_simple.csv]]}, file_type=csv, has_header=true
+
+# Float 16/32/64 for log
+query RT
+SELECT log(2.5, arrow_cast(10.9, 'Float16')), arrow_typeof(log(2.5, arrow_cast(10.9, 'Float16')));
+----
+2.6074219 Float16
+
+query RT
+SELECT log(2.5, 10.9::float), arrow_typeof(log(2.5, 10.9::float));
+----
+2.606992 Float32
+
+query RT
+SELECT log(2.5, 10.9::double), arrow_typeof(log(2.5, 10.9::double));
+----
+2.606992198152 Float64

From bfb9e92ec0235e80c29e1005a379db6d5f3475d3 Mon Sep 17 00:00:00 2001
From: Jefffrey <jeffrey.vo.australia@gmail.com>
Date: Fri, 7 Nov 2025 15:28:04 +0900
Subject: [PATCH 4/5] Add issue links

---
 datafusion/functions/src/math/log.rs           | 2 ++
 datafusion/sqllogictest/test_files/decimal.slt | 1 +
 2 files changed, 3 insertions(+)

diff --git a/datafusion/functions/src/math/log.rs b/datafusion/functions/src/math/log.rs
index 0fb00ab250e3..24000a3876bd 100644
--- a/datafusion/functions/src/math/log.rs
+++ b/datafusion/functions/src/math/log.rs
@@ -225,6 +225,7 @@ impl ScalarUDFImpl for LogFunc {
             }
             // 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, _>(
@@ -363,6 +364,7 @@ mod tests {
         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
         );
     }
diff --git a/datafusion/sqllogictest/test_files/decimal.slt b/datafusion/sqllogictest/test_files/decimal.slt
index 7658ea88f38b..f350d9b3bfe1 100644
--- a/datafusion/sqllogictest/test_files/decimal.slt
+++ b/datafusion/sqllogictest/test_files/decimal.slt
@@ -891,6 +891,7 @@ select log(2, 100000000000000000000000000000000000::decimal(38,0));
 
 # log(10^35) for decimal128 with another base
 # TODO: this should be 116.267483321058, error with native decimal log impl
+#       https://github.com/apache/datafusion/issues/18524
 query R
 select log(2.0, 100000000000000000000000000000000000::decimal(38,0));
 ----

From 029aa39e9849ae45f6f5e022d9d434de83d02ea2 Mon Sep 17 00:00:00 2001
From: Jefffrey <jeffrey.vo.australia@gmail.com>
Date: Fri, 7 Nov 2025 22:31:04 +0900
Subject: [PATCH 5/5] review comments

---
 datafusion/common/src/scalar/mod.rs     | 1 +
 datafusion/expr-common/src/signature.rs | 6 +++++-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/datafusion/common/src/scalar/mod.rs b/datafusion/common/src/scalar/mod.rs
index 8db3c3a66915..60ba309a6802 100644
--- a/datafusion/common/src/scalar/mod.rs
+++ b/datafusion/common/src/scalar/mod.rs
@@ -4407,6 +4407,7 @@ macro_rules! impl_scalar {
 
 impl_scalar!(f64, Float64);
 impl_scalar!(f32, Float32);
+impl_scalar!(f16, Float16);
 impl_scalar!(i8, Int8);
 impl_scalar!(i16, Int16);
 impl_scalar!(i32, Int32);
diff --git a/datafusion/expr-common/src/signature.rs b/datafusion/expr-common/src/signature.rs
index d059df22e65f..6ee1c4a2a40c 100644
--- a/datafusion/expr-common/src/signature.rs
+++ b/datafusion/expr-common/src/signature.rs
@@ -381,7 +381,11 @@ impl TypeSignatureClass {
             }
             TypeSignatureClass::Decimal => vec![Decimal128Type::DEFAULT_TYPE],
             TypeSignatureClass::Float => vec![DataType::Float64],
-            TypeSignatureClass::Numeric => vec![DataType::Float64],
+            TypeSignatureClass::Numeric => vec![
+                DataType::Float64,
+                DataType::Int64,
+                Decimal128Type::DEFAULT_TYPE,
+            ],
         }
     }
 
