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

cloud-fan pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new bb8caec31005 [SPARK-40945][SQL][PYTHON] Add truncate function
bb8caec31005 is described below

commit bb8caec31005751399947aa99d393520098c76a2
Author: SreeramaYeshwanthGowd <[email protected]>
AuthorDate: Mon Aug 10 16:50:41 2026 +0800

    [SPARK-40945][SQL][PYTHON] Add truncate function
    
    ### What changes were proposed in this pull request?
    
    Add a built in `truncate(expr[, scale])` scalar function that truncates a 
numeric value toward zero to `scale` decimal places. `scale` defaults to 0, and 
a negative `scale` truncates digits to the left of the decimal point.
    
    API surface added:
    - SQL: `truncate(expr)` and `truncate(expr, scale)`
    - Scala DataFrame: `functions.truncate(col)`, `functions.truncate(col, 
scale)`
    - PySpark, classic and Spark Connect: `pyspark.sql.functions.truncate(col, 
scale=None)`
    
    Implementation notes:
    - `Truncate` is a small `RoundBase` subclass using `RoundingMode.DOWN`, 
mirroring the existing `Round` (HALF_UP) and `BRound` (HALF_EVEN). It reuses 
the same input types, result type derivation, and constant scale handling.
    - `RoundBase` routes `DecimalType` through `Decimal.changePrecision`, which 
previously supported only the FLOOR, CEILING, HALF_UP, and HALF_EVEN modes. 
This adds `Decimal.ROUND_DOWN` and the corresponding branches so the DOWN mode 
is supported. The branches are trivial: on the compact (long backed) path, 
integer division already truncates toward zero, so no adjustment is needed; the 
BigDecimal path already uses `setScale(scale, roundMode)`, which supports DOWN 
natively.
    
    On naming: this adds a new function named `truncate` rather than 
