This is an automated email from the ASF dual-hosted git repository.
uros-b pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 8e8f37fd6ad0 [SPARK-60542][SQL] Support nanosecond-precision
timestamps in percentile, median, percentile_cont and percentile_disc
8e8f37fd6ad0 is described below
commit 8e8f37fd6ad0cf655cd101a834ce8202f23f1022
Author: Stevo Mitric <[email protected]>
AuthorDate: Wed Oct 28 08:42:45 2026 +0000
[SPARK-60542][SQL] Support nanosecond-precision timestamps in percentile,
median, percentile_cont and percentile_disc
### What changes were proposed in this pull request?
This PR makes the exact percentile aggregates `percentile`, `median`,
`percentile_cont` and `percentile_disc` accept nanosecond-precision timestamps
(`TIMESTAMP_NTZ(p)` / `TIMESTAMP_LTZ(p)`, p in [7, 9]) and return the input
type, family and precision. It's part of the nanosecond timestamp SPIP
(SPARK-56822).
- `AnyTimestampNanoType` is appended last to the input `TypeCollection`. A
nanos input matches it directly, so no cast is inserted. Because it's last, the
implicit-cast fallback for every other input type picks the same member as
before.
- `percentile_disc` returns an input value, so the result is exact.
- `percentile`, `median` and `percentile_cont` interpolate `lower +
fraction * (higher - lower)` exactly in epoch nanoseconds using
`java.math.BigDecimal`, then floor the result to the input precision (100 ns
for p=7, 10 ns for p=8). A double can't do this: epoch nanoseconds need about
61 bits, and the 0001..9999 span overflows a long. The position `percentage *
(count - 1)` is computed exactly too, because a double can land just below a
whole number (`90 * 0.7` is `62.99999999999999` [...]
- `percentile_cont` over a nanos timestamp is not pushed down to DS V2
sources, which would interpolate with their own rounding. `percentile_disc`
still is, since it returns an input value.
Design decision for reviewers: an interpolated value that falls between two
grid points is floored toward -inf. For example, the median of `.000000001` and
`.000000002` at p=9 is `.000000001`. Flooring matches how nanos values are
already stored at a lower precision, and it keeps the result between the two
neighboring values for both ascending and descending order. The TIME and
interval paths truncate toward zero with `.toLong`. That's the same thing for
TIME, which is never negative, [...]
### Why are the changes needed?
Since SPARK-60335 stopped the implicit narrowing, these functions reject a
nanos timestamp with `DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE` in both ANSI
modes; before that, ANSI silently cast it to `TIME(6)`. The SPIP asks for the
supported functions to work at nanosecond precision, and the exact percentile
family can do so without losing any digits.
### Does this PR introduce _any_ user-facing change?
Yes, for the nanosecond timestamp preview types only
(`spark.sql.timestampNanosTypes.enabled`). These functions now return a nanos
timestamp of the input type instead of a type-mismatch error. The
`UNEXPECTED_INPUT_TYPE` message for other unsupported inputs now also lists the
nanos types. Microsecond timestamps behave as before.
### How was this patch tested?
- `PercentileSuite`: result types, exact interpolation (sub-microsecond,
pre-epoch, the 0001..9999 range, p=7/8 flooring, non-dyadic percentages,
ascending vs descending), `percentile_disc` (current and legacy calculation),
and serialized buffer merge.
- `TimestampNanosFunctionsAnsiOnSuite` / `AnsiOffSuite`: SQL and Column API
results and types for NTZ/LTZ at p=7/8/9 over multiple partitions.
`TypeCoercionSuite` / `AnsiTypeCoercionSuite`: nanos input is not cast and
micro timestamps are never widened to nanos. `DataSourceV2StrategySuite`: no
pushdown of `percentile_cont` over nanos.
- `timestamp-ntz-nanos.sql` / `timestamp-ltz-nanos.sql` golden files,
including window, frequency and ANSI-off cases; the four functions move out of
the SPARK-60335 rejection block.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Isaac
Closes #59781 from stevomitric/stevomitric/nanos-percentile.
Authored-by: Stevo Mitric <[email protected]>
Signed-off-by: Uros Bojanic <[email protected]>
---
python/pyspark/sql/functions/builtin.py | 2 +-
.../expressions/aggregate/percentiles.scala | 113 +++++--
.../sql/catalyst/util/V2ExpressionBuilder.scala | 9 +-
.../sql/catalyst/analysis/TypeCoercionSuite.scala | 21 +-
.../expressions/aggregate/PercentileSuite.scala | 140 ++++++++-
.../analyzer-results/timestamp-ltz-nanos.sql.out | 292 ++++++------------
.../analyzer-results/timestamp-ntz-nanos.sql.out | 316 ++++++++------------
.../sql-tests/inputs/timestamp-ltz-nanos.sql | 55 +++-
.../sql-tests/inputs/timestamp-ntz-nanos.sql | 63 +++-
.../sql-tests/results/timestamp-ltz-nanos.sql.out | 306 ++++++-------------
.../sql-tests/results/timestamp-ntz-nanos.sql.out | 329 ++++++++-------------
.../sql/TimestampNanosFunctionsSuiteBase.scala | 76 +++++
.../datasources/v2/DataSourceV2StrategySuite.scala | 15 +-
13 files changed, 851 insertions(+), 886 deletions(-)
diff --git a/python/pyspark/sql/functions/builtin.py
b/python/pyspark/sql/functions/builtin.py
index 0ec168874b55..d4941b890bdb 100644
--- a/python/pyspark/sql/functions/builtin.py
+++ b/python/pyspark/sql/functions/builtin.py
@@ -1871,7 +1871,7 @@ def median(col: "ColumnOrName") -> Column:
----------
col : :class:`~pyspark.sql.Column` or column name
target column to compute on.
- A column that evaluates to a numeric, interval, or time.
+ A column that evaluates to a numeric, interval, time, or
nanosecond-precision timestamp.
Returns
-------
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/aggregate/percentiles.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/aggregate/percentiles.scala
index 4427dc25097e..56ca77d64d4d 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/aggregate/percentiles.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/aggregate/percentiles.scala
@@ -17,6 +17,7 @@
package org.apache.spark.sql.catalyst.expressions.aggregate
+import java.math.{BigDecimal => JBigDecimal, RoundingMode}
import java.util
import org.apache.spark.sql.catalyst.InternalRow
@@ -31,6 +32,7 @@ import org.apache.spark.sql.errors.{QueryCompilationErrors,
QueryExecutionErrors
import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.types._
import org.apache.spark.sql.types.TypeCollection.NumericAndAnsiInterval
+import org.apache.spark.unsafe.types.TimestampNanosVal
import org.apache.spark.util.ArrayImplicits._
import org.apache.spark.util.collection.OpenHashMap
@@ -58,6 +60,10 @@ abstract class PercentileBase
case arrayData: ArrayData => arrayData.toDoubleArray()
}
+ // Nanosecond timestamps are interpolated exactly, at an exact position.
+ @transient
+ private lazy val isNanos = child.dataType.isInstanceOf[AnyTimestampNanoType]
+
// Returns null for empty inputs
override def nullable: Boolean = true
@@ -66,6 +72,7 @@ abstract class PercentileBase
case _: YearMonthIntervalType => YearMonthIntervalType()
case _: DayTimeIntervalType => DayTimeIntervalType()
case t: TimeType => t
+ case t: AnyTimestampNanoType => t
case _ => DoubleType
}
if (returnPercentileArray) ArrayType(resultType, false) else resultType
@@ -76,7 +83,11 @@ abstract class PercentileBase
case _: ArrayType => ArrayType(DoubleType, false)
case _ => DoubleType
}
- Seq(TypeCollection(NumericAndAnsiInterval, AnyTimeType),
percentageExpType, IntegralType)
+ // AnyTimestampNanoType goes last, so the implicit cast fallback for other
inputs is unchanged.
+ Seq(
+ TypeCollection(NumericAndAnsiInterval, AnyTimeType,
AnyTimestampNanoType),
+ percentageExpType,
+ IntegralType)
}
// Check the inputTypes are valid, and the percentageExpression satisfies:
@@ -121,6 +132,9 @@ abstract class PercentileBase
case n: Number => n.doubleValue
}
+ // Nanosecond timestamps are kept as is, so that no sub-microsecond digit is
lost.
+ protected def toPercentileValue(key: AnyRef): Any = if (isNanos) key else
toDoubleValue(key)
+
override def update(
buffer: OpenHashMap[AnyRef, Long],
input: InternalRow): OpenHashMap[AnyRef, Long] = {
@@ -154,7 +168,7 @@ abstract class PercentileBase
generateOutput(getPercentiles(buffer))
}
- private def getPercentiles(buffer: OpenHashMap[AnyRef, Long]): Seq[Double] =
{
+ private def getPercentiles(buffer: OpenHashMap[AnyRef, Long]): Seq[Any] = {
if (buffer.isEmpty) {
return Seq.empty
}
@@ -172,11 +186,11 @@ abstract class PercentileBase
percentages.map(getPercentile(accumulatedCounts, _)).toImmutableArraySeq
}
- private def generateOutput(percentiles: Seq[Double]): Any = {
+ private def generateOutput(percentiles: Seq[Any]): Any = {
val results = child.dataType match {
- case _: YearMonthIntervalType => percentiles.map(_.toInt)
- case _: DayTimeIntervalType => percentiles.map(_.toLong)
- case _: TimeType => percentiles.map(_.toLong)
+ case _: YearMonthIntervalType =>
percentiles.map(_.asInstanceOf[Double].toInt)
+ case _: DayTimeIntervalType =>
percentiles.map(_.asInstanceOf[Double].toLong)
+ case _: TimeType => percentiles.map(_.asInstanceOf[Double].toLong)
case _ => percentiles
}
if (percentiles.isEmpty) {
@@ -195,12 +209,21 @@ abstract class PercentileBase
*/
protected def getPercentile(
accumulatedCounts: Seq[(AnyRef, Long)],
- percentile: Double): Double = {
+ percentile: Double): Any = {
val position = (accumulatedCounts.last._2 - 1) * percentile
+ // A double position can land just below a whole number (e.g. 90 * 0.7),
which flooring a
+ // nanosecond result would turn into a whole step. A NaN percentage keeps
the double position
+ // (the first value), as for other types.
+ lazy val exactPosition = JBigDecimal.valueOf(accumulatedCounts.last._2 - 1)
+ .multiply(JBigDecimal.valueOf(percentile))
// We may need to do linear interpolation to get the exact percentile
- val lower = position.floor.toLong
- val higher = position.ceil.toLong
+ val (lower, higher) = if (isNanos && !percentile.isNaN) {
+ (exactPosition.setScale(0, RoundingMode.FLOOR).longValueExact(),
+ exactPosition.setScale(0, RoundingMode.CEILING).longValueExact())
+ } else {
+ (position.floor.toLong, position.ceil.toLong)
+ }
// Use binary search to find the lower and the higher position.
val countsArray = accumulatedCounts.map(_._2).toArray[Long]
@@ -210,18 +233,22 @@ abstract class PercentileBase
val lowerKey = accumulatedCounts(lowerIndex)._1
if (higher == lower) {
// no interpolation needed because position does not have a fraction
- return toDoubleValue(lowerKey)
+ return toPercentileValue(lowerKey)
}
val higherKey = accumulatedCounts(higherIndex)._1
if (higherKey == lowerKey) {
// no interpolation needed because lower position and higher position
has the same key
- return toDoubleValue(lowerKey)
+ return toPercentileValue(lowerKey)
}
if (discrete) {
// We end up here only if spark.sql.legacy.percentileDiscCalculation=true
- toDoubleValue(lowerKey)
+ toPercentileValue(lowerKey)
+ } else if (isNanos) {
+ interpolateNanos(lowerKey.asInstanceOf[TimestampNanosVal],
+ higherKey.asInstanceOf[TimestampNanosVal],
+ exactPosition.subtract(JBigDecimal.valueOf(lower)))
} else {
// Linear interpolation to get the exact percentile. Computed as lower +
fraction *
// (higher - lower) to stay monotonic; the delta can overflow to
Infinity only for adjacent
@@ -232,6 +259,28 @@ abstract class PercentileBase
}
}
+ // Exact `lower + fraction * (higher - lower)` in epoch nanoseconds (a
double can't hold them),
+ // floored to the input precision.
+ private def interpolateNanos(
+ lower: TimestampNanosVal,
+ higher: TimestampNanosVal,
+ fraction: JBigDecimal): TimestampNanosVal = {
+ import PercentileBase.NANOS_PER_MICRO
+ def toEpochNanos(v: TimestampNanosVal): JBigDecimal =
+ JBigDecimal.valueOf(v.epochMicros).multiply(NANOS_PER_MICRO).add(
+ JBigDecimal.valueOf(v.nanosWithinMicro.toLong))
+ val lowerNanos = toEpochNanos(lower)
+ val nanos = toEpochNanos(higher).subtract(lowerNanos)
+ .multiply(fraction)
+ .add(lowerNanos)
+ .setScale(0, RoundingMode.FLOOR)
+ val micros = nanos.divide(NANOS_PER_MICRO, 0, RoundingMode.FLOOR)
+ val nanosWithinMicro =
nanos.subtract(micros.multiply(NANOS_PER_MICRO)).intValueExact()
+ DateTimeUtils.truncateTimestampNanosToPrecision(
+ TimestampNanosVal.fromParts(micros.longValueExact(),
nanosWithinMicro.toShort),
+ TimestampFamily.fractionalPrecision(child.dataType).get)
+ }
+
/**
* use a binary search to find the index of the position closest to the
current value.
*/
@@ -244,6 +293,10 @@ abstract class PercentileBase
}
}
+object PercentileBase {
+ private val NANOS_PER_MICRO =
JBigDecimal.valueOf(DateTimeConstants.NANOS_PER_MICROS)
+}
+
/**
* The Percentile aggregate function returns the exact percentile(s) of
numeric column `expr` at
* the given percentage(s) with value range in [0.0, 1.0].
@@ -262,8 +315,9 @@ abstract class PercentileBase
usage =
"""
_FUNC_(col, percentage [, frequency]) - Returns the exact percentile
value of numeric,
- ANSI interval or TIME column `col` at the given percentage. The value
of percentage must be
- between 0.0 and 1.0. The value of frequency should be positive integral
+ ANSI interval, TIME or nanosecond-precision timestamp column `col` at
the given percentage.
+ The value of percentage must be between 0.0 and 1.0. The value of
frequency should be
+ positive integral
_FUNC_(col, array(percentage1 [, percentage2]...) [, frequency]) -
Returns the exact
percentile value array of numeric column `col` at the given
percentage(s). Each value
@@ -274,7 +328,8 @@ abstract class PercentileBase
arguments = """
Arguments:
* col - The column to compute the percentile of.
- An expression that evaluates to a numeric, ANSI interval, or time.
+ An expression that evaluates to a numeric, ANSI interval, time, or
nanosecond-precision
+ timestamp (TIMESTAMP_LTZ(p) / TIMESTAMP_NTZ(p), p in [7, 9]).
* percentage - The percentile(s) to compute, each between 0.0 and 1.0.
Either a
single numeric value or an array of numeric values. Must be foldable.
* frequency - Optional. The number of times each value should be counted.
@@ -343,11 +398,13 @@ case class Percentile(
}
@ExpressionDescription(
- usage = "_FUNC_(col) - Returns the median of numeric, ANSI interval or TIME
column `col`.",
+ usage = "_FUNC_(col) - Returns the median of numeric, ANSI interval, TIME or
" +
+ "nanosecond-precision timestamp column `col`.",
arguments = """
Arguments:
* col - The column to compute the median of.
- An expression that evaluates to a numeric, interval, or time.
+ An expression that evaluates to a numeric, interval, time, or
nanosecond-precision
+ timestamp (TIMESTAMP_LTZ(p) / TIMESTAMP_NTZ(p), p in [7, 9]).
""",
examples = """
Examples:
@@ -372,8 +429,8 @@ case class Median(child: Expression)
}
/**
- * Return a percentile value based on a continuous distribution of
- * numeric, ANSI interval or TIME column at the given percentage (specified in
ORDER BY clause).
+ * Return a percentile value based on a continuous distribution of numeric,
ANSI interval, TIME or
+ * nanosecond-precision timestamp column at the given percentage (specified in
ORDER BY clause).
* The value of percentage must be between 0.0 and 1.0.
*/
case class PercentileCont(left: Expression, right: Expression, reverse:
Boolean = false)
@@ -473,7 +530,7 @@ case class PercentileDisc(
override protected def getPercentile(
accumulatedCounts: Seq[(AnyRef, Long)],
- percentile: Double): Double = {
+ percentile: Double): Any = {
if (legacyCalculation) {
super.getPercentile(accumulatedCounts, percentile)
} else {
@@ -488,7 +545,7 @@ case class PercentileDisc(
val higherIndex = binarySearchCount(countsArray, 0,
accumulatedCounts.size, higher)
val higherKey = accumulatedCounts(higherIndex)._1
- toDoubleValue(higherKey)
+ toPercentileValue(higherKey)
}
}
@@ -500,13 +557,14 @@ case class PercentileDisc(
// scalastyle:off line.size.limit
@ExpressionDescription(
usage = "_FUNC_(percentage) WITHIN GROUP (ORDER BY col) - Return a
percentile value based on " +
- "a continuous distribution of numeric, ANSI interval or TIME column `col`
at the given " +
- "`percentage` (specified in ORDER BY clause).",
+ "a continuous distribution of numeric, ANSI interval, TIME or
nanosecond-precision timestamp " +
+ "column `col` at the given `percentage` (specified in ORDER BY clause).",
arguments = """
Arguments:
* percentage - The percentile to compute, between 0.0 and 1.0. Must be
foldable.
* col - The column to compute the percentile of, specified in the ORDER
BY clause.
- An expression that evaluates to a numeric, ANSI interval, or time.
+ An expression that evaluates to a numeric, ANSI interval, time, or
nanosecond-precision
+ timestamp (TIMESTAMP_LTZ(p) / TIMESTAMP_NTZ(p), p in [7, 9]).
""",
examples = """
Examples:
@@ -532,13 +590,14 @@ object PercentileContBuilder extends ExpressionBuilder {
// scalastyle:off line.size.limit
@ExpressionDescription(
usage = "_FUNC_(percentage) WITHIN GROUP (ORDER BY col) - Return a
percentile value based on " +
- "a discrete distribution of numeric, ANSI interval or TIME column `col` at
the given " +
- "`percentage` (specified in ORDER BY clause).",
+ "a discrete distribution of numeric, ANSI interval, TIME or
nanosecond-precision timestamp " +
+ "column `col` at the given `percentage` (specified in ORDER BY clause).",
arguments = """
Arguments:
* percentage - The percentile to compute, between 0.0 and 1.0. Must be
foldable.
* col - The column to compute the percentile of, specified in the ORDER
BY clause.
- An expression that evaluates to a numeric, ANSI interval, or time.
+ An expression that evaluates to a numeric, ANSI interval, time, or
nanosecond-precision
+ timestamp (TIMESTAMP_LTZ(p) / TIMESTAMP_NTZ(p), p in [7, 9]).
""",
examples = """
Examples:
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/V2ExpressionBuilder.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/V2ExpressionBuilder.scala
index 4fa4a327c792..db9f1b66c5da 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/V2ExpressionBuilder.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/V2ExpressionBuilder.scala
@@ -29,7 +29,8 @@ import org.apache.spark.sql.connector.expressions.{Cast =>
V2Cast, Expression =>
import org.apache.spark.sql.connector.expressions.aggregate.{AggregateFunc,
Avg, Count, CountStar, GeneralAggregateFunc, Max, Min, Sum,
UserDefinedAggregateFunc}
import org.apache.spark.sql.connector.expressions.filter.{AlwaysFalse,
AlwaysTrue, And => V2And, Not => V2Not, Or => V2Or, Predicate => V2Predicate}
import org.apache.spark.sql.internal.SQLConf
-import org.apache.spark.sql.types.{BooleanType, DataType, IntegerType,
StringType}
+import org.apache.spark.sql.types.{AnyTimestampNanoType, BooleanType,
DataType, IntegerType,
+ StringType}
/**
* The builder to generate V2 expressions from catalyst expressions.
@@ -431,8 +432,10 @@ class V2ExpressionBuilder(e: Expression, isPredicate:
Boolean = false) extends L
case aggregate.Mode(PushableExpression(expr), _, _, Some(reverse)) =>
Some(new GeneralAggregateFunc(
"MODE", isDistinct, Array.empty, Array(generateSortValue(expr,
!reverse))))
- case aggregate.Percentile(
- PushableExpression(left), PushableExpression(right), LongLiteral(1L), _,
_, reverse) =>
+ // A source would interpolate a nanosecond timestamp with its own
rounding, not Spark's floor.
+ case p @ aggregate.Percentile(
+ PushableExpression(left), PushableExpression(right), LongLiteral(1L), _,
_, reverse)
+ if !p.child.dataType.isInstanceOf[AnyTimestampNanoType] =>
Some(new GeneralAggregateFunc("PERCENTILE_CONT", isDistinct,
Array(right), Array(generateSortValue(left, reverse))))
case aggregate.PercentileDisc(
diff --git
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala
index fb9cc87257e6..d2d0823ccea8 100644
---
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala
+++
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/analysis/TypeCoercionSuite.scala
@@ -25,7 +25,8 @@ import org.apache.spark.sql.catalyst.analysis.TypeCoercion._
import org.apache.spark.sql.catalyst.dsl.expressions._
import org.apache.spark.sql.catalyst.dsl.plans._
import org.apache.spark.sql.catalyst.expressions._
-import
org.apache.spark.sql.catalyst.expressions.aggregate.ApproximatePercentile
+import
org.apache.spark.sql.catalyst.expressions.aggregate.{ApproximatePercentile,
Median,
+ Percentile, PercentileCont, PercentileDisc}
import org.apache.spark.sql.catalyst.plans.ReferenceAllColumns
import org.apache.spark.sql.catalyst.plans.logical._
import org.apache.spark.sql.catalyst.rules.{Rule, RuleExecutor}
@@ -427,6 +428,24 @@ abstract class TypeCoercionSuiteBase extends AnalysisTest {
ruleTest(implicitTypeCastsRule, LastDay(nanos), LastDay(Cast(nanos,
DateType)))
}
+ test("SPARK-60542: implicit type cast - percentile family over nanos and
micro timestamps") {
+ val nanos = Literal.default(TimestampNTZNanosType(9))
+ Seq(
+ new Percentile(nanos, Literal(0.5)),
+ Median(nanos),
+ PercentileCont(nanos, Literal(0.5)),
+ PercentileDisc(nanos, Literal(0.5))).foreach { e =>
+ ruleTest(implicitTypeCastsRule, e, e)
+ }
+ // The nanos member is last in the input TypeCollection, so a micro
timestamp keeps its
+ // implicit cast and is never widened to nanos.
+ val inputType = new Percentile(nanos, Literal(0.5)).inputTypes.head
+ Seq(TimestampType, TimestampNTZType).foreach { micro =>
+ assert(!implicitCast(Literal.default(micro), inputType)
+ .exists(_.dataType.isInstanceOf[AnyTimestampNanoType]))
+ }
+ }
+
test("type coercion for Concat") {
val rule = TypeCoercion.ConcatCoercion
diff --git
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/aggregate/PercentileSuite.scala
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/aggregate/PercentileSuite.scala
index 92b036cbf94d..ccf62c9484a9 100644
---
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/aggregate/PercentileSuite.scala
+++
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/aggregate/PercentileSuite.scala
@@ -28,6 +28,7 @@ import org.apache.spark.sql.catalyst.expressions.Cast._
import org.apache.spark.sql.catalyst.plans.logical.LocalRelation
import org.apache.spark.sql.catalyst.util.ArrayData
import org.apache.spark.sql.types._
+import org.apache.spark.unsafe.types.TimestampNanosVal
import org.apache.spark.util.collection.OpenHashMap
class PercentileSuite extends SparkFunSuite {
@@ -128,6 +129,139 @@ class PercentileSuite extends SparkFunSuite {
assert(agg.eval(buffer) === 5L)
}
+ private def nanosTs(epochMicros: Long, nanosWithinMicro: Int):
TimestampNanosVal =
+ TimestampNanosVal.fromParts(epochMicros, nanosWithinMicro.toShort)
+
+ private def evalNanos(agg: PercentileBase, values: Seq[TimestampNanosVal]):
Any = {
+ val buffer = agg.createAggregationBuffer()
+ values.foreach(v => agg.update(buffer, InternalRow(v)))
+ agg.eval(buffer)
+ }
+
+ test("SPARK-60542: percentile family over nanosecond timestamps keeps the
input type") {
+ Seq(7, 8, 9).flatMap(p => Seq(TimestampNTZNanosType(p),
TimestampLTZNanosType(p))).foreach {
+ dt =>
+ val child = BoundReference(0, dt, nullable = true)
+ Seq(new Percentile(child, Literal(0.5)), PercentileDisc(child,
Literal(0.5))).foreach {
+ agg =>
+ assert(agg.checkInputDataTypes() === TypeCheckSuccess)
+ assert(agg.dataType === dt)
+ }
+ val percentages = CreateArray(Seq(Literal(0.25), Literal(0.75)))
+ assert(new Percentile(child, percentages).dataType === ArrayType(dt,
false))
+ assert(Median(child).dataType === dt)
+ assert(PercentileCont(child, Literal(0.5)).dataType === dt)
+ }
+ }
+
+ test("SPARK-60542: percentile over nanosecond timestamps interpolates
exactly") {
+ // 2020-01-01 12:00:00.123456 UTC.
+ val micros = 1577880000123456L
+ val p9 = BoundReference(0, TimestampNTZNanosType(9), nullable = true)
+ // Both inputs map to the same double, so a double interpolation can't
return .123456790.
+ assert(evalNanos(new Percentile(p9, Literal(0.5)),
+ Seq(nanosTs(micros, 789), nanosTs(micros, 791))) === nanosTs(micros,
790))
+ // The exact midpoint .1234567895 is floored to the precision.
+ assert(evalNanos(new Percentile(p9, Literal(0.5)),
+ Seq(nanosTs(micros, 789), nanosTs(micros, 790))) === nanosTs(micros,
789))
+ // The result carries into the next microsecond.
+ assert(evalNanos(new Percentile(p9, Literal(0.5)),
+ Seq(nanosTs(micros, 999), nanosTs(micros + 1, 1))) === nanosTs(micros +
1, 0))
+ // Pre-epoch: the midpoint of -1000ns and 1ns is -499.5ns, floored to
-500ns.
+ assert(evalNanos(new Percentile(p9, Literal(0.5)),
+ Seq(nanosTs(-1, 0), nanosTs(0, 1))) === nanosTs(-1, 500))
+ // 0001-01-01 00:00:00 and 9999-12-31 23:59:59.999999999: the span
overflows a long in nanos.
+ assert(evalNanos(new Percentile(p9, Literal(0.5)),
+ Seq(nanosTs(-62135596800000000L, 0), nanosTs(253402300799999999L, 999)))
===
+ nanosTs(95633351999999999L, 999))
+ // Single value, p = 0 and p = 1, and an array of percentages.
+ val values = Seq(nanosTs(micros, 100), nanosTs(micros, 200),
nanosTs(micros, 900))
+ assert(evalNanos(new Percentile(p9, Literal(0.5)), values.take(1)) ===
values.head)
+ assert(evalNanos(new Percentile(p9, Literal(0.0)), values) === values.head)
+ assert(evalNanos(new Percentile(p9, Literal(1.0)), values) === values.last)
+ val percentages = CreateArray(Seq(Literal(0.25), Literal(0.75)))
+ assert(evalNanos(new Percentile(p9, percentages),
values).asInstanceOf[ArrayData]
+ .toSeq[TimestampNanosVal](TimestampNTZNanosType(9)) ===
+ Seq(nanosTs(micros, 150), nanosTs(micros, 550)))
+ // Descending order (percentile_cont ... ORDER BY c DESC) mirrors the
ascending result.
+ val twoValues = Seq(nanosTs(0, 0), nanosTs(0, 3))
+ assert(evalNanos(new Percentile(p9, Literal(0.25), reverse = true),
twoValues) ===
+ nanosTs(0, 2))
+ assert(evalNanos(new Percentile(p9, Literal(0.75)), twoValues) ===
nanosTs(0, 2))
+
+ // Results are floored to the 100ns (p = 7) and 10ns (p = 8) grids.
+ val p7 = BoundReference(0, TimestampLTZNanosType(7), nullable = true)
+ assert(evalNanos(new Percentile(p7, Literal(0.5)),
+ Seq(nanosTs(micros, 100), nanosTs(micros, 400))) === nanosTs(micros,
200))
+ assert(evalNanos(new Percentile(p7, Literal(0.5)),
+ Seq(nanosTs(-1, 0), nanosTs(0, 100))) === nanosTs(-1, 500))
+ val p8 = BoundReference(0, TimestampNTZNanosType(8), nullable = true)
+ assert(evalNanos(new Percentile(p8, Literal(0.5)),
+ Seq(nanosTs(micros, 10), nanosTs(micros, 40))) === nanosTs(micros, 20))
+ }
+
+ test("SPARK-60542: percentile over nanosecond timestamps uses an exact
position") {
+ val p9 = BoundReference(0, TimestampNTZNanosType(9), nullable = true)
+ // 90 * 0.7 is 62.99999999999999 as a double, but the exact position is
the 63rd input value.
+ val spaced = (0 to 90).map(nanosTs(0, _))
+ assert(evalNanos(new Percentile(p9, Literal(0.7)), spaced) === spaced(63))
+ assert(evalNanos(new Percentile(p9, Literal(0.3)), Seq(nanosTs(0, 0),
nanosTs(0, 10))) ===
+ nanosTs(0, 3))
+ // 3 * 0.7 is 2.0999999999999996 as a double; ascending 0.7 and descending
0.3 agree.
+ val values = Seq(nanosTs(0, 100), nanosTs(0, 200), nanosTs(0, 300),
nanosTs(1, 300))
+ val p7 = BoundReference(0, TimestampLTZNanosType(7), nullable = true)
+ Seq(p9, p7).foreach { child =>
+ assert(evalNanos(new Percentile(child, Literal(0.7)), values) ===
nanosTs(0, 400))
+ assert(evalNanos(new Percentile(child, Literal(0.3), reverse = true),
values) ===
+ nanosTs(0, 400))
+ }
+ // A tenth of a one-year gap is a whole number of nanoseconds.
+ val year = 365L * 24 * 60 * 60 * 1000 * 1000
+ assert(evalNanos(new Percentile(p9, Literal(0.7)),
+ Seq(nanosTs(0, 0), nanosTs(0, 1), nanosTs(0, 2), nanosTs(year, 2))) ===
+ nanosTs(year / 10, 2))
+ }
+
+ test("SPARK-60542: NaN percentage over nanosecond timestamps matches other
types") {
+ // NaN passes the range check; as for other types, the first value in sort
order is returned.
+ val child = BoundReference(0, TimestampNTZNanosType(9), nullable = true)
+ val values = Seq(nanosTs(0, 100), nanosTs(0, 200), nanosTs(0, 300))
+ val nan = Literal(Double.NaN)
+ assert(evalNanos(new Percentile(child, nan), values) === values.head)
+ assert(evalNanos(new Percentile(child, nan, reverse = true), values) ===
values.last)
+ assert(evalNanos(new Percentile(child, CreateArray(Seq(Literal(0.5),
nan))), values)
+
.asInstanceOf[ArrayData].toSeq[TimestampNanosVal](TimestampNTZNanosType(9)) ===
+ Seq(values(1), values.head))
+ assert(evalNanos(PercentileDisc(child, nan), values) === values.head)
+ assert(evalNanos(PercentileDisc(child, nan, legacyCalculation = true),
values) === values.head)
+ val doubles = new Percentile(BoundReference(0, DoubleType, nullable =
true), nan)
+ val buffer = doubles.createAggregationBuffer()
+ Seq(1.0, 2.0, 3.0).foreach(v => doubles.update(buffer, InternalRow(v)))
+ assert(doubles.eval(buffer) === 1.0)
+ }
+
+ test("SPARK-60542: percentile_disc over nanosecond timestamps returns an
input value") {
+ val child = BoundReference(0, TimestampNTZNanosType(9), nullable = true)
+ val values = (1 to 4).map(nanosTs(1577880000123456L, _))
+ assert(evalNanos(PercentileDisc(child, Literal(0.6)), values) ===
values(2))
+ assert(evalNanos(PercentileDisc(child, Literal(0.6), legacyCalculation =
true), values) ===
+ values(1))
+ assert(evalNanos(PercentileDisc(child, Literal(0.6), reverse = true),
values) === values(1))
+ }
+
+ test("SPARK-60542: percentile over nanosecond timestamps merges serialized
buffers") {
+ val child = BoundReference(0, TimestampLTZNanosType(9), nullable = true)
+ val agg = new Percentile(child, Literal(0.5))
+ val buffer1 = agg.createAggregationBuffer()
+ Seq(nanosTs(-1, 999), null).foreach(v => agg.update(buffer1,
InternalRow(v)))
+ val buffer2 = agg.createAggregationBuffer()
+ agg.update(buffer2, InternalRow(nanosTs(0, 3)))
+ val merged = agg.merge(
+ agg.deserialize(agg.serialize(buffer1)),
agg.deserialize(agg.serialize(buffer2)))
+ assert(agg.eval(merged) === nanosTs(0, 1))
+ assert(agg.eval(agg.createAggregationBuffer()) === null)
+ }
+
test("class Percentile, low level interface, update, merge, eval...") {
val childExpression = Cast(BoundReference(0, IntegerType, nullable =
true), DoubleType)
val inputAggregationBufferOffset = 1
@@ -191,7 +325,8 @@ class PercentileSuite extends SparkFunSuite {
messageParameters = Map(
"paramIndex" -> ordinalNumber(0),
"requiredType" -> ("((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" " +
- "or \"INTERVAL YEAR TO MONTH\") or \"TIME\")"),
+ "or \"INTERVAL YEAR TO MONTH\") or \"TIME\" " +
+ "or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7,
9])\")"),
"inputSql" -> "\"a\"",
"inputType" -> toSQLType(dataType)
)
@@ -214,7 +349,8 @@ class PercentileSuite extends SparkFunSuite {
messageParameters = Map(
"paramIndex" -> ordinalNumber(0),
"requiredType" -> ("((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" " +
- "or \"INTERVAL YEAR TO MONTH\") or \"TIME\")"),
+ "or \"INTERVAL YEAR TO MONTH\") or \"TIME\" " +
+ "or \"(TIMESTAMP_LTZ(P) OR TIMESTAMP_NTZ(P) WITH P IN [7,
9])\")"),
"inputSql" -> "\"a\"",
"inputType" -> toSQLType(dataType)
)
diff --git
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
index 0a02eadcc7fe..f39e0eefd15f 100644
---
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
+++
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ltz-nanos.sql.out
@@ -2145,106 +2145,6 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
}
--- !query
-SELECT percentile(c, 0.5)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile(c, 0.5, 1)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 25,
- "fragment" : "percentile(c, 0.5)"
- } ]
-}
-
-
--- !query
-SELECT median(c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"median(c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 16,
- "fragment" : "median(c)"
- } ]
-}
-
-
--- !query
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_cont(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_cont(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
-
-
--- !query
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_disc(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_disc(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
-
-
-- !query
SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
@@ -2461,7 +2361,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
-- !query
-SELECT percentile(c, 0.5)
+SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query analysis
org.apache.spark.sql.catalyst.ExtendedAnalysisException
@@ -2471,22 +2371,22 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"messageParameters" : {
"inputSql" : "\"c\"",
"inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile(c, 0.5, 1)\""
+ "paramIndex" : "second",
+ "requiredType" : "\"TIME\"",
+ "sqlExpr" : "\"time_trunc(HOUR, c)\""
},
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
"startIndex" : 8,
- "stopIndex" : 25,
- "fragment" : "percentile(c, 0.5)"
+ "stopIndex" : 28,
+ "fragment" : "time_trunc('HOUR', c)"
} ]
}
-- !query
-SELECT median(c)
+SELECT time_diff('SECOND', TIME '12:00:00', c)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query analysis
org.apache.spark.sql.catalyst.ExtendedAnalysisException
@@ -2496,125 +2396,121 @@
org.apache.spark.sql.catalyst.ExtendedAnalysisException
"messageParameters" : {
"inputSql" : "\"c\"",
"inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"median(c)\""
+ "paramIndex" : "third",
+ "requiredType" : "\"TIME\"",
+ "sqlExpr" : "\"time_diff(SECOND, TIME '12:00:00', c)\""
},
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
"startIndex" : 8,
- "stopIndex" : 16,
- "fragment" : "median(c)"
+ "stopIndex" : 46,
+ "fragment" : "time_diff('SECOND', TIME '12:00:00', c)"
} ]
}
-- !query
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
+SELECT last_day(c), next_day(c, 'MO'), datediff(c, '2019-12-31'),
add_months(c, 1)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_cont(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_cont(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
+Project [last_day(cast(c#x as date)) AS last_day(c)#x, next_day(cast(c#x as
date), MO, false) AS next_day(c, MO)#x, datediff(cast(c#x as date),
cast(2019-12-31 as date)) AS datediff(c, 2019-12-31)#x, add_months(cast(c#x as
date), 1) AS add_months(c, 1)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
-- !query
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SET spark.sql.ansi.enabled=true
-- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_disc(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_disc(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
+SetCommand (spark.sql.ansi.enabled,Some(true))
-- !query
-SELECT time_trunc('HOUR', c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_cont(0.25) WITHIN GROUP (ORDER BY c DESC),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000100 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000400 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000001000 UTC'), (NULL) AS
t(c)
-- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "second",
- "requiredType" : "\"TIME\"",
- "sqlExpr" : "\"time_trunc(HOUR, c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 28,
- "fragment" : "time_trunc('HOUR', c)"
- } ]
-}
+Aggregate [median(c#x) AS median(c)#x, percentile(c#x, cast(0.25 as double),
1, 0, 0, false) AS percentile(c, 0.25, 1)#x, percentile_cont(c#x, cast(0.75 as
double), false) AS percentile_cont(0.75) WITHIN GROUP (ORDER BY c)#x,
percentile_cont(c#x, cast(0.25 as double), true) AS percentile_cont(0.25)
WITHIN GROUP (ORDER BY c DESC)#x, percentile_disc(c#x, cast(0.5 as double),
false, 0, 0, false) AS percentile_disc(0.5) WITHIN GROUP (ORDER BY c)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
-- !query
-SELECT time_diff('SECOND', TIME '12:00:00', c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT typeof(median(c)), typeof(percentile(c, array(0.25, 0.75))),
+ typeof(percentile_disc(0.5) WITHIN GROUP (ORDER BY c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001 UTC' AS timestamp_ltz(7))) AS
t(c)
-- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "third",
- "requiredType" : "\"TIME\"",
- "sqlExpr" : "\"time_diff(SECOND, TIME '12:00:00', c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 46,
- "fragment" : "time_diff('SECOND', TIME '12:00:00', c)"
- } ]
-}
+Aggregate [typeof(median(c#x)) AS typeof(median(c))#x, typeof(percentile(c#x,
cast(array(0.25, 0.75) as array<double>), 1, 0, 0, false)) AS
typeof(percentile(c, array(0.25, 0.75), 1))#x, typeof(percentile_disc(c#x,
cast(0.5 as double), false, 0, 0, false)) AS typeof(percentile_disc(0.5) WITHIN
GROUP (ORDER BY c))#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
-- !query
-SELECT last_day(c), next_day(c, 'MO'), datediff(c, '2019-12-31'),
add_months(c, 1)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT median(c) FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789
UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456791
UTC') AS t(c)
-- !query analysis
-Project [last_day(cast(c#x as date)) AS last_day(c)#x, next_day(cast(c#x as
date), MO, false) AS next_day(c, MO)#x, datediff(cast(c#x as date),
cast(2019-12-31 as date)) AS datediff(c, 2019-12-31)#x, add_months(cast(c#x as
date), 1) AS add_months(c, 1)#x]
+Aggregate [median(c#x) AS median(c)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
+-- !query
+SELECT median(c) FROM VALUES (TIMESTAMP_LTZ '1969-12-31 23:59:59.999999000
UTC'),
+ (TIMESTAMP_LTZ '1970-01-01 00:00:00.000000001
UTC') AS t(c)
+-- !query analysis
+Aggregate [median(c#x) AS median(c)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
+-- !query
+SELECT percentile(c, 0.7), percentile_cont(0.3) WITHIN GROUP (ORDER BY c DESC)
+ FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000100 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000200 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000300 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000001300 UTC') AS t(c)
+-- !query analysis
+Aggregate [percentile(c#x, cast(0.7 as double), 1, 0, 0, false) AS
percentile(c, 0.7, 1)#x, percentile_cont(c#x, cast(0.3 as double), true) AS
percentile_cont(0.3) WITHIN GROUP (ORDER BY c DESC)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
+-- !query
+SELECT median(c), percentile(c, array(0.0, 1.0))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001 UTC' AS timestamp_ltz(7))),
+ (CAST('2020-01-01 12:00:00.0000004 UTC' AS timestamp_ltz(7))) AS
t(c)
+-- !query analysis
+Aggregate [median(c#x) AS median(c)#x, percentile(c#x, cast(array(cast(0.0 as
decimal(2,1)), 1.0) as array<double>), 1, 0, 0, false) AS percentile(c,
array(0.0, 1.0), 1)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
+-- !query
+SET spark.sql.ansi.enabled=false
+-- !query analysis
+SetCommand (spark.sql.ansi.enabled,Some(false))
+
+
+-- !query
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000100 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000400 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000001000 UTC'), (NULL) AS
t(c)
+-- !query analysis
+Aggregate [median(c#x) AS median(c)#x, percentile(c#x, cast(0.25 as double),
1, 0, 0, false) AS percentile(c, 0.25, 1)#x, percentile_cont(c#x, cast(0.75 as
double), false) AS percentile_cont(0.75) WITHIN GROUP (ORDER BY c)#x,
percentile_disc(c#x, cast(0.5 as double), false, 0, 0, false) AS
percentile_disc(0.5) WITHIN GROUP (ORDER BY c)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
+-- !query
+SELECT typeof(median(c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001 UTC' AS timestamp_ltz(7))) AS
t(c)
+-- !query analysis
+Aggregate [typeof(median(c#x)) AS typeof(median(c))#x]
+- SubqueryAlias t
+- LocalRelation [c#x]
diff --git
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
index deb4b44486be..6d937615ceb0 100644
---
a/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
+++
b/sql/core/src/test/resources/sql-tests/analyzer-results/timestamp-ntz-nanos.sql.out
@@ -2235,106 +2235,6 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
}
--- !query
-SELECT percentile(c, 0.5)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile(c, 0.5, 1)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 25,
- "fragment" : "percentile(c, 0.5)"
- } ]
-}
-
-
--- !query
-SELECT median(c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"median(c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 16,
- "fragment" : "median(c)"
- } ]
-}
-
-
--- !query
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_cont(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_cont(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
-
-
--- !query
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_disc(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_disc(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
-
-
-- !query
SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
@@ -2551,7 +2451,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
-- !query
-SELECT percentile(c, 0.5)
+SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query analysis
org.apache.spark.sql.catalyst.ExtendedAnalysisException
@@ -2561,22 +2461,22 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"messageParameters" : {
"inputSql" : "\"c\"",
"inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile(c, 0.5, 1)\""
+ "paramIndex" : "second",
+ "requiredType" : "\"TIME\"",
+ "sqlExpr" : "\"time_trunc(HOUR, c)\""
},
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
"startIndex" : 8,
- "stopIndex" : 25,
- "fragment" : "percentile(c, 0.5)"
+ "stopIndex" : 28,
+ "fragment" : "time_trunc('HOUR', c)"
} ]
}
-- !query
-SELECT median(c)
+SELECT time_diff('SECOND', TIME '12:00:00', c)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query analysis
org.apache.spark.sql.catalyst.ExtendedAnalysisException
@@ -2586,125 +2486,145 @@
org.apache.spark.sql.catalyst.ExtendedAnalysisException
"messageParameters" : {
"inputSql" : "\"c\"",
"inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"median(c)\""
+ "paramIndex" : "third",
+ "requiredType" : "\"TIME\"",
+ "sqlExpr" : "\"time_diff(SECOND, TIME '12:00:00', c)\""
},
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
"startIndex" : 8,
- "stopIndex" : 16,
- "fragment" : "median(c)"
+ "stopIndex" : 46,
+ "fragment" : "time_diff('SECOND', TIME '12:00:00', c)"
} ]
}
-- !query
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
+SELECT last_day(c), next_day(c, 'MO'), datediff(c, '2019-12-31'),
add_months(c, 1)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_cont(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_cont(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
+Project [last_day(cast(c#x as date)) AS last_day(c)#x, next_day(cast(c#x as
date), MO, false) AS next_day(c, MO)#x, datediff(cast(c#x as date),
cast(2019-12-31 as date)) AS datediff(c, 2019-12-31)#x, add_months(cast(c#x as
date), 1) AS add_months(c, 1)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
-- !query
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SET spark.sql.ansi.enabled=true
-- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_disc(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_disc(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
+SetCommand (spark.sql.ansi.enabled,Some(true))
-- !query
-SELECT time_trunc('HOUR', c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_cont(0.25) WITHIN GROUP (ORDER BY c DESC),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000100'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000400'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000001000'), (NULL) AS t(c)
-- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "second",
- "requiredType" : "\"TIME\"",
- "sqlExpr" : "\"time_trunc(HOUR, c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 28,
- "fragment" : "time_trunc('HOUR', c)"
- } ]
-}
+Aggregate [median(c#x) AS median(c)#x, percentile(c#x, cast(0.25 as double),
1, 0, 0, false) AS percentile(c, 0.25, 1)#x, percentile_cont(c#x, cast(0.75 as
double), false) AS percentile_cont(0.75) WITHIN GROUP (ORDER BY c)#x,
percentile_cont(c#x, cast(0.25 as double), true) AS percentile_cont(0.25)
WITHIN GROUP (ORDER BY c DESC)#x, percentile_disc(c#x, cast(0.5 as double),
false, 0, 0, false) AS percentile_disc(0.5) WITHIN GROUP (ORDER BY c)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
-- !query
-SELECT time_diff('SECOND', TIME '12:00:00', c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT typeof(median(c)), typeof(percentile(c, array(0.25, 0.75))),
+ typeof(percentile_disc(0.5) WITHIN GROUP (ORDER BY c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001' AS timestamp_ntz(7))) AS t(c)
-- !query analysis
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "third",
- "requiredType" : "\"TIME\"",
- "sqlExpr" : "\"time_diff(SECOND, TIME '12:00:00', c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 46,
- "fragment" : "time_diff('SECOND', TIME '12:00:00', c)"
- } ]
-}
+Aggregate [typeof(median(c#x)) AS typeof(median(c))#x, typeof(percentile(c#x,
cast(array(0.25, 0.75) as array<double>), 1, 0, 0, false)) AS
typeof(percentile(c, array(0.25, 0.75), 1))#x, typeof(percentile_disc(c#x,
cast(0.5 as double), false, 0, 0, false)) AS typeof(percentile_disc(0.5) WITHIN
GROUP (ORDER BY c))#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
-- !query
-SELECT last_day(c), next_day(c, 'MO'), datediff(c, '2019-12-31'),
add_months(c, 1)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT median(c) FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456791')
AS t(c)
-- !query analysis
-Project [last_day(cast(c#x as date)) AS last_day(c)#x, next_day(cast(c#x as
date), MO, false) AS next_day(c, MO)#x, datediff(cast(c#x as date),
cast(2019-12-31 as date)) AS datediff(c, 2019-12-31)#x, add_months(cast(c#x as
date), 1) AS add_months(c, 1)#x]
+Aggregate [median(c#x) AS median(c)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
+-- !query
+SELECT median(c) FROM VALUES (TIMESTAMP_NTZ '1969-12-31 23:59:59.999999000'),
+ (TIMESTAMP_NTZ '1970-01-01 00:00:00.000000001')
AS t(c)
+-- !query analysis
+Aggregate [median(c#x) AS median(c)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
+-- !query
+SELECT percentile(c, 0.7), percentile_cont(0.3) WITHIN GROUP (ORDER BY c DESC)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000100'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000200'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000300'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000001300') AS t(c)
+-- !query analysis
+Aggregate [percentile(c#x, cast(0.7 as double), 1, 0, 0, false) AS
percentile(c, 0.7, 1)#x, percentile_cont(c#x, cast(0.3 as double), true) AS
percentile_cont(0.3) WITHIN GROUP (ORDER BY c DESC)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
+-- !query
+SELECT median(c), percentile(c, array(0.0, 1.0))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001' AS timestamp_ntz(7))),
+ (CAST('2020-01-01 12:00:00.0000004' AS timestamp_ntz(7))) AS t(c)
+-- !query analysis
+Aggregate [median(c#x) AS median(c)#x, percentile(c#x, cast(array(cast(0.0 as
decimal(2,1)), 1.0) as array<double>), 1, 0, 0, false) AS percentile(c,
array(0.0, 1.0), 1)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
+-- !query
+SELECT k, median(c) OVER (PARTITION BY k)
+ FROM VALUES (1, TIMESTAMP_NTZ '2020-01-01 12:00:00.000000001'),
+ (1, TIMESTAMP_NTZ '2020-01-01 12:00:00.000000003'),
+ (2, TIMESTAMP_NTZ '2020-01-01 12:00:00.000000005') AS t(k, c)
+-- !query analysis
+Project [k#x, median(c) OVER (PARTITION BY k ROWS BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING)#x]
++- Project [k#x, c#x, median(c) OVER (PARTITION BY k ROWS BETWEEN UNBOUNDED
PRECEDING AND UNBOUNDED FOLLOWING)#x, median(c) OVER (PARTITION BY k ROWS
BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)#x]
+ +- Window [median(c#x) windowspecdefinition(k#x,
specifiedwindowframe(RowFrame, unboundedpreceding$(), unboundedfollowing$()))
AS median(c) OVER (PARTITION BY k ROWS BETWEEN UNBOUNDED PRECEDING AND
UNBOUNDED FOLLOWING)#x], [k#x]
+ +- Project [k#x, c#x]
+ +- SubqueryAlias t
+ +- LocalRelation [k#x, c#x]
+
+
+-- !query
+SELECT percentile(c, 0.5, f)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000001', 3),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000009', 1) AS t(c, f)
+-- !query analysis
+Aggregate [percentile(c#x, cast(0.5 as double), f#x, 0, 0, false) AS
percentile(c, 0.5, f)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x, f#x]
+
+
+-- !query
+SET spark.sql.ansi.enabled=false
+-- !query analysis
+SetCommand (spark.sql.ansi.enabled,Some(false))
+
+
+-- !query
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000100'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000400'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000001000'), (NULL) AS t(c)
+-- !query analysis
+Aggregate [median(c#x) AS median(c)#x, percentile(c#x, cast(0.25 as double),
1, 0, 0, false) AS percentile(c, 0.25, 1)#x, percentile_cont(c#x, cast(0.75 as
double), false) AS percentile_cont(0.75) WITHIN GROUP (ORDER BY c)#x,
percentile_disc(c#x, cast(0.5 as double), false, 0, 0, false) AS
percentile_disc(0.5) WITHIN GROUP (ORDER BY c)#x]
++- SubqueryAlias t
+ +- LocalRelation [c#x]
+
+
+-- !query
+SELECT typeof(median(c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001' AS timestamp_ntz(7))) AS t(c)
+-- !query analysis
+Aggregate [typeof(median(c#x)) AS typeof(median(c))#x]
+- SubqueryAlias t
+- LocalRelation [c#x]
diff --git
a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
index 8a4a21ac0a13..b805490baac7 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ltz-nanos.sql
@@ -656,14 +656,6 @@ SELECT approx_percentile(c, 0.5)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT histogram_numeric(c, 2)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile(c, 0.5)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT median(c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT time_diff('SECOND', TIME '12:00:00', c)
@@ -683,14 +675,6 @@ SELECT approx_percentile(c, 0.5)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT histogram_numeric(c, 2)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile(c, 0.5)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT median(c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT time_diff('SECOND', TIME '12:00:00', c)
@@ -698,3 +682,42 @@ SELECT time_diff('SECOND', TIME '12:00:00', c)
SELECT last_day(c), next_day(c, 'MO'), datediff(c, '2019-12-31'),
add_months(c, 1)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c);
SET spark.sql.ansi.enabled=true;
+
+-- percentile / median / percentile_cont / percentile_disc over
TIMESTAMP_LTZ(p) keep the input
+-- type. Interpolation is exact in nanoseconds and floored to p;
percentile_disc returns an input
+-- value; NULLs are ignored.
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_cont(0.25) WITHIN GROUP (ORDER BY c DESC),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000100 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000400 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000001000 UTC'), (NULL) AS
t(c);
+SELECT typeof(median(c)), typeof(percentile(c, array(0.25, 0.75))),
+ typeof(percentile_disc(0.5) WITHIN GROUP (ORDER BY c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001 UTC' AS timestamp_ltz(7))) AS
t(c);
+-- A sub-microsecond midpoint that a double interpolation would lose.
+SELECT median(c) FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789
UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456791
UTC') AS t(c);
+-- Pre-epoch: the midpoint -499.5ns is floored to -500ns.
+SELECT median(c) FROM VALUES (TIMESTAMP_LTZ '1969-12-31 23:59:59.999999000
UTC'),
+ (TIMESTAMP_LTZ '1970-01-01 00:00:00.000000001
UTC') AS t(c);
+-- A non-dyadic percentage uses an exact position: 3 * 0.7 is
2.0999999999999996 as a double.
+SELECT percentile(c, 0.7), percentile_cont(0.3) WITHIN GROUP (ORDER BY c DESC)
+ FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000100 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000200 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000300 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000001300 UTC') AS t(c);
+-- At p=7 the midpoint .00000025 is floored to .0000002.
+SELECT median(c), percentile(c, array(0.0, 1.0))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001 UTC' AS timestamp_ltz(7))),
+ (CAST('2020-01-01 12:00:00.0000004 UTC' AS timestamp_ltz(7))) AS
t(c);
+-- Same results with ANSI mode off.
+SET spark.sql.ansi.enabled=false;
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000100 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000400 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000001000 UTC'), (NULL) AS
t(c);
+SELECT typeof(median(c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001 UTC' AS timestamp_ltz(7))) AS
t(c);
+SET spark.sql.ansi.enabled=true;
diff --git
a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
index 6744c8f09f41..32361ee72303 100644
--- a/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
+++ b/sql/core/src/test/resources/sql-tests/inputs/timestamp-ntz-nanos.sql
@@ -660,14 +660,6 @@ SELECT percentile_approx(c, 0.5)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT histogram_numeric(c, 2)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile(c, 0.5)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT median(c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT time_diff('SECOND', TIME '12:00:00', c)
@@ -687,14 +679,6 @@ SELECT percentile_approx(c, 0.5)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT histogram_numeric(c, 2)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile(c, 0.5)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT median(c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
SELECT time_diff('SECOND', TIME '12:00:00', c)
@@ -702,3 +686,50 @@ SELECT time_diff('SECOND', TIME '12:00:00', c)
SELECT last_day(c), next_day(c, 'MO'), datediff(c, '2019-12-31'),
add_months(c, 1)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c);
SET spark.sql.ansi.enabled=true;
+
+-- percentile / median / percentile_cont / percentile_disc over
TIMESTAMP_NTZ(p) keep the input
+-- type. Interpolation is exact in nanoseconds and floored to p;
percentile_disc returns an input
+-- value; NULLs are ignored.
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_cont(0.25) WITHIN GROUP (ORDER BY c DESC),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000100'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000400'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000001000'), (NULL) AS t(c);
+SELECT typeof(median(c)), typeof(percentile(c, array(0.25, 0.75))),
+ typeof(percentile_disc(0.5) WITHIN GROUP (ORDER BY c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001' AS timestamp_ntz(7))) AS
t(c);
+-- A sub-microsecond midpoint that a double interpolation would lose.
+SELECT median(c) FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456791')
AS t(c);
+-- Pre-epoch: the midpoint -499.5ns is floored to -500ns.
+SELECT median(c) FROM VALUES (TIMESTAMP_NTZ '1969-12-31 23:59:59.999999000'),
+ (TIMESTAMP_NTZ '1970-01-01 00:00:00.000000001')
AS t(c);
+-- A non-dyadic percentage uses an exact position: 3 * 0.7 is
2.0999999999999996 as a double.
+SELECT percentile(c, 0.7), percentile_cont(0.3) WITHIN GROUP (ORDER BY c DESC)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000100'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000200'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000300'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000001300') AS t(c);
+-- At p=7 the midpoint .00000025 is floored to .0000002.
+SELECT median(c), percentile(c, array(0.0, 1.0))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001' AS timestamp_ntz(7))),
+ (CAST('2020-01-01 12:00:00.0000004' AS timestamp_ntz(7))) AS
t(c);
+-- As a window function, and with a frequency column.
+SELECT k, median(c) OVER (PARTITION BY k)
+ FROM VALUES (1, TIMESTAMP_NTZ '2020-01-01 12:00:00.000000001'),
+ (1, TIMESTAMP_NTZ '2020-01-01 12:00:00.000000003'),
+ (2, TIMESTAMP_NTZ '2020-01-01 12:00:00.000000005') AS t(k, c);
+SELECT percentile(c, 0.5, f)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000001', 3),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000009', 1) AS t(c, f);
+-- Same results with ANSI mode off.
+SET spark.sql.ansi.enabled=false;
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000100'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000400'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000001000'), (NULL) AS t(c);
+SELECT typeof(median(c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001' AS timestamp_ntz(7))) AS
t(c);
+SET spark.sql.ansi.enabled=true;
diff --git
a/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
b/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
index 5d1d0cb3a1dd..4b6900205e41 100644
--- a/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/timestamp-ltz-nanos.sql.out
@@ -2345,114 +2345,6 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
}
--- !query
-SELECT percentile(c, 0.5)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query schema
-struct<>
--- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile(c, 0.5, 1)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 25,
- "fragment" : "percentile(c, 0.5)"
- } ]
-}
-
-
--- !query
-SELECT median(c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query schema
-struct<>
--- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"median(c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 16,
- "fragment" : "median(c)"
- } ]
-}
-
-
--- !query
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query schema
-struct<>
--- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_cont(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_cont(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
-
-
--- !query
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query schema
-struct<>
--- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_disc(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_disc(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
-
-
-- !query
SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
@@ -2687,7 +2579,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
-- !query
-SELECT percentile(c, 0.5)
+SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query schema
struct<>
@@ -2699,22 +2591,22 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"messageParameters" : {
"inputSql" : "\"c\"",
"inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile(c, 0.5, 1)\""
+ "paramIndex" : "second",
+ "requiredType" : "\"TIME\"",
+ "sqlExpr" : "\"time_trunc(HOUR, c)\""
},
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
"startIndex" : 8,
- "stopIndex" : 25,
- "fragment" : "percentile(c, 0.5)"
+ "stopIndex" : 28,
+ "fragment" : "time_trunc('HOUR', c)"
} ]
}
-- !query
-SELECT median(c)
+SELECT time_diff('SECOND', TIME '12:00:00', c)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query schema
struct<>
@@ -2726,135 +2618,127 @@
org.apache.spark.sql.catalyst.ExtendedAnalysisException
"messageParameters" : {
"inputSql" : "\"c\"",
"inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"median(c)\""
+ "paramIndex" : "third",
+ "requiredType" : "\"TIME\"",
+ "sqlExpr" : "\"time_diff(SECOND, TIME '12:00:00', c)\""
},
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
"startIndex" : 8,
- "stopIndex" : 16,
- "fragment" : "median(c)"
+ "stopIndex" : 46,
+ "fragment" : "time_diff('SECOND', TIME '12:00:00', c)"
} ]
}
-- !query
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
+SELECT last_day(c), next_day(c, 'MO'), datediff(c, '2019-12-31'),
add_months(c, 1)
FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query schema
-struct<>
+struct<last_day(c):date,next_day(c, MO):date,datediff(c,
2019-12-31):int,add_months(c, 1):date>
-- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_cont(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_cont(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
+2020-01-31 2020-01-06 1 2020-02-01
-- !query
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SET spark.sql.ansi.enabled=true
-- !query schema
-struct<>
+struct<key:string,value:string>
-- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_disc(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_disc(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
+spark.sql.ansi.enabled true
-- !query
-SELECT time_trunc('HOUR', c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_cont(0.25) WITHIN GROUP (ORDER BY c DESC),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000100 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000400 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000001000 UTC'), (NULL) AS
t(c)
-- !query schema
-struct<>
+struct<median(c):timestamp_ltz(9),percentile(c, 0.25,
1):timestamp_ltz(9),percentile_cont(0.75) WITHIN GROUP (ORDER BY
c):timestamp_ltz(9),percentile_cont(0.25) WITHIN GROUP (ORDER BY c
DESC):timestamp_ltz(9),percentile_disc(0.5) WITHIN GROUP (ORDER BY
c):timestamp_ltz(9)>
-- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "second",
- "requiredType" : "\"TIME\"",
- "sqlExpr" : "\"time_trunc(HOUR, c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 28,
- "fragment" : "time_trunc('HOUR', c)"
- } ]
-}
+2020-01-01 04:00:00.0000004 2020-01-01 04:00:00.00000025 2020-01-01
04:00:00.0000007 2020-01-01 04:00:00.0000007 2020-01-01 04:00:00.0000004
-- !query
-SELECT time_diff('SECOND', TIME '12:00:00', c)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT typeof(median(c)), typeof(percentile(c, array(0.25, 0.75))),
+ typeof(percentile_disc(0.5) WITHIN GROUP (ORDER BY c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001 UTC' AS timestamp_ltz(7))) AS
t(c)
-- !query schema
-struct<>
+struct<typeof(median(c)):string,typeof(percentile(c, array(0.25, 0.75),
1)):string,typeof(percentile_disc(0.5) WITHIN GROUP (ORDER BY c)):string>
-- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_LTZ(9)\"",
- "paramIndex" : "third",
- "requiredType" : "\"TIME\"",
- "sqlExpr" : "\"time_diff(SECOND, TIME '12:00:00', c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 46,
- "fragment" : "time_diff('SECOND', TIME '12:00:00', c)"
- } ]
-}
+timestamp_ltz(7) array<timestamp_ltz(7)> timestamp_ltz(7)
-- !query
-SELECT last_day(c), next_day(c, 'MO'), datediff(c, '2019-12-31'),
add_months(c, 1)
- FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT median(c) FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456789
UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.123456791
UTC') AS t(c)
-- !query schema
-struct<last_day(c):date,next_day(c, MO):date,datediff(c,
2019-12-31):int,add_months(c, 1):date>
+struct<median(c):timestamp_ltz(9)>
-- !query output
-2020-01-31 2020-01-06 1 2020-02-01
+2020-01-01 04:00:00.12345679
+
+
+-- !query
+SELECT median(c) FROM VALUES (TIMESTAMP_LTZ '1969-12-31 23:59:59.999999000
UTC'),
+ (TIMESTAMP_LTZ '1970-01-01 00:00:00.000000001
UTC') AS t(c)
+-- !query schema
+struct<median(c):timestamp_ltz(9)>
+-- !query output
+1969-12-31 15:59:59.9999995
+
+
+-- !query
+SELECT percentile(c, 0.7), percentile_cont(0.3) WITHIN GROUP (ORDER BY c DESC)
+ FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000100 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000200 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000300 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000001300 UTC') AS t(c)
+-- !query schema
+struct<percentile(c, 0.7, 1):timestamp_ltz(9),percentile_cont(0.3) WITHIN
GROUP (ORDER BY c DESC):timestamp_ltz(9)>
+-- !query output
+2020-01-01 04:00:00.0000004 2020-01-01 04:00:00.0000004
+
+
+-- !query
+SELECT median(c), percentile(c, array(0.0, 1.0))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001 UTC' AS timestamp_ltz(7))),
+ (CAST('2020-01-01 12:00:00.0000004 UTC' AS timestamp_ltz(7))) AS
t(c)
+-- !query schema
+struct<median(c):timestamp_ltz(7),percentile(c, array(0.0, 1.0),
1):array<timestamp_ltz(7)>>
+-- !query output
+2020-01-01 04:00:00.0000002 [2020-01-01 04:00:00.0000001,2020-01-01
04:00:00.0000004]
+
+
+-- !query
+SET spark.sql.ansi.enabled=false
+-- !query schema
+struct<key:string,value:string>
+-- !query output
+spark.sql.ansi.enabled false
+
+
+-- !query
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000100 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000000400 UTC'),
+ (TIMESTAMP_LTZ '2020-01-01 12:00:00.000001000 UTC'), (NULL) AS
t(c)
+-- !query schema
+struct<median(c):timestamp_ltz(9),percentile(c, 0.25,
1):timestamp_ltz(9),percentile_cont(0.75) WITHIN GROUP (ORDER BY
c):timestamp_ltz(9),percentile_disc(0.5) WITHIN GROUP (ORDER BY
c):timestamp_ltz(9)>
+-- !query output
+2020-01-01 04:00:00.0000004 2020-01-01 04:00:00.00000025 2020-01-01
04:00:00.0000007 2020-01-01 04:00:00.0000004
+
+
+-- !query
+SELECT typeof(median(c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001 UTC' AS timestamp_ltz(7))) AS
t(c)
+-- !query schema
+struct<typeof(median(c)):string>
+-- !query output
+timestamp_ltz(7)
-- !query
diff --git
a/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
b/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
index e897b32e13f4..70e6832990dc 100644
--- a/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/timestamp-ntz-nanos.sql.out
@@ -2357,114 +2357,6 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
}
--- !query
-SELECT percentile(c, 0.5)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query schema
-struct<>
--- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile(c, 0.5, 1)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 25,
- "fragment" : "percentile(c, 0.5)"
- } ]
-}
-
-
--- !query
-SELECT median(c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query schema
-struct<>
--- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"median(c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 16,
- "fragment" : "median(c)"
- } ]
-}
-
-
--- !query
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query schema
-struct<>
--- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_cont(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_cont(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
-
-
--- !query
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
--- !query schema
-struct<>
--- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_disc(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_disc(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
-
-
-- !query
SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
@@ -2699,7 +2591,7 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
-- !query
-SELECT percentile(c, 0.5)
+SELECT time_trunc('HOUR', c)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query schema
struct<>
@@ -2711,22 +2603,22 @@ org.apache.spark.sql.catalyst.ExtendedAnalysisException
"messageParameters" : {
"inputSql" : "\"c\"",
"inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile(c, 0.5, 1)\""
+ "paramIndex" : "second",
+ "requiredType" : "\"TIME\"",
+ "sqlExpr" : "\"time_trunc(HOUR, c)\""
},
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
"startIndex" : 8,
- "stopIndex" : 25,
- "fragment" : "percentile(c, 0.5)"
+ "stopIndex" : 28,
+ "fragment" : "time_trunc('HOUR', c)"
} ]
}
-- !query
-SELECT median(c)
+SELECT time_diff('SECOND', TIME '12:00:00', c)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query schema
struct<>
@@ -2738,135 +2630,150 @@
org.apache.spark.sql.catalyst.ExtendedAnalysisException
"messageParameters" : {
"inputSql" : "\"c\"",
"inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"median(c)\""
+ "paramIndex" : "third",
+ "requiredType" : "\"TIME\"",
+ "sqlExpr" : "\"time_diff(SECOND, TIME '12:00:00', c)\""
},
"queryContext" : [ {
"objectType" : "",
"objectName" : "",
"startIndex" : 8,
- "stopIndex" : 16,
- "fragment" : "median(c)"
+ "stopIndex" : 46,
+ "fragment" : "time_diff('SECOND', TIME '12:00:00', c)"
} ]
}
-- !query
-SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY c)
+SELECT last_day(c), next_day(c, 'MO'), datediff(c, '2019-12-31'),
add_months(c, 1)
FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
-- !query schema
-struct<>
+struct<last_day(c):date,next_day(c, MO):date,datediff(c,
2019-12-31):int,add_months(c, 1):date>
-- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_cont(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_cont(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
+2020-01-31 2020-01-06 1 2020-02-01
-- !query
-SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SET spark.sql.ansi.enabled=true
-- !query schema
-struct<>
+struct<key:string,value:string>
-- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "first",
- "requiredType" : "((\"NUMERIC\" or \"INTERVAL DAY TO SECOND\" or
\"INTERVAL YEAR TO MONTH\") or \"TIME\")",
- "sqlExpr" : "\"percentile_disc(c, 0.5)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 53,
- "fragment" : "percentile_disc(0.5) WITHIN GROUP (ORDER BY c)"
- } ]
-}
+spark.sql.ansi.enabled true
-- !query
-SELECT time_trunc('HOUR', c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_cont(0.25) WITHIN GROUP (ORDER BY c DESC),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000100'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000400'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000001000'), (NULL) AS t(c)
-- !query schema
-struct<>
+struct<median(c):timestamp_ntz(9),percentile(c, 0.25,
1):timestamp_ntz(9),percentile_cont(0.75) WITHIN GROUP (ORDER BY
c):timestamp_ntz(9),percentile_cont(0.25) WITHIN GROUP (ORDER BY c
DESC):timestamp_ntz(9),percentile_disc(0.5) WITHIN GROUP (ORDER BY
c):timestamp_ntz(9)>
-- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "second",
- "requiredType" : "\"TIME\"",
- "sqlExpr" : "\"time_trunc(HOUR, c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 28,
- "fragment" : "time_trunc('HOUR', c)"
- } ]
-}
+2020-01-01 12:00:00.0000004 2020-01-01 12:00:00.00000025 2020-01-01
12:00:00.0000007 2020-01-01 12:00:00.0000007 2020-01-01 12:00:00.0000004
-- !query
-SELECT time_diff('SECOND', TIME '12:00:00', c)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT typeof(median(c)), typeof(percentile(c, array(0.25, 0.75))),
+ typeof(percentile_disc(0.5) WITHIN GROUP (ORDER BY c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001' AS timestamp_ntz(7))) AS t(c)
-- !query schema
-struct<>
+struct<typeof(median(c)):string,typeof(percentile(c, array(0.25, 0.75),
1)):string,typeof(percentile_disc(0.5) WITHIN GROUP (ORDER BY c)):string>
-- !query output
-org.apache.spark.sql.catalyst.ExtendedAnalysisException
-{
- "errorClass" : "DATATYPE_MISMATCH.UNEXPECTED_INPUT_TYPE",
- "sqlState" : "42K09",
- "messageParameters" : {
- "inputSql" : "\"c\"",
- "inputType" : "\"TIMESTAMP_NTZ(9)\"",
- "paramIndex" : "third",
- "requiredType" : "\"TIME\"",
- "sqlExpr" : "\"time_diff(SECOND, TIME '12:00:00', c)\""
- },
- "queryContext" : [ {
- "objectType" : "",
- "objectName" : "",
- "startIndex" : 8,
- "stopIndex" : 46,
- "fragment" : "time_diff('SECOND', TIME '12:00:00', c)"
- } ]
-}
+timestamp_ntz(7) array<timestamp_ntz(7)> timestamp_ntz(7)
-- !query
-SELECT last_day(c), next_day(c, 'MO'), datediff(c, '2019-12-31'),
add_months(c, 1)
- FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789') AS t(c)
+SELECT median(c) FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456789'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.123456791')
AS t(c)
-- !query schema
-struct<last_day(c):date,next_day(c, MO):date,datediff(c,
2019-12-31):int,add_months(c, 1):date>
+struct<median(c):timestamp_ntz(9)>
-- !query output
-2020-01-31 2020-01-06 1 2020-02-01
+2020-01-01 12:00:00.12345679
+
+
+-- !query
+SELECT median(c) FROM VALUES (TIMESTAMP_NTZ '1969-12-31 23:59:59.999999000'),
+ (TIMESTAMP_NTZ '1970-01-01 00:00:00.000000001')
AS t(c)
+-- !query schema
+struct<median(c):timestamp_ntz(9)>
+-- !query output
+1969-12-31 23:59:59.9999995
+
+
+-- !query
+SELECT percentile(c, 0.7), percentile_cont(0.3) WITHIN GROUP (ORDER BY c DESC)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000100'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000200'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000300'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000001300') AS t(c)
+-- !query schema
+struct<percentile(c, 0.7, 1):timestamp_ntz(9),percentile_cont(0.3) WITHIN
GROUP (ORDER BY c DESC):timestamp_ntz(9)>
+-- !query output
+2020-01-01 12:00:00.0000004 2020-01-01 12:00:00.0000004
+
+
+-- !query
+SELECT median(c), percentile(c, array(0.0, 1.0))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001' AS timestamp_ntz(7))),
+ (CAST('2020-01-01 12:00:00.0000004' AS timestamp_ntz(7))) AS t(c)
+-- !query schema
+struct<median(c):timestamp_ntz(7),percentile(c, array(0.0, 1.0),
1):array<timestamp_ntz(7)>>
+-- !query output
+2020-01-01 12:00:00.0000002 [2020-01-01 12:00:00.0000001,2020-01-01
12:00:00.0000004]
+
+
+-- !query
+SELECT k, median(c) OVER (PARTITION BY k)
+ FROM VALUES (1, TIMESTAMP_NTZ '2020-01-01 12:00:00.000000001'),
+ (1, TIMESTAMP_NTZ '2020-01-01 12:00:00.000000003'),
+ (2, TIMESTAMP_NTZ '2020-01-01 12:00:00.000000005') AS t(k, c)
+-- !query schema
+struct<k:int,median(c) OVER (PARTITION BY k ROWS BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING):timestamp_ntz(9)>
+-- !query output
+1 2020-01-01 12:00:00.000000002
+1 2020-01-01 12:00:00.000000002
+2 2020-01-01 12:00:00.000000005
+
+
+-- !query
+SELECT percentile(c, 0.5, f)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000001', 3),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000009', 1) AS t(c, f)
+-- !query schema
+struct<percentile(c, 0.5, f):timestamp_ntz(9)>
+-- !query output
+2020-01-01 12:00:00.000000001
+
+
+-- !query
+SET spark.sql.ansi.enabled=false
+-- !query schema
+struct<key:string,value:string>
+-- !query output
+spark.sql.ansi.enabled false
+
+
+-- !query
+SELECT median(c), percentile(c, 0.25), percentile_cont(0.75) WITHIN GROUP
(ORDER BY c),
+ percentile_disc(0.5) WITHIN GROUP (ORDER BY c)
+ FROM VALUES (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000100'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000000400'),
+ (TIMESTAMP_NTZ '2020-01-01 12:00:00.000001000'), (NULL) AS t(c)
+-- !query schema
+struct<median(c):timestamp_ntz(9),percentile(c, 0.25,
1):timestamp_ntz(9),percentile_cont(0.75) WITHIN GROUP (ORDER BY
c):timestamp_ntz(9),percentile_disc(0.5) WITHIN GROUP (ORDER BY
c):timestamp_ntz(9)>
+-- !query output
+2020-01-01 12:00:00.0000004 2020-01-01 12:00:00.00000025 2020-01-01
12:00:00.0000007 2020-01-01 12:00:00.0000004
+
+
+-- !query
+SELECT typeof(median(c))
+ FROM VALUES (CAST('2020-01-01 12:00:00.0000001' AS timestamp_ntz(7))) AS t(c)
+-- !query schema
+struct<typeof(median(c)):string>
+-- !query output
+timestamp_ntz(7)
-- !query
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
b/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
index b9916957725d..5b99decc1d5e 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/TimestampNanosFunctionsSuiteBase.scala
@@ -911,6 +911,82 @@ abstract class TimestampNanosFunctionsSuiteBase extends
SharedSparkSession {
}
}
+ test("SPARK-60542: percentile family over nanosecond timestamps keeps the
input type") {
+ Seq(7, 8, 9).foreach { p =>
+ val schema = new StructType()
+ .add("ntz", TimestampNTZNanosType(p))
+ .add("ltz", TimestampLTZNanosType(p))
+ // Sub-microsecond offsets that are multiples of 100ns (exact at every
p) plus a NULL row,
+ // spread over several partitions so that partial buffers get merged.
+ val ldt = LocalDateTime.parse("2020-01-01T12:00:00")
+ val ins = Instant.parse("2020-01-01T12:00:00Z")
+ val data = Seq(100, 400, 1000).map(n => Row(ldt.plusNanos(n),
ins.plusNanos(n))) :+
+ Row(null, null)
+ val df = spark.createDataFrame(spark.sparkContext.parallelize(data, 3),
schema)
+ val exprs = Seq(
+ "median(%s)",
+ "percentile(%s, 0.25)",
+ "percentile_cont(0.75) WITHIN GROUP (ORDER BY %s)",
+ "percentile_cont(0.25) WITHIN GROUP (ORDER BY %s DESC)",
+ "percentile_disc(0.5) WITHIN GROUP (ORDER BY %s)")
+ // The interpolated 250ns is floored to 200ns at p = 7.
+ val expectedNanos = Seq(400, if (p == 7) 200 else 250, 700, 700, 400)
+ val ntzRes = df.selectExpr(exprs.map(_.format("ntz")): _*)
+ assert(ntzRes.schema.map(_.dataType) ===
Seq.fill(exprs.length)(TimestampNTZNanosType(p)))
+ checkAnswer(ntzRes, Row(expectedNanos.map(ldt.plusNanos(_)): _*))
+ val ltzRes = df.selectExpr(exprs.map(_.format("ltz")): _*)
+ assert(ltzRes.schema.map(_.dataType) ===
Seq.fill(exprs.length)(TimestampLTZNanosType(p)))
+ checkAnswer(ltzRes, Row(expectedNanos.map(ins.plusNanos(_)): _*))
+
+ // The Scala Column API agrees, also for an array of percentages.
+ val arrayRes = df.select(
+ median(col("ntz")), percentile(col("ltz"), array(lit(0.25),
lit(0.75))))
+ assert(arrayRes.schema.map(_.dataType) === Seq(
+ TimestampNTZNanosType(p), ArrayType(TimestampLTZNanosType(p),
containsNull = false)))
+ checkAnswer(arrayRes,
+ Row(ldt.plusNanos(400), Seq(expectedNanos(1),
700).map(ins.plusNanos(_))))
+
+ // A non-dyadic percentage uses an exact position: 3 * 0.7 is
2.0999999999999996 as a double.
+ val df2 = spark.createDataFrame(spark.sparkContext.parallelize(
+ Seq(100, 200, 300, 1300).map(n => Row(ldt.plusNanos(n),
ins.plusNanos(n)))), schema)
+ checkAnswer(
+ df2.selectExpr(
+ "percentile(ntz, 0.7)", "percentile_cont(0.3) WITHIN GROUP (ORDER BY
ltz DESC)"),
+ Row(ldt.plusNanos(400), ins.plusNanos(400)))
+ }
+ }
+
+ test("SPARK-60542: percentile over nanosecond timestamps interpolates
exactly") {
+ def median9(values: String*): DataFrame = spark.createDataFrame(
+ spark.sparkContext.parallelize(values.map(v =>
Row(LocalDateTime.parse(v)))),
+ new StructType().add("c",
TimestampNTZNanosType(9))).selectExpr("median(c)")
+ // Both values map to the same double, so a double interpolation can't
return .123456790.
+ checkAnswer(
+ median9("2020-01-01T12:00:00.123456789",
"2020-01-01T12:00:00.123456791"),
+ Row(LocalDateTime.parse("2020-01-01T12:00:00.123456790")))
+ // Pre-epoch: the midpoint -499.5ns is floored to -500ns.
+ checkAnswer(
+ median9("1969-12-31T23:59:59.999999", "1970-01-01T00:00:00.000000001"),
+ Row(LocalDateTime.parse("1969-12-31T23:59:59.999999500")))
+ // The full 0001..9999 range overflows a long in nanoseconds.
+ checkAnswer(
+ median9("0001-01-01T00:00:00", "9999-12-31T23:59:59.999999999"),
+ Row(LocalDateTime.parse("5000-07-02T11:59:59.999999999")))
+ checkAnswer(median9("2020-01-01T12:00:00.123456789"),
+ Row(LocalDateTime.parse("2020-01-01T12:00:00.123456789")))
+ checkAnswer(median9(), Row(null))
+ // A NaN percentage passes analysis and returns the first value, as for
other types.
+ val first = LocalDateTime.parse("2020-01-01T12:00:00.000000100")
+ val df = spark.createDataFrame(
+ spark.sparkContext.parallelize(Seq(Row(first),
Row(first.plusNanos(100)))),
+ new StructType().add("c", TimestampNTZNanosType(9)))
+ checkAnswer(
+ df.selectExpr(
+ "percentile(c, CAST('NaN' AS DOUBLE))",
+ "percentile_cont(CAST('NaN' AS DOUBLE)) WITHIN GROUP (ORDER BY c)"),
+ Row(first, first))
+ }
+
test("SPARK-57816: date_format / to_char / to_varchar over
nanosecond-precision timestamps") {
// The 9-`S` pattern is a fixed-width fraction field, so it always emits 9
digits; truncating to
// precision `p` zeros the low digits (floor); it does not drop them. The
session zone is
diff --git
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2StrategySuite.scala
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2StrategySuite.scala
index 5e62431b404d..533ac9b593cc 100644
---
a/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2StrategySuite.scala
+++
b/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2StrategySuite.scala
@@ -26,7 +26,7 @@ import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.analysis.UnresolvedAttribute
import org.apache.spark.sql.catalyst.dsl.expressions._
import org.apache.spark.sql.catalyst.expressions._
-import org.apache.spark.sql.catalyst.expressions.aggregate.Count
+import
org.apache.spark.sql.catalyst.expressions.aggregate.{AggregateExpression,
AggregateFunction, Complete, Count, Percentile, PercentileDisc}
import org.apache.spark.sql.catalyst.expressions.variant.VariantGet
import org.apache.spark.sql.catalyst.optimizer.{BooleanSimplification,
CombineFilters, ConstantFolding}
import org.apache.spark.sql.catalyst.plans.Inner
@@ -43,7 +43,7 @@ import org.apache.spark.sql.internal.SQLConf
import org.apache.spark.sql.internal.connector.SupportsPushDownCatalystFilters
import org.apache.spark.sql.sources.{BaseRelation, TableScan}
import org.apache.spark.sql.test.SharedSparkSession
-import org.apache.spark.sql.types.{ArrayType, BooleanType, DoubleType,
FloatType, IntegerType, LongType, StringType, StructField, StructType,
TimestampType, TimeType, VariantType}
+import org.apache.spark.sql.types.{ArrayType, BooleanType, DoubleType,
FloatType, IntegerType, LongType, StringType, StructField, StructType,
TimestampNTZNanosType, TimestampType, TimeType, VariantType}
import org.apache.spark.sql.util.CaseInsensitiveStringMap
import org.apache.spark.udf.worker.UDFWorkerSpecification
import org.apache.spark.unsafe.types.UTF8String
@@ -1010,6 +1010,17 @@ class DataSourceV2StrategySuite extends
SharedSparkSession {
assert(result.isEmpty, "null path should not translate (graceful, no NPE)")
}
+ test("SPARK-60542: percentile_cont over nanosecond timestamps is not pushed
down") {
+ def translate(agg: AggregateFunction): Option[V2Expression] =
+ new V2ExpressionBuilder(AggregateExpression(agg, Complete, isDistinct =
false)).build()
+ val d = AttributeReference("d", DoubleType)()
+ val ts = AttributeReference("ts", TimestampNTZNanosType(9))()
+ assert(translate(new Percentile(d, Literal(0.5))).isDefined)
+ // A source would interpolate with its own rounding; percentile_disc
returns an input value.
+ assert(translate(new Percentile(ts, Literal(0.5))).isEmpty)
+ assert(translate(PercentileDisc(ts, Literal(0.5))).isDefined)
+ }
+
test("VariantGet with non-column child returns None") {
val lit = Literal("v")
val path = Literal.create("$.a", StringType)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]