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

mbutrovich pushed a commit to branch df55
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git


The following commit(s) were added to refs/heads/df55 by this push:
     new d8cea04c2b add spark_sqrt
d8cea04c2b is described below

commit d8cea04c2b01ce12cb85cdc9459cd2b4bf35b35b
Author: Matt Butrovich <[email protected]>
AuthorDate: Wed Aug 5 09:35:01 2026 -0400

    add spark_sqrt
---
 native/spark-expr/src/comet_scalar_funcs.rs        |  5 ++
 native/spark-expr/src/lib.rs                       |  6 +-
 native/spark-expr/src/math_funcs/mod.rs            |  2 +
 native/spark-expr/src/math_funcs/sqrt.rs           | 90 ++++++++++++++++++++++
 .../org/apache/comet/serde/QueryPlanSerde.scala    |  2 +-
 .../main/scala/org/apache/comet/serde/math.scala   | 19 ++++-
 6 files changed, 119 insertions(+), 5 deletions(-)

diff --git a/native/spark-expr/src/comet_scalar_funcs.rs 
b/native/spark-expr/src/comet_scalar_funcs.rs
index 50732959c8..0173d02d35 100644
--- a/native/spark-expr/src/comet_scalar_funcs.rs
+++ b/native/spark-expr/src/comet_scalar_funcs.rs
@@ -23,6 +23,7 @@ use crate::math_funcs::checked_arithmetic::{checked_add, 
checked_div, checked_mu
 use crate::math_funcs::log::spark_log;
 use crate::math_funcs::modulo_expr::spark_modulo;
 use crate::math_funcs::pow::spark_pow;
+use crate::math_funcs::sqrt::spark_sqrt;
 use crate::{
     spark_ceil, spark_day_name, spark_decimal_div, spark_decimal_integral_div, 
spark_floor,
     spark_isnan, spark_lpad, spark_make_decimal, spark_month_name, 
spark_read_side_padding,
@@ -213,6 +214,10 @@ pub fn create_comet_physical_fun_with_eval_mode(
             let func = Arc::new(spark_pow);
             make_comet_scalar_udf!("pow", func, without data_type)
         }
+        "spark_sqrt" => {
+            let func = Arc::new(spark_sqrt);
+            make_comet_scalar_udf!("spark_sqrt", func, without data_type)
+        }
         "base64" => {
             let func = Arc::new(crate::string_funcs::spark_base64);
             make_comet_scalar_udf!("base64", func, without data_type)
diff --git a/native/spark-expr/src/lib.rs b/native/spark-expr/src/lib.rs
index 42ab43361a..099f23b347 100644
--- a/native/spark-expr/src/lib.rs
+++ b/native/spark-expr/src/lib.rs
@@ -86,9 +86,9 @@ pub use json_funcs::{FromJson, ToJson};
 pub use math_funcs::{
     checked_add, checked_div, checked_mul, checked_sub, create_modulo_expr, 
create_negate_expr,
     spark_ceil, spark_decimal_div, spark_decimal_integral_div, spark_floor, 
spark_log,
-    spark_make_decimal, spark_pow, spark_round, spark_unhex, 
spark_unscaled_value, CheckOverflow,
-    DecimalRescaleCheckOverflow, NegativeExpr, NormalizeNaNAndZero, 
WideDecimalBinaryExpr,
-    WideDecimalOp,
+    spark_make_decimal, spark_pow, spark_round, spark_sqrt, spark_unhex, 
spark_unscaled_value,
+    CheckOverflow, DecimalRescaleCheckOverflow, NegativeExpr, 
NormalizeNaNAndZero,
+    WideDecimalBinaryExpr, WideDecimalOp,
 };
 pub use query_context::{create_query_context_map, QueryContext, 
QueryContextMap};
 pub use string_funcs::*;
diff --git a/native/spark-expr/src/math_funcs/mod.rs 
b/native/spark-expr/src/math_funcs/mod.rs
index 56809a01fb..7a7c6a9753 100644
--- a/native/spark-expr/src/math_funcs/mod.rs
+++ b/native/spark-expr/src/math_funcs/mod.rs
@@ -26,6 +26,7 @@ pub mod modulo_expr;
 mod negative;
 pub(crate) mod pow;
 mod round;
+pub(crate) mod sqrt;
 pub(crate) mod unhex;
 mod utils;
 mod wide_decimal_binary_expr;
@@ -41,5 +42,6 @@ pub use modulo_expr::create_modulo_expr;
 pub use negative::{create_negate_expr, NegativeExpr};
 pub use pow::spark_pow;
 pub use round::spark_round;
+pub use sqrt::spark_sqrt;
 pub use unhex::spark_unhex;
 pub use wide_decimal_binary_expr::{WideDecimalBinaryExpr, WideDecimalOp};
diff --git a/native/spark-expr/src/math_funcs/sqrt.rs 
b/native/spark-expr/src/math_funcs/sqrt.rs
new file mode 100644
index 0000000000..1397386eb9
--- /dev/null
+++ b/native/spark-expr/src/math_funcs/sqrt.rs
@@ -0,0 +1,90 @@
+// 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.
+
+use arrow::array::{ArrayRef, AsArray, Float64Array};
+use arrow::datatypes::Float64Type;
+use datafusion::common::{DataFusionError, ScalarValue};
+use datafusion::physical_plan::ColumnarValue;
+use std::sync::Arc;
+
+/// Spark-compatible `sqrt`, matching `java.lang.Math.sqrt`: a negative input 
produces
+/// `NaN` rather than an error, unlike DataFusion's own `sqrt`.
+pub fn spark_sqrt(args: &[ColumnarValue]) -> Result<ColumnarValue, 
DataFusionError> {
+    if args.len() != 1 {
+        return Err(DataFusionError::Internal(format!(
+            "spark_sqrt requires 1 argument, got {}",
+            args.len()
+        )));
+    }
+
+    match &args[0] {
+        ColumnarValue::Array(array) => {
+            let values = array.as_primitive_opt::<Float64Type>().ok_or_else(|| 
{
+                DataFusionError::Internal(format!(
+                    "spark_sqrt expected Float64, got {:?}",
+                    array.data_type()
+                ))
+            })?;
+            let result: Float64Array = values.unary(|v| v.sqrt());
+            Ok(ColumnarValue::Array(Arc::new(result) as ArrayRef))
+        }
+        ColumnarValue::Scalar(ScalarValue::Float64(v)) => 
Ok(ColumnarValue::Scalar(
+            ScalarValue::Float64(v.map(f64::sqrt)),
+        )),
+        ColumnarValue::Scalar(other) => Err(DataFusionError::Internal(format!(
+            "spark_sqrt expected Float64 scalar, got {other:?}",
+        ))),
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use arrow::array::{Array, Float64Array};
+
+    #[test]
+    fn test_spark_sqrt_negative_is_nan() {
+        let input = Float64Array::from(vec![Some(4.0), Some(-1.0), Some(0.0), 
None]);
+        let result = 
spark_sqrt(&[ColumnarValue::Array(Arc::new(input))]).unwrap();
+        let ColumnarValue::Array(result) = result else {
+            unreachable!()
+        };
+        let result = result.as_primitive::<Float64Type>();
+        assert_eq!(result.value(0), 2.0);
+        assert!(result.value(1).is_nan());
+        assert_eq!(result.value(2), 0.0);
+        assert!(result.is_null(3));
+    }
+
+    #[test]
+    fn test_spark_sqrt_scalar_negative_is_nan() {
+        let result =
+            
spark_sqrt(&[ColumnarValue::Scalar(ScalarValue::Float64(Some(-1.0)))]).unwrap();
+        let ColumnarValue::Scalar(ScalarValue::Float64(Some(result))) = result 
else {
+            unreachable!()
+        };
+        assert!(result.is_nan());
+    }
+
+    #[test]
+    fn test_spark_sqrt_scalar_null() {
+        let result = 
spark_sqrt(&[ColumnarValue::Scalar(ScalarValue::Float64(None))]).unwrap();
+        let ColumnarValue::Scalar(ScalarValue::Float64(None)) = result else {
+            unreachable!()
+        };
+    }
+}
diff --git a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala 
b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
index 9e867aad2d..d4ac222643 100644
--- a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
@@ -153,7 +153,7 @@ object QueryPlanSerde extends Logging with CometExprShim 
with CometTypeShim {
       classOf[Signum] -> CometScalarFunction("signum"),
       classOf[Sin] -> CometScalarFunction("sin"),
       classOf[Sinh] -> CometScalarFunction("sinh"),
-      classOf[Sqrt] -> CometScalarFunction("sqrt"),
+      classOf[Sqrt] -> CometSqrt,
       classOf[Subtract] -> CometSubtract,
       classOf[Tan] -> CometScalarFunction("tan"),
       classOf[Tanh] -> CometScalarFunction("tanh"),
diff --git a/spark/src/main/scala/org/apache/comet/serde/math.scala 
b/spark/src/main/scala/org/apache/comet/serde/math.scala
index e70794faa3..add477c835 100644
--- a/spark/src/main/scala/org/apache/comet/serde/math.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/math.scala
@@ -19,7 +19,7 @@
 
 package org.apache.comet.serde
 
-import org.apache.spark.sql.catalyst.expressions.{Abs, Add, Atan2, Attribute, 
BRound, Ceil, CheckOverflow, Conv, Expression, Floor, Hex, Hypot, If, 
LessThanOrEqual, Literal, Log, Log10, Log1p, Log2, Logarithm, NaNvl, Pmod, Pow, 
UnaryPositive, Unhex, WidthBucket}
+import org.apache.spark.sql.catalyst.expressions.{Abs, Add, Atan2, Attribute, 
BRound, Ceil, CheckOverflow, Conv, Expression, Floor, Hex, Hypot, If, 
LessThanOrEqual, Literal, Log, Log10, Log1p, Log2, Logarithm, NaNvl, Pmod, Pow, 
Sqrt, UnaryPositive, Unhex, WidthBucket}
 import org.apache.spark.sql.types.{DecimalType, DoubleType, NumericType}
 
 import org.apache.comet.CometSparkSessionExtensions.withFallbackReason
@@ -217,6 +217,23 @@ object CometPow extends CometExpressionSerde[Pow] {
   }
 }
 
+// Uses a custom spark_sqrt UDF because DataFusion's own `sqrt` errors on 
negative
+// input, while Spark's Sqrt (a plain wrapper around java.lang.Math.sqrt) 
returns NaN.
+// spark_sqrt is a Comet-only name with no DataFusion builtin counterpart, so 
the
+// return type must be set explicitly here to skip the session registry lookup 
that
+// scalarFunctionExprToProto would otherwise trigger (see 
CometLogarithm/spark_log).
+object CometSqrt extends CometExpressionSerde[Sqrt] {
+  override def convert(
+      expr: Sqrt,
+      inputs: Seq[Attribute],
+      binding: Boolean): Option[ExprOuterClass.Expr] = {
+    val childExpr = exprToProtoInternal(expr.child, inputs, binding)
+    val optExpr =
+      scalarFunctionExprToProtoWithReturnType("spark_sqrt", DoubleType, false, 
childExpr)
+    optExprWithFallbackReason(optExpr, expr, expr.child)
+  }
+}
+
 sealed trait MathExprBase {
   protected def nullIfNegative(expression: Expression): Expression = {
     val zero = Literal.default(expression.dataType)


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

Reply via email to