overloading the existing `trunc`. Spark's `trunc` is date only (`trunc(date, 
fmt)`), and both the date form and a numeric `trunc(numeric, scale)` take two 
arguments, so they cannot be distinguished by arity the way `ceil`/`floor` 
overload their scale argument. Introducing a numeric overload of `trunc` would 
require type based dispatch at parse time, which is a larger and more error 
prone change. The name `truncate` match [...]
    
    ### Why are the changes needed?
    
    Truncation toward zero to a given number of decimal places is a common 
numeric operation that Spark cannot express directly today. `floor` and `ceil` 
with a scale round toward negative and positive infinity, which is wrong for 
negative values, and `round`/`bround` round to nearest. The function is 
requested in SPARK-40945 and is provided by PostgreSQL, BigQuery, MySQL, 
Oracle, Snowflake, and Trino, so it also improves parity with the engines Spark 
users migrate from.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. It adds a new built in SQL function `truncate` and the corresponding 
Scala and PySpark DataFrame API entries. No existing behavior changes; the 
`Decimal` change only adds support for a new rounding mode and does not alter 
the existing modes.
    
    Example:
    
    ```
    spark-sql> SELECT truncate(1234.5678, 2);
    1234.56
    spark-sql> SELECT truncate(-1234.5678, 2);
    -1234.56
    spark-sql> SELECT truncate(1234.5678, -2);
    1200
    ```
    
    ### How was this patch tested?
    
    Added catalyst unit tests in `MathExpressionsSuite` covering decimal (both 
long backed and BigDecimal backed), double, and integral inputs, positive and 
negative values, positive and negative scale, the default scale, and null 
propagation. Extended `DecimalSuite` so its existing "respect rounding mode" 
test also exercises `ROUND_DOWN` by comparing `Decimal.changePrecision` against 
`BigDecimal.setScale` for the DOWN mode. Added a DataFrame API test in 
`MathFunctionsSuite`, PySpark doct [...]
    
    ### Was this patch authored or co-authored using generative AI tooling? No
    
    Closes #57476 from SreeramaYeshwanthGowd/add-truncate-function.
    
    Authored-by: SreeramaYeshwanthGowd <[email protected]>
    Signed-off-by: Wenchen Fan <[email protected]>
    (cherry picked from commit ae51635b2faad20ba1addd6a6ec8a507546a4862)
    Signed-off-by: Wenchen Fan <[email protected]>
---
 .../source/reference/pyspark.sql/functions.rst     |   1 +
 python/pyspark/sql/connect/functions/builtin.py    |  12 +
 python/pyspark/sql/functions/__init__.py           |   1 +
 python/pyspark/sql/functions/builtin.py            |  61 ++++
 .../scala/org/apache/spark/sql/functions.scala     |  47 +++
 .../scala/org/apache/spark/sql/types/Decimal.scala |   5 +-
 .../sql/catalyst/analysis/FunctionRegistry.scala   |   1 +
 .../sql/catalyst/expressions/mathExpressions.scala |  45 +++
 .../expressions/MathExpressionsSuite.scala         |  21 ++
 .../org/apache/spark/sql/types/DecimalSuite.scala  |   3 +-
 .../apache/spark/sql/PlanGenerationTestSuite.scala |   4 +
 .../explain-results/function_truncate.explain      |   2 +
 .../query-tests/queries/function_truncate.json     |  81 +++++
 .../queries/function_truncate.proto.bin            | Bin 0 -> 730 bytes
 .../sql-functions/sql-expression-schema.md         |   1 +
 .../sql-tests/analyzer-results/math.sql.out        | 301 ++++++++++++++++++
 .../analyzer-results/nonansi/math.sql.out          | 301 ++++++++++++++++++
 .../src/test/resources/sql-tests/inputs/math.sql   |  58 ++++
 .../test/resources/sql-tests/results/math.sql.out  | 344 +++++++++++++++++++++
 .../sql-tests/results/nonansi/math.sql.out         | 344 +++++++++++++++++++++
 .../org/apache/spark/sql/MathFunctionsSuite.scala  |  40 +++
 21 files changed, 1671 insertions(+), 2 deletions(-)

diff --git a/python/docs/source/reference/pyspark.sql/functions.rst 
b/python/docs/source/reference/pyspark.sql/functions.rst
index 7cc05cae3e21..f3d973b1e735 100644
--- a/python/docs/source/reference/pyspark.sql/functions.rst
+++ b/python/docs/source/reference/pyspark.sql/functions.rst
@@ -143,6 +143,7 @@ Mathematical Functions
     sqrt
     tan
     tanh
+    truncate
     try_add
     try_divide
     try_mod
diff --git a/python/pyspark/sql/connect/functions/builtin.py 
b/python/pyspark/sql/connect/functions/builtin.py
index 3353e6c4333c..b2f1149db00d 100644
--- a/python/pyspark/sql/connect/functions/builtin.py
+++ b/python/pyspark/sql/connect/functions/builtin.py
@@ -847,6 +847,18 @@ def round(col: "ColumnOrName", scale: 
Optional[Union[Column, int]] = None) -> Co
 round.__doc__ = pysparkfuncs.round.__doc__
 
 
+def truncate(col: "ColumnOrName", scale: Optional[Union[Column, int]] = None) 
-> Column:
+    if scale is None:
+        return _invoke_function_over_columns("truncate", col)
+    else:
+        scale = _enum_to_value(scale)
+        scale = lit(scale) if isinstance(scale, int) else scale
+        return _invoke_function_over_columns("truncate", col, scale)
+
+
+truncate.__doc__ = pysparkfuncs.truncate.__doc__
+
+
 def sec(col: "ColumnOrName") -> Column:
     return _invoke_function_over_columns("sec", col)
 
diff --git a/python/pyspark/sql/functions/__init__.py 
b/python/pyspark/sql/functions/__init__.py
index 93e80822f2ad..696691884f69 100644
--- a/python/pyspark/sql/functions/__init__.py
+++ b/python/pyspark/sql/functions/__init__.py
@@ -110,6 +110,7 @@ __all__ = [  # noqa: F405
     "sqrt",
     "tan",
     "tanh",
+    "truncate",
     "try_add",
     "try_divide",
     "try_mod",
diff --git a/python/pyspark/sql/functions/builtin.py 
b/python/pyspark/sql/functions/builtin.py
index 736f4884a533..6e67fec8f3c8 100644
--- a/python/pyspark/sql/functions/builtin.py
+++ b/python/pyspark/sql/functions/builtin.py
@@ -8311,6 +8311,67 @@ def round(col: "ColumnOrName", scale: 
Optional[Union[Column, int]] = None) -> Co
         return _invoke_function_over_columns("round", col, scale)
 
 
+@_try_remote_functions
+def truncate(col: "ColumnOrName", scale: Optional[Union[Column, int]] = None) 
-> Column:
+    """
+    Truncate the given value toward zero to `scale` decimal places when 
`scale` >= 0,
+    or to the left of the decimal point when `scale` < 0. `scale` defaults to 
0.
+
+    Unlike :func:`round`, the result is always rounded toward zero, and unlike 
:func:`floor`
+    negative values are not rounded toward negative infinity.
+
+    .. versionadded:: 4.4.0
+
+    Parameters
+    ----------
+    col : :class:`~pyspark.sql.Column` or column name
+        The target column or column name to truncate.
+        A column that evaluates to a numeric.
+    scale : :class:`~pyspark.sql.Column` or int, optional
+        An optional parameter to control the number of decimal places to keep.
+        A column that evaluates to an integer. Must be a constant. Defaults to 
0.
+
+    Returns
+    -------
+    :class:`~pyspark.sql.Column`
+        A column for the truncated value, of the same type as the input, 
except that a decimal
+        input may return a decimal of different precision and scale.
+
+    See Also
+    --------
+    :meth:`pyspark.sql.functions.round`
+    :meth:`pyspark.sql.functions.trunc`
+    :meth:`pyspark.sql.functions.floor`
+    :meth:`pyspark.sql.functions.ceil`
+
+    Examples
+    --------
+    Example 1: Truncate toward zero to a given number of decimal places
+
+    >>> import pyspark.sql.functions as sf
+    >>> spark.range(1).select(sf.truncate(sf.lit(15.79), 
sf.lit(1)).alias("r")).collect()
+    [Row(r=15.7)]
+
+    Example 2: Truncation rounds toward zero for negative values
+
+    >>> import pyspark.sql.functions as sf
+    >>> spark.range(1).select(sf.truncate(sf.lit(-2.99), 
sf.lit(0)).alias("r")).collect()
+    [Row(r=-2.0)]
+
+    Example 3: The scale argument defaults to 0 when omitted
+
+    >>> import pyspark.sql.functions as sf
+    >>> 
spark.range(1).select(sf.truncate(sf.lit(1234.5678)).alias("r")).collect()
+    [Row(r=1234.0)]
+    """
+    if scale is None:
+        return _invoke_function_over_columns("truncate", col)
+    else:
+        scale = _enum_to_value(scale)
+        scale = lit(scale) if isinstance(scale, int) else scale
+        return _invoke_function_over_columns("truncate", col, scale)
+
+
 @_try_remote_functions
 def bround(col: "ColumnOrName", scale: Optional[Union[Column, int]] = None) -> 
Column:
     """
diff --git a/sql/api/src/main/scala/org/apache/spark/sql/functions.scala 
b/sql/api/src/main/scala/org/apache/spark/sql/functions.scala
index 98f83528a272..a0599b42d425 100644
--- a/sql/api/src/main/scala/org/apache/spark/sql/functions.scala
+++ b/sql/api/src/main/scala/org/apache/spark/sql/functions.scala
@@ -6069,6 +6069,53 @@ object functions {
    */
   def round(e: Column, scale: Column): Column = Column.fn("round", e, scale)
 
+  /**
+   * Truncates the value of `e` toward zero to 0 decimal places.
+   *
+   * @param e
+   *   the value to truncate. A column that evaluates to a numeric.
+   * @return
+   *   Returns a column of the same type as the input, except that a decimal 
input may return a
+   *   decimal of different precision and scale.
+   * @group math_funcs
+   * @since 4.4.0
+   */
+  def truncate(e: Column): Column = truncate(e, 0)
+
+  /**
+   * Truncates the value of `e` toward zero to `scale` decimal places when 
`scale` is greater than
+   * or equal to 0, or to the left of the decimal point when `scale` is less 
than 0.
+   *
+   * @param e
+   *   the value to truncate. A column that evaluates to a numeric.
+   * @param scale
+   *   the number of decimal places to keep. A column that evaluates to an 
integral. Must be a
+   *   constant.
+   * @return
+   *   Returns a column of the same type as the input, except that a decimal 
input may return a
+   *   decimal of different precision and scale.
+   * @group math_funcs
+   * @since 4.4.0
+   */
+  def truncate(e: Column, scale: Int): Column = Column.fn("truncate", e, 
lit(scale))
+
+  /**
+   * Truncates the value of `e` toward zero to `scale` decimal places when 
`scale` is greater than
+   * or equal to 0, or to the left of the decimal point when `scale` is less 
than 0.
+   *
+   * @param e
+   *   the value to truncate. A column that evaluates to a numeric.
+   * @param scale
+   *   the number of decimal places to keep. A column that evaluates to an 
integral. Must be a
+   *   constant.
+   * @return
+   *   Returns a column of the same type as the input, except that a decimal 
input may return a
+   *   decimal of different precision and scale.
+   * @group math_funcs
+   * @since 4.4.0
+   */
+  def truncate(e: Column, scale: Column): Column = Column.fn("truncate", e, 
scale)
+
   /**
    * Returns the value of the column `e` rounded to 0 decimal places with 
HALF_EVEN round mode.
    *
diff --git a/sql/api/src/main/scala/org/apache/spark/sql/types/Decimal.scala 
b/sql/api/src/main/scala/org/apache/spark/sql/types/Decimal.scala
index a41847f02c2a..a8de8e9d908a 100644
--- a/sql/api/src/main/scala/org/apache/spark/sql/types/Decimal.scala
+++ b/sql/api/src/main/scala/org/apache/spark/sql/types/Decimal.scala
@@ -407,7 +407,7 @@ final class Decimal extends Ordered[Decimal] with 
Serializable {
           lv = roundMode match {
             case ROUND_FLOOR => if (lv < 0) -1L else 0L
             case ROUND_CEILING => if (lv > 0) 1L else 0L
-            case ROUND_HALF_UP | ROUND_HALF_EVEN => 0L
+            case ROUND_HALF_UP | ROUND_HALF_EVEN | ROUND_DOWN => 0L
             case _ => throw DataTypeErrors.unsupportedRoundingMode(roundMode)
           }
         } else {
@@ -433,6 +433,8 @@ final class Decimal extends Ordered[Decimal] with 
Serializable {
               if (doubled > pow10diff || doubled == pow10diff && lv % 2 != 0) {
                 lv += (if (droppedDigits < 0) -1L else 1L)
               }
+            case ROUND_DOWN =>
+            // Truncation toward zero: `lv /= pow10diff` already dropped the 
fractional part.
             case _ =>
               throw DataTypeErrors.unsupportedRoundingMode(roundMode)
           }
@@ -578,6 +580,7 @@ object Decimal {
   val ROUND_HALF_EVEN = BigDecimal.RoundingMode.HALF_EVEN
   val ROUND_CEILING = BigDecimal.RoundingMode.CEILING
   val ROUND_FLOOR = BigDecimal.RoundingMode.FLOOR
+  val ROUND_DOWN = BigDecimal.RoundingMode.DOWN
 
   /** Maximum number of decimal digits an Int can represent */
   val MAX_INT_DIGITS = 9
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
index 08bf81b19c0c..7950f90016ad 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/FunctionRegistry.scala
@@ -473,6 +473,7 @@ object FunctionRegistry {
     expression[ToRadians]("radians"),
     expression[Rint]("rint"),
     expression[Round]("round"),
+    expression[Truncate]("truncate"),
     expression[ShiftLeft]("shiftleft"),
     expression[ShiftRight]("shiftright"),
     expression[ShiftRightUnsigned]("shiftrightunsigned"),
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/mathExpressions.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/mathExpressions.scala
index aed6b34716a7..0cf7b7db157a 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/mathExpressions.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/mathExpressions.scala
@@ -1971,6 +1971,51 @@ case class BRound(
     newLeft: Expression, newRight: Expression): BRound = copy(child = newLeft, 
scale = newRight)
 }
 
+/**
+ * Truncate an expression toward zero to `scale` decimal places.
+ * A negative `scale` truncates digits to the left of the decimal point.
+ * truncate(1234.5678, 2) = 1234.56, truncate(-1234.5678, 2) = -1234.56.
+ */
+// scalastyle:off line.size.limit
+@ExpressionDescription(
+  usage = "_FUNC_(expr[, scale]) - Returns `expr` truncated toward zero to 
`scale` decimal places. `scale` defaults to 0. A negative `scale` truncates 
digits to the left of the decimal point.",
+  arguments = """
+    Arguments:
+      * expr - The expression to truncate. An expression that evaluates to a 
numeric.
+      * scale - The number of decimal places to keep. It must be a constant 
integer expression and defaults to 0. A negative value truncates digits to the 
left of the decimal point.
+  """,
+  examples = """
+    Examples:
+      > SELECT _FUNC_(1234.5678, 2);
+       1234.56
+      > SELECT _FUNC_(1234.5678, -2);
+       1200
+      > SELECT _FUNC_(-1234.5678, 2);
+       -1234.56
+  """,
+  since = "4.4.0",
+  group = "math_funcs")
+// scalastyle:on line.size.limit
+case class Truncate(
+    child: Expression,
+    scale: Expression,
+    // Kept for symmetry with Round/BRound, which do need it. Truncation 
toward zero can never
+    // increase magnitude, so the ANSI overflow checks inherited from 
RoundBase never trigger.
+    override val ansiEnabled: Boolean = SQLConf.get.ansiEnabled)
+  // Also inherits RoundBase's one-digit decimal precision widening, needed 
for rounding modes
+  // that can carry (e.g. ceil(9.9, 0) = 10) but never exercised here since 
truncation cannot.
+  extends RoundBase(child, scale, BigDecimal.RoundingMode.DOWN, "ROUND_DOWN") {
+  def this(child: Expression) = this(child, Literal(0), 
SQLConf.get.ansiEnabled)
+
+  def this(child: Expression, scale: Expression) = this(child, scale, 
SQLConf.get.ansiEnabled)
+
+  override def flatArguments: Iterator[Any] = Iterator(child, scale)
+
+  override protected def withNewChildrenInternal(
+      newLeft: Expression, newRight: Expression): Truncate =
+    copy(child = newLeft, scale = newRight)
+}
+
 object WidthBucket {
   /** Shared by interpreted eval and generated Java code; must stay public for 
codegen. */
   def computeBucketNumber(value: Double, min: Double, max: Double, numBucket: 
Long): jl.Long = {
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/MathExpressionsSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/MathExpressionsSuite.scala
index 0ea3bc77ec1c..90148932237c 100644
--- 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/MathExpressionsSuite.scala
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/MathExpressionsSuite.scala
@@ -727,6 +727,27 @@ class MathExpressionsSuite extends SparkFunSuite with 
ExpressionEvalHelper {
     checkConsistencyBetweenInterpretedAndCodegen(Logarithm, DoubleType, 
DoubleType)
   }
 
+  test("truncate") {
+    // Truncation toward zero, distinct from floor/ceil (toward -inf/+inf) and 
round (to nearest).
+    checkEvaluation(Truncate(Literal(Decimal(BigDecimal("1234.5678"))), 
Literal(2)),
+      Decimal(BigDecimal("1234.56")))
+    checkEvaluation(Truncate(Literal(Decimal(BigDecimal("-1234.5678"))), 
Literal(2)),
+      Decimal(BigDecimal("-1234.56")))
+    checkEvaluation(Truncate(Literal(Decimal(BigDecimal("1234.5678"))), 
Literal(-2)),
+      Decimal(BigDecimal("1200")))
+    checkEvaluation(Truncate(Literal(Decimal(BigDecimal("-3.99"))), 
Literal(0)),
+      Decimal(BigDecimal("-3")))
+    // Default scale is 0.
+    checkEvaluation(new Truncate(Literal(Decimal(BigDecimal("3.99")))), 
Decimal(BigDecimal("3")))
+    // Integral input with negative scale.
+    checkEvaluation(Truncate(Literal(125), Literal(-1)), 120)
+    // Double input.
+    checkEvaluation(Truncate(Literal(3.1415926), Literal(3)), 3.141)
+    // Null propagation.
+    checkEvaluation(Truncate(Literal.create(null, DoubleType), Literal(2)), 
null)
+    checkEvaluation(Truncate(Literal(1.23), Literal.create(null, 
IntegerType)), null)
+  }
+
   test("round/bround/floor/ceil") {
     val scales = -6 to 6
     val doublePi: Double = math.Pi
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DecimalSuite.scala 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DecimalSuite.scala
index 080c6693280a..a987aa52e101 100644
--- a/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DecimalSuite.scala
+++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/types/DecimalSuite.scala
@@ -28,7 +28,8 @@ import org.apache.spark.unsafe.types.UTF8String
 
 class DecimalSuite extends SparkFunSuite with PrivateMethodTester with 
SQLHelper {
 
-  val allSupportedRoundModes = Seq(ROUND_HALF_UP, ROUND_HALF_EVEN, 
ROUND_CEILING, ROUND_FLOOR)
+  val allSupportedRoundModes =
+    Seq(ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_CEILING, ROUND_FLOOR, ROUND_DOWN)
 
   /** Check that a Decimal has the given string representation, precision and 
scale */
   private def checkDecimal(d: Decimal, string: String, precision: Int, scale: 
Int): Unit = {
diff --git 
a/sql/connect/client/jvm/src/test/scala/org/apache/spark/sql/PlanGenerationTestSuite.scala
 
b/sql/connect/client/jvm/src/test/scala/org/apache/spark/sql/PlanGenerationTestSuite.scala
index b3d8c160266a..04074accf5c8 100644
--- 
a/sql/connect/client/jvm/src/test/scala/org/apache/spark/sql/PlanGenerationTestSuite.scala
+++ 
b/sql/connect/client/jvm/src/test/scala/org/apache/spark/sql/PlanGenerationTestSuite.scala
@@ -1623,6 +1623,10 @@ class PlanGenerationTestSuite extends ConnectFunSuite 
with Logging {
     fn.round(fn.col("b"), 2)
   }
 
+  functionTest("truncate") {
+    fn.truncate(fn.col("b"), 2)
+  }
+
   functionTest("sec") {
     fn.sec(fn.col("b"))
   }
diff --git 
a/sql/connect/common/src/test/resources/query-tests/explain-results/function_truncate.explain
 
b/sql/connect/common/src/test/resources/query-tests/explain-results/function_truncate.explain
new file mode 100644
index 000000000000..6f07efb1efd8
--- /dev/null
+++ 
b/sql/connect/common/src/test/resources/query-tests/explain-results/function_truncate.explain
@@ -0,0 +1,2 @@
+Project [truncate(b#0, 2) AS truncate(b, 2)#0]
++- LocalRelation <empty>, [id#0L, a#0, b#0, d#0, e#0, f#0, g#0]
diff --git 
a/sql/connect/common/src/test/resources/query-tests/queries/function_truncate.json
 
b/sql/connect/common/src/test/resources/query-tests/queries/function_truncate.json
new file mode 100644
index 000000000000..48f46452c670
--- /dev/null
+++ 
b/sql/connect/common/src/test/resources/query-tests/queries/function_truncate.json
@@ -0,0 +1,81 @@
+{
+  "common": {
+    "planId": "1"
+  },
+  "project": {
+    "input": {
+      "common": {
+        "planId": "0"
+      },
+      "localRelation": {
+        "schema": 
"struct\u003cid:bigint,a:int,b:double,d:struct\u003cid:bigint,a:int,b:double\u003e,e:array\u003cint\u003e,f:map\u003cstring,struct\u003cid:bigint,a:int,b:double\u003e\u003e,g:string\u003e"
+      }
+    },
+    "expressions": [{
+      "unresolvedFunction": {
+        "functionName": "truncate",
+        "arguments": [{
+          "unresolvedAttribute": {
+            "unparsedIdentifier": "b"
+          },
+          "common": {
+            "origin": {
+              "jvmOrigin": {
+                "stackTrace": [{
+                  "classLoaderName": "app",
+                  "declaringClass": "org.apache.spark.sql.functions$",
+                  "methodName": "col",
+                  "fileName": "functions.scala"
+                }, {
+                  "classLoaderName": "app",
+                  "declaringClass": 
"org.apache.spark.sql.PlanGenerationTestSuite",
+                  "methodName": "~~trimmed~anonfun~~",
+                  "fileName": "PlanGenerationTestSuite.scala"
+                }]
+              }
+            }
+          }
+        }, {
+          "literal": {
+            "integer": 2
+          },
+          "common": {
+            "origin": {
+              "jvmOrigin": {
+                "stackTrace": [{
+                  "classLoaderName": "app",
+                  "declaringClass": "org.apache.spark.sql.functions$",
+                  "methodName": "truncate",
+                  "fileName": "functions.scala"
+                }, {
+                  "classLoaderName": "app",
+                  "declaringClass": 
"org.apache.spark.sql.PlanGenerationTestSuite",
+                  "methodName": "~~trimmed~anonfun~~",
+                  "fileName": "PlanGenerationTestSuite.scala"
+                }]
+              }
+            }
+          }
+        }],
+        "isInternal": false
+      },
+      "common": {
+        "origin": {
+          "jvmOrigin": {
+            "stackTrace": [{
+              "classLoaderName": "app",
+              "declaringClass": "org.apache.spark.sql.functions$",
+              "methodName": "truncate",
+              "fileName": "functions.scala"
+            }, {
+              "classLoaderName": "app",
+              "declaringClass": "org.apache.spark.sql.PlanGenerationTestSuite",
+              "methodName": "~~trimmed~anonfun~~",
+              "fileName": "PlanGenerationTestSuite.scala"
+            }]
+          }
+        }
+      }
+    }]
+  }
+}
\ No newline at end of file
diff --git 
a/sql/connect/common/src/test/resources/query-tests/queries/function_truncate.proto.bin
 
b/sql/connect/common/src/test/resources/query-tests/queries/function_truncate.proto.bin
new file mode 100644
index 000000000000..f9f634226752
Binary files /dev/null and 
b/sql/connect/common/src/test/resources/query-tests/queries/function_truncate.proto.bin
 differ
diff --git a/sql/core/src/test/resources/sql-functions/sql-expression-schema.md 
b/sql/core/src/test/resources/sql-functions/sql-expression-schema.md
index 76a28ee68ca9..3923f966ab23 100644
--- a/sql/core/src/test/resources/sql-functions/sql-expression-schema.md
+++ b/sql/core/src/test/resources/sql-functions/sql-expression-schema.md
@@ -393,6 +393,7 @@
 | org.apache.spark.sql.catalyst.expressions.TransformValues | transform_values 
| SELECT transform_values(map_from_arrays(array(1, 2, 3), array(1, 2, 3)), (k, 
v) -> v + 1) | struct<transform_values(map_from_arrays(array(1, 2, 3), array(1, 
2, 3)), lambdafunction((namedlambdavariable() + 1), namedlambdavariable(), 
namedlambdavariable())):map<int,int>> |
 | org.apache.spark.sql.catalyst.expressions.TruncDate | trunc | SELECT 
trunc('2019-08-04', 'week') | struct<trunc(2019-08-04, week):date> |
 | org.apache.spark.sql.catalyst.expressions.TruncTimestamp | date_trunc | 
SELECT date_trunc('YEAR', '2015-03-05T09:32:05.359') | struct<date_trunc(YEAR, 
2015-03-05T09:32:05.359):timestamp> |
+| org.apache.spark.sql.catalyst.expressions.Truncate | truncate | SELECT 
truncate(1234.5678, 2) | struct<truncate(1234.5678, 2):decimal(7,2)> |
 | org.apache.spark.sql.catalyst.expressions.TryAdd | try_add | SELECT 
try_add(1, 2) | struct<try_add(1, 2):int> |
 | org.apache.spark.sql.catalyst.expressions.TryAesDecrypt | try_aes_decrypt | 
SELECT 
try_aes_decrypt(unhex('6E7CA17BBB468D3084B5744BCA729FB7B2B7BCB8E4472847D02670489D95FA97DBBA7D3210'),
 '0000111122223333', 'GCM') | 
struct<try_aes_decrypt(unhex(6E7CA17BBB468D3084B5744BCA729FB7B2B7BCB8E4472847D02670489D95FA97DBBA7D3210),
 0000111122223333, GCM, DEFAULT, ):binary> |
 | org.apache.spark.sql.catalyst.expressions.TryDivide | try_divide | SELECT 
try_divide(3, 2) | struct<try_divide(3, 2):double> |
diff --git 
a/sql/core/src/test/resources/sql-tests/analyzer-results/math.sql.out 
b/sql/core/src/test/resources/sql-tests/analyzer-results/math.sql.out
index 1fa7b7513993..053740acf191 100644
--- a/sql/core/src/test/resources/sql-tests/analyzer-results/math.sql.out
+++ b/sql/core/src/test/resources/sql-tests/analyzer-results/math.sql.out
@@ -391,6 +391,307 @@ Project [bround(-9223372036854775808, -1) AS 
bround(-9223372036854775808, -1)#xL
 +- OneRowRelation
 
 
+-- !query
+SELECT truncate(25y, 1)
+-- !query analysis
+Project [truncate(25, 1) AS truncate(25, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(25y, 0)
+-- !query analysis
+Project [truncate(25, 0) AS truncate(25, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(25y, -1)
+-- !query analysis
+Project [truncate(25, -1) AS truncate(25, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(25y, -2)
+-- !query analysis
+Project [truncate(25, -2) AS truncate(25, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(25y, -3)
+-- !query analysis
+Project [truncate(25, -3) AS truncate(25, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-25y, 1)
+-- !query analysis
+Project [truncate(-25, 1) AS truncate(-25, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-25y, 0)
+-- !query analysis
+Project [truncate(-25, 0) AS truncate(-25, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-25y, -1)
+-- !query analysis
+Project [truncate(-25, -1) AS truncate(-25, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-25y, -2)
+-- !query analysis
+Project [truncate(-25, -2) AS truncate(-25, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-25y, -3)
+-- !query analysis
+Project [truncate(-25, -3) AS truncate(-25, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(127y, -1)
+-- !query analysis
+Project [truncate(127, -1) AS truncate(127, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-128y, -1)
+-- !query analysis
+Project [truncate(-128, -1) AS truncate(-128, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525s, 1)
+-- !query analysis
+Project [truncate(525, 1) AS truncate(525, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525s, 0)
+-- !query analysis
+Project [truncate(525, 0) AS truncate(525, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525s, -1)
+-- !query analysis
+Project [truncate(525, -1) AS truncate(525, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525s, -2)
+-- !query analysis
+Project [truncate(525, -2) AS truncate(525, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525s, -3)
+-- !query analysis
+Project [truncate(525, -3) AS truncate(525, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525s, 1)
+-- !query analysis
+Project [truncate(-525, 1) AS truncate(-525, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525s, 0)
+-- !query analysis
+Project [truncate(-525, 0) AS truncate(-525, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525s, -1)
+-- !query analysis
+Project [truncate(-525, -1) AS truncate(-525, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525s, -2)
+-- !query analysis
+Project [truncate(-525, -2) AS truncate(-525, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525s, -3)
+-- !query analysis
+Project [truncate(-525, -3) AS truncate(-525, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525, 1)
+-- !query analysis
+Project [truncate(525, 1) AS truncate(525, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525, 0)
+-- !query analysis
+Project [truncate(525, 0) AS truncate(525, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525, -1)
+-- !query analysis
+Project [truncate(525, -1) AS truncate(525, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525, -2)
+-- !query analysis
+Project [truncate(525, -2) AS truncate(525, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525, -3)
+-- !query analysis
+Project [truncate(525, -3) AS truncate(525, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525, 1)
+-- !query analysis
+Project [truncate(-525, 1) AS truncate(-525, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525, 0)
+-- !query analysis
+Project [truncate(-525, 0) AS truncate(-525, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525, -1)
+-- !query analysis
+Project [truncate(-525, -1) AS truncate(-525, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525, -2)
+-- !query analysis
+Project [truncate(-525, -2) AS truncate(-525, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525, -3)
+-- !query analysis
+Project [truncate(-525, -3) AS truncate(-525, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525L, 1)
+-- !query analysis
+Project [truncate(525, 1) AS truncate(525, 1)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525L, 0)
+-- !query analysis
+Project [truncate(525, 0) AS truncate(525, 0)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525L, -1)
+-- !query analysis
+Project [truncate(525, -1) AS truncate(525, -1)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525L, -2)
+-- !query analysis
+Project [truncate(525, -2) AS truncate(525, -2)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525L, -3)
+-- !query analysis
+Project [truncate(525, -3) AS truncate(525, -3)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525L, 1)
+-- !query analysis
+Project [truncate(-525, 1) AS truncate(-525, 1)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525L, 0)
+-- !query analysis
+Project [truncate(-525, 0) AS truncate(-525, 0)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525L, -1)
+-- !query analysis
+Project [truncate(-525, -1) AS truncate(-525, -1)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525L, -2)
+-- !query analysis
+Project [truncate(-525, -2) AS truncate(-525, -2)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525L, -3)
+-- !query analysis
+Project [truncate(-525, -3) AS truncate(-525, -3)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(1234.5678)
+-- !query analysis
+Project [truncate(1234.5678, 0) AS truncate(1234.5678, 0)#x]
++- OneRowRelation
+
+
 -- !query
 SELECT conv('100', 2, 10)
 -- !query analysis
diff --git 
a/sql/core/src/test/resources/sql-tests/analyzer-results/nonansi/math.sql.out 
b/sql/core/src/test/resources/sql-tests/analyzer-results/nonansi/math.sql.out
index 5fe1b69352f5..161477a44b5f 100644
--- 
a/sql/core/src/test/resources/sql-tests/analyzer-results/nonansi/math.sql.out
+++ 
b/sql/core/src/test/resources/sql-tests/analyzer-results/nonansi/math.sql.out
@@ -391,6 +391,307 @@ Project [bround(-9223372036854775808, -1) AS 
bround(-9223372036854775808, -1)#xL
 +- OneRowRelation
 
 
+-- !query
+SELECT truncate(25y, 1)
+-- !query analysis
+Project [truncate(25, 1) AS truncate(25, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(25y, 0)
+-- !query analysis
+Project [truncate(25, 0) AS truncate(25, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(25y, -1)
+-- !query analysis
+Project [truncate(25, -1) AS truncate(25, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(25y, -2)
+-- !query analysis
+Project [truncate(25, -2) AS truncate(25, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(25y, -3)
+-- !query analysis
+Project [truncate(25, -3) AS truncate(25, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-25y, 1)
+-- !query analysis
+Project [truncate(-25, 1) AS truncate(-25, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-25y, 0)
+-- !query analysis
+Project [truncate(-25, 0) AS truncate(-25, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-25y, -1)
+-- !query analysis
+Project [truncate(-25, -1) AS truncate(-25, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-25y, -2)
+-- !query analysis
+Project [truncate(-25, -2) AS truncate(-25, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-25y, -3)
+-- !query analysis
+Project [truncate(-25, -3) AS truncate(-25, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(127y, -1)
+-- !query analysis
+Project [truncate(127, -1) AS truncate(127, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-128y, -1)
+-- !query analysis
+Project [truncate(-128, -1) AS truncate(-128, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525s, 1)
+-- !query analysis
+Project [truncate(525, 1) AS truncate(525, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525s, 0)
+-- !query analysis
+Project [truncate(525, 0) AS truncate(525, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525s, -1)
+-- !query analysis
+Project [truncate(525, -1) AS truncate(525, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525s, -2)
+-- !query analysis
+Project [truncate(525, -2) AS truncate(525, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525s, -3)
+-- !query analysis
+Project [truncate(525, -3) AS truncate(525, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525s, 1)
+-- !query analysis
+Project [truncate(-525, 1) AS truncate(-525, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525s, 0)
+-- !query analysis
+Project [truncate(-525, 0) AS truncate(-525, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525s, -1)
+-- !query analysis
+Project [truncate(-525, -1) AS truncate(-525, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525s, -2)
+-- !query analysis
+Project [truncate(-525, -2) AS truncate(-525, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525s, -3)
+-- !query analysis
+Project [truncate(-525, -3) AS truncate(-525, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525, 1)
+-- !query analysis
+Project [truncate(525, 1) AS truncate(525, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525, 0)
+-- !query analysis
+Project [truncate(525, 0) AS truncate(525, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525, -1)
+-- !query analysis
+Project [truncate(525, -1) AS truncate(525, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525, -2)
+-- !query analysis
+Project [truncate(525, -2) AS truncate(525, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525, -3)
+-- !query analysis
+Project [truncate(525, -3) AS truncate(525, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525, 1)
+-- !query analysis
+Project [truncate(-525, 1) AS truncate(-525, 1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525, 0)
+-- !query analysis
+Project [truncate(-525, 0) AS truncate(-525, 0)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525, -1)
+-- !query analysis
+Project [truncate(-525, -1) AS truncate(-525, -1)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525, -2)
+-- !query analysis
+Project [truncate(-525, -2) AS truncate(-525, -2)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525, -3)
+-- !query analysis
+Project [truncate(-525, -3) AS truncate(-525, -3)#x]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525L, 1)
+-- !query analysis
+Project [truncate(525, 1) AS truncate(525, 1)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525L, 0)
+-- !query analysis
+Project [truncate(525, 0) AS truncate(525, 0)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525L, -1)
+-- !query analysis
+Project [truncate(525, -1) AS truncate(525, -1)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525L, -2)
+-- !query analysis
+Project [truncate(525, -2) AS truncate(525, -2)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(525L, -3)
+-- !query analysis
+Project [truncate(525, -3) AS truncate(525, -3)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525L, 1)
+-- !query analysis
+Project [truncate(-525, 1) AS truncate(-525, 1)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525L, 0)
+-- !query analysis
+Project [truncate(-525, 0) AS truncate(-525, 0)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525L, -1)
+-- !query analysis
+Project [truncate(-525, -1) AS truncate(-525, -1)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525L, -2)
+-- !query analysis
+Project [truncate(-525, -2) AS truncate(-525, -2)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(-525L, -3)
+-- !query analysis
+Project [truncate(-525, -3) AS truncate(-525, -3)#xL]
++- OneRowRelation
+
+
+-- !query
+SELECT truncate(1234.5678)
+-- !query analysis
+Project [truncate(1234.5678, 0) AS truncate(1234.5678, 0)#x]
++- OneRowRelation
+
+
 -- !query
 SELECT conv('100', 2, 10)
 -- !query analysis
diff --git a/sql/core/src/test/resources/sql-tests/inputs/math.sql 
b/sql/core/src/test/resources/sql-tests/inputs/math.sql
index 14a647a610cc..bd9a6b2c3de3 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/math.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/math.sql
@@ -70,6 +70,64 @@ SELECT bround(525L, -3);
 SELECT bround(9223372036854775807L, -1);
 SELECT bround(-9223372036854775808L, -1);
 
+-- Truncate with Byte input
+SELECT truncate(25y, 1);
+SELECT truncate(25y, 0);
+SELECT truncate(25y, -1);
+SELECT truncate(25y, -2);
+SELECT truncate(25y, -3);
+-- Truncate with negative Byte input: truncation is toward zero, unlike floor.
+SELECT truncate(-25y, 1);
+SELECT truncate(-25y, 0);
+SELECT truncate(-25y, -1);
+SELECT truncate(-25y, -2);
+SELECT truncate(-25y, -3);
+-- Truncate never overflows, unlike round: truncate(-128y, -1) is -120, not an 
overflow.
+SELECT truncate(127y, -1);
+SELECT truncate(-128y, -1);
+
+-- Truncate with short integer input
+SELECT truncate(525s, 1);
+SELECT truncate(525s, 0);
+SELECT truncate(525s, -1);
+SELECT truncate(525s, -2);
+SELECT truncate(525s, -3);
+-- Truncate with negative short integer input: truncation is toward zero, 
unlike floor.
+SELECT truncate(-525s, 1);
+SELECT truncate(-525s, 0);
+SELECT truncate(-525s, -1);
+SELECT truncate(-525s, -2);
+SELECT truncate(-525s, -3);
+
+-- Truncate with integer input
+SELECT truncate(525, 1);
+SELECT truncate(525, 0);
+SELECT truncate(525, -1);
+SELECT truncate(525, -2);
+SELECT truncate(525, -3);
+-- Truncate with negative integer input: truncation is toward zero, unlike 
floor.
+SELECT truncate(-525, 1);
+SELECT truncate(-525, 0);
+SELECT truncate(-525, -1);
+SELECT truncate(-525, -2);
+SELECT truncate(-525, -3);
+
+-- Truncate with big integer input
+SELECT truncate(525L, 1);
+SELECT truncate(525L, 0);
+SELECT truncate(525L, -1);
+SELECT truncate(525L, -2);
+SELECT truncate(525L, -3);
+-- Truncate with negative big integer input: truncation is toward zero, unlike 
floor.
+SELECT truncate(-525L, 1);
+SELECT truncate(-525L, 0);
+SELECT truncate(-525L, -1);
+SELECT truncate(-525L, -2);
+SELECT truncate(-525L, -3);
+
+-- Truncate with the scale argument omitted; it defaults to 0.
+SELECT truncate(1234.5678);
+
 -- Conv
 SELECT conv('100', 2, 10);
 SELECT conv(-10, 16, -10);
diff --git a/sql/core/src/test/resources/sql-tests/results/math.sql.out 
b/sql/core/src/test/resources/sql-tests/results/math.sql.out
index e2abcb099130..1f027d362cdf 100644
--- a/sql/core/src/test/resources/sql-tests/results/math.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/math.sql.out
@@ -703,6 +703,350 @@ org.apache.spark.SparkArithmeticException
 }
 
 
+-- !query
+SELECT truncate(25y, 1)
+-- !query schema
+struct<truncate(25, 1):tinyint>
+-- !query output
+25
+
+
+-- !query
+SELECT truncate(25y, 0)
+-- !query schema
+struct<truncate(25, 0):tinyint>
+-- !query output
+25
+
+
+-- !query
+SELECT truncate(25y, -1)
+-- !query schema
+struct<truncate(25, -1):tinyint>
+-- !query output
+20
+
+
+-- !query
+SELECT truncate(25y, -2)
+-- !query schema
+struct<truncate(25, -2):tinyint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(25y, -3)
+-- !query schema
+struct<truncate(25, -3):tinyint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(-25y, 1)
+-- !query schema
+struct<truncate(-25, 1):tinyint>
+-- !query output
+-25
+
+
+-- !query
+SELECT truncate(-25y, 0)
+-- !query schema
+struct<truncate(-25, 0):tinyint>
+-- !query output
+-25
+
+
+-- !query
+SELECT truncate(-25y, -1)
+-- !query schema
+struct<truncate(-25, -1):tinyint>
+-- !query output
+-20
+
+
+-- !query
+SELECT truncate(-25y, -2)
+-- !query schema
+struct<truncate(-25, -2):tinyint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(-25y, -3)
+-- !query schema
+struct<truncate(-25, -3):tinyint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(127y, -1)
+-- !query schema
+struct<truncate(127, -1):tinyint>
+-- !query output
+120
+
+
+-- !query
+SELECT truncate(-128y, -1)
+-- !query schema
+struct<truncate(-128, -1):tinyint>
+-- !query output
+-120
+
+
+-- !query
+SELECT truncate(525s, 1)
+-- !query schema
+struct<truncate(525, 1):smallint>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525s, 0)
+-- !query schema
+struct<truncate(525, 0):smallint>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525s, -1)
+-- !query schema
+struct<truncate(525, -1):smallint>
+-- !query output
+520
+
+
+-- !query
+SELECT truncate(525s, -2)
+-- !query schema
+struct<truncate(525, -2):smallint>
+-- !query output
+500
+
+
+-- !query
+SELECT truncate(525s, -3)
+-- !query schema
+struct<truncate(525, -3):smallint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(-525s, 1)
+-- !query schema
+struct<truncate(-525, 1):smallint>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525s, 0)
+-- !query schema
+struct<truncate(-525, 0):smallint>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525s, -1)
+-- !query schema
+struct<truncate(-525, -1):smallint>
+-- !query output
+-520
+
+
+-- !query
+SELECT truncate(-525s, -2)
+-- !query schema
+struct<truncate(-525, -2):smallint>
+-- !query output
+-500
+
+
+-- !query
+SELECT truncate(-525s, -3)
+-- !query schema
+struct<truncate(-525, -3):smallint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(525, 1)
+-- !query schema
+struct<truncate(525, 1):int>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525, 0)
+-- !query schema
+struct<truncate(525, 0):int>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525, -1)
+-- !query schema
+struct<truncate(525, -1):int>
+-- !query output
+520
+
+
+-- !query
+SELECT truncate(525, -2)
+-- !query schema
+struct<truncate(525, -2):int>
+-- !query output
+500
+
+
+-- !query
+SELECT truncate(525, -3)
+-- !query schema
+struct<truncate(525, -3):int>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(-525, 1)
+-- !query schema
+struct<truncate(-525, 1):int>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525, 0)
+-- !query schema
+struct<truncate(-525, 0):int>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525, -1)
+-- !query schema
+struct<truncate(-525, -1):int>
+-- !query output
+-520
+
+
+-- !query
+SELECT truncate(-525, -2)
+-- !query schema
+struct<truncate(-525, -2):int>
+-- !query output
+-500
+
+
+-- !query
+SELECT truncate(-525, -3)
+-- !query schema
+struct<truncate(-525, -3):int>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(525L, 1)
+-- !query schema
+struct<truncate(525, 1):bigint>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525L, 0)
+-- !query schema
+struct<truncate(525, 0):bigint>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525L, -1)
+-- !query schema
+struct<truncate(525, -1):bigint>
+-- !query output
+520
+
+
+-- !query
+SELECT truncate(525L, -2)
+-- !query schema
+struct<truncate(525, -2):bigint>
+-- !query output
+500
+
+
+-- !query
+SELECT truncate(525L, -3)
+-- !query schema
+struct<truncate(525, -3):bigint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(-525L, 1)
+-- !query schema
+struct<truncate(-525, 1):bigint>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525L, 0)
+-- !query schema
+struct<truncate(-525, 0):bigint>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525L, -1)
+-- !query schema
+struct<truncate(-525, -1):bigint>
+-- !query output
+-520
+
+
+-- !query
+SELECT truncate(-525L, -2)
+-- !query schema
+struct<truncate(-525, -2):bigint>
+-- !query output
+-500
+
+
+-- !query
+SELECT truncate(-525L, -3)
+-- !query schema
+struct<truncate(-525, -3):bigint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(1234.5678)
+-- !query schema
+struct<truncate(1234.5678, 0):decimal(5,0)>
+-- !query output
+1234
+
+
 -- !query
 SELECT conv('100', 2, 10)
 -- !query schema
diff --git a/sql/core/src/test/resources/sql-tests/results/nonansi/math.sql.out 
b/sql/core/src/test/resources/sql-tests/results/nonansi/math.sql.out
index 09f438393328..5ab0677bfd7c 100644
--- a/sql/core/src/test/resources/sql-tests/results/nonansi/math.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/nonansi/math.sql.out
@@ -447,6 +447,350 @@ struct<bround(-9223372036854775808, -1):bigint>
 9223372036854775806
 
 
+-- !query
+SELECT truncate(25y, 1)
+-- !query schema
+struct<truncate(25, 1):tinyint>
+-- !query output
+25
+
+
+-- !query
+SELECT truncate(25y, 0)
+-- !query schema
+struct<truncate(25, 0):tinyint>
+-- !query output
+25
+
+
+-- !query
+SELECT truncate(25y, -1)
+-- !query schema
+struct<truncate(25, -1):tinyint>
+-- !query output
+20
+
+
+-- !query
+SELECT truncate(25y, -2)
+-- !query schema
+struct<truncate(25, -2):tinyint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(25y, -3)
+-- !query schema
+struct<truncate(25, -3):tinyint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(-25y, 1)
+-- !query schema
+struct<truncate(-25, 1):tinyint>
+-- !query output
+-25
+
+
+-- !query
+SELECT truncate(-25y, 0)
+-- !query schema
+struct<truncate(-25, 0):tinyint>
+-- !query output
+-25
+
+
+-- !query
+SELECT truncate(-25y, -1)
+-- !query schema
+struct<truncate(-25, -1):tinyint>
+-- !query output
+-20
+
+
+-- !query
+SELECT truncate(-25y, -2)
+-- !query schema
+struct<truncate(-25, -2):tinyint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(-25y, -3)
+-- !query schema
+struct<truncate(-25, -3):tinyint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(127y, -1)
+-- !query schema
+struct<truncate(127, -1):tinyint>
+-- !query output
+120
+
+
+-- !query
+SELECT truncate(-128y, -1)
+-- !query schema
+struct<truncate(-128, -1):tinyint>
+-- !query output
+-120
+
+
+-- !query
+SELECT truncate(525s, 1)
+-- !query schema
+struct<truncate(525, 1):smallint>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525s, 0)
+-- !query schema
+struct<truncate(525, 0):smallint>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525s, -1)
+-- !query schema
+struct<truncate(525, -1):smallint>
+-- !query output
+520
+
+
+-- !query
+SELECT truncate(525s, -2)
+-- !query schema
+struct<truncate(525, -2):smallint>
+-- !query output
+500
+
+
+-- !query
+SELECT truncate(525s, -3)
+-- !query schema
+struct<truncate(525, -3):smallint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(-525s, 1)
+-- !query schema
+struct<truncate(-525, 1):smallint>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525s, 0)
+-- !query schema
+struct<truncate(-525, 0):smallint>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525s, -1)
+-- !query schema
+struct<truncate(-525, -1):smallint>
+-- !query output
+-520
+
+
+-- !query
+SELECT truncate(-525s, -2)
+-- !query schema
+struct<truncate(-525, -2):smallint>
+-- !query output
+-500
+
+
+-- !query
+SELECT truncate(-525s, -3)
+-- !query schema
+struct<truncate(-525, -3):smallint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(525, 1)
+-- !query schema
+struct<truncate(525, 1):int>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525, 0)
+-- !query schema
+struct<truncate(525, 0):int>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525, -1)
+-- !query schema
+struct<truncate(525, -1):int>
+-- !query output
+520
+
+
+-- !query
+SELECT truncate(525, -2)
+-- !query schema
+struct<truncate(525, -2):int>
+-- !query output
+500
+
+
+-- !query
+SELECT truncate(525, -3)
+-- !query schema
+struct<truncate(525, -3):int>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(-525, 1)
+-- !query schema
+struct<truncate(-525, 1):int>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525, 0)
+-- !query schema
+struct<truncate(-525, 0):int>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525, -1)
+-- !query schema
+struct<truncate(-525, -1):int>
+-- !query output
+-520
+
+
+-- !query
+SELECT truncate(-525, -2)
+-- !query schema
+struct<truncate(-525, -2):int>
+-- !query output
+-500
+
+
+-- !query
+SELECT truncate(-525, -3)
+-- !query schema
+struct<truncate(-525, -3):int>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(525L, 1)
+-- !query schema
+struct<truncate(525, 1):bigint>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525L, 0)
+-- !query schema
+struct<truncate(525, 0):bigint>
+-- !query output
+525
+
+
+-- !query
+SELECT truncate(525L, -1)
+-- !query schema
+struct<truncate(525, -1):bigint>
+-- !query output
+520
+
+
+-- !query
+SELECT truncate(525L, -2)
+-- !query schema
+struct<truncate(525, -2):bigint>
+-- !query output
+500
+
+
+-- !query
+SELECT truncate(525L, -3)
+-- !query schema
+struct<truncate(525, -3):bigint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(-525L, 1)
+-- !query schema
+struct<truncate(-525, 1):bigint>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525L, 0)
+-- !query schema
+struct<truncate(-525, 0):bigint>
+-- !query output
+-525
+
+
+-- !query
+SELECT truncate(-525L, -1)
+-- !query schema
+struct<truncate(-525, -1):bigint>
+-- !query output
+-520
+
+
+-- !query
+SELECT truncate(-525L, -2)
+-- !query schema
+struct<truncate(-525, -2):bigint>
+-- !query output
+-500
+
+
+-- !query
+SELECT truncate(-525L, -3)
+-- !query schema
+struct<truncate(-525, -3):bigint>
+-- !query output
+0
+
+
+-- !query
+SELECT truncate(1234.5678)
+-- !query schema
+struct<truncate(1234.5678, 0):decimal(5,0)>
+-- !query output
+1234
+
+
 -- !query
 SELECT conv('100', 2, 10)
 -- !query schema
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/MathFunctionsSuite.scala 
b/sql/core/src/test/scala/org/apache/spark/sql/MathFunctionsSuite.scala
index f7b27c205293..3ce1940e10e7 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/MathFunctionsSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/MathFunctionsSuite.scala
@@ -324,6 +324,46 @@ class MathFunctionsSuite extends SharedSparkSession {
     testOneToOneMathFunction(rint, math.rint)
   }
 
+  test("truncate") {
+    val df = Seq(5, 55, 555).map(Tuple1(_)).toDF("a")
+    checkAnswer(
+      df.select(truncate($"a", lit(-1)), truncate($"a", lit(-2))),
+      Seq(Row(0, 0), Row(50, 0), Row(550, 500))
+    )
+    // Truncation rounds toward zero, unlike floor for negative values.
+    val df2 = Seq(1234.5678, -1234.5678).map(Tuple1(_)).toDF("a")
+    checkAnswer(
+      df2.select(truncate($"a", lit(2))),
+      Seq(Row(1234.56), Row(-1234.56))
+    )
+    checkAnswer(
+      df2.selectExpr("truncate(a, 2)"),
+      Seq(Row(1234.56), Row(-1234.56))
+    )
+    // The truncate(Column, Int) and truncate(Column) overloads.
+    checkAnswer(
+      df2.select(truncate($"a", 2)),
+      Seq(Row(1234.56), Row(-1234.56))
+    )
+    checkAnswer(
+      df2.select(truncate($"a")),
+      Seq(Row(1234.0), Row(-1234.0))
+    )
+    // Decimal input, the type that motivated the new Decimal.changePrecision 
rounding mode.
+    val df3 = Seq(BigDecimal("1234.5678"), 
BigDecimal("-1234.5678")).map(Tuple1(_)).toDF("a")
+    checkAnswer(
+      df3.select(truncate($"a", lit(2))),
+      Seq(Row(BigDecimal("1234.56")), Row(BigDecimal("-1234.56")))
+    )
+    // NaN and Infinity are returned unchanged, matching RoundBase's other 
rounding modes.
+    val df4 = Seq(Double.NaN, Double.PositiveInfinity, Double.NegativeInfinity)
+      .map(Tuple1(_)).toDF("a")
+    checkAnswer(
+      df4.select(truncate($"a", lit(2))),
+      Seq(Row(Double.NaN), Row(Double.PositiveInfinity), 
Row(Double.NegativeInfinity))
+    )
+  }
+
   test("round/bround/ceil/floor") {
     val df = Seq(5, 55, 555).map(Tuple1(_)).toDF("a")
     checkAnswer(


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

Reply via email to