Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2090,11 +2090,11 @@ object WidthBucket {
arguments = """
Arguments:
* value - The value to assign to a bucket.
An expression that evaluates to a double or interval.
An expression that evaluates to a double, interval, or time.
* min_value - The minimum value of the histogram range.
An expression that evaluates to a double or interval.
An expression that evaluates to a double, interval, or time.
* max_value - The maximum value of the histogram range.
An expression that evaluates to a double or interval.
An expression that evaluates to a double, interval, or time.
* num_bucket - The number of equiwidth buckets in the histogram.
An expression that evaluates to a long.
""",
Expand All @@ -2116,6 +2116,8 @@ object WidthBucket {
1
> SELECT _FUNC_(INTERVAL '1' DAY, INTERVAL '0' DAY, INTERVAL '10' DAY, 10);
2
> SELECT _FUNC_(TIME'12:00:00', TIME'09:00:00', TIME'17:00:00', 8);
4
""",
since = "3.1.0",
group = "math_funcs")
Expand All @@ -2128,9 +2130,9 @@ case class WidthBucket(
override def nullIntolerant: Boolean = true

override def inputTypes: Seq[AbstractDataType] = Seq(
TypeCollection(DoubleType, YearMonthIntervalType, DayTimeIntervalType),
TypeCollection(DoubleType, YearMonthIntervalType, DayTimeIntervalType),
TypeCollection(DoubleType, YearMonthIntervalType, DayTimeIntervalType),
TypeCollection(DoubleType, YearMonthIntervalType, DayTimeIntervalType, AnyTimeType),
TypeCollection(DoubleType, YearMonthIntervalType, DayTimeIntervalType, AnyTimeType),
TypeCollection(DoubleType, YearMonthIntervalType, DayTimeIntervalType, AnyTimeType),
LongType)

override def checkInputDataTypes(): TypeCheckResult = {
Expand All @@ -2141,6 +2143,8 @@ case class WidthBucket(
TypeCheckSuccess
case (_: DayTimeIntervalType, _: DayTimeIntervalType, _: DayTimeIntervalType) =>
TypeCheckSuccess
case (_: TimeType, _: TimeType, _: TimeType) =>
TypeCheckSuccess
case _ =>
val types = Seq(value.dataType, minValue.dataType, maxValue.dataType)
TypeUtils.checkForSameTypeInputExpr(types, prettyName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package org.apache.spark.sql.catalyst.expressions

import java.nio.charset.StandardCharsets
import java.time.{Duration, Period}
import java.time.{Duration, LocalTime, Period}
import java.time.temporal.ChronoUnit

import com.google.common.math.LongMath
Expand Down Expand Up @@ -1044,6 +1044,38 @@ class MathExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper {
checkEvaluation(WidthBucket(5.35, 0.024, Double.PositiveInfinity, 5L), null)
}

test("width_bucket with the TIME type") {
def time(t: LocalTime, precision: Int = TimeType.MICROS_PRECISION): Literal =
Literal.create(t.toNanoOfDay, TimeType(precision))
val t08 = time(LocalTime.of(8, 0, 0))
val t09 = time(LocalTime.of(9, 0, 0))
val t12 = time(LocalTime.of(12, 0, 0))
val t17 = time(LocalTime.of(17, 0, 0))
val n8 = Literal(8L)

// 09:00:00..17:00:00 split into 8 one-hour buckets.
checkEvaluation(WidthBucket(t12, t09, t17, n8), 4L)
checkEvaluation(WidthBucket(t09, t09, t17, n8), 1L) // lower edge
checkEvaluation(WidthBucket(t17, t09, t17, n8), 9L) // >= max -> numBucket + 1
checkEvaluation(WidthBucket(t08, t09, t17, n8), 0L) // < min -> 0
// Reversed range (min > max).
checkEvaluation(WidthBucket(t12, t17, t09, n8), 6L)

// Mixed precisions are allowed; comparison uses the shared nanos-of-day value.
checkEvaluation(
WidthBucket(
time(LocalTime.of(12, 0, 0), 0),
time(LocalTime.of(9, 0, 0), 3),
time(LocalTime.of(17, 0, 0), 9),
n8),
4L)

// Null and degenerate inputs.
checkEvaluation(WidthBucket(Literal.create(null, TimeType()), t09, t17, n8), null)
checkEvaluation(WidthBucket(t12, t09, t17, Literal.create(null, LongType)), null)
checkEvaluation(WidthBucket(t12, t09, t09, n8), null) // min == max
}

test("context independent foldable math expressions") {
// Create some base literals
val intLit = Literal(5)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2527,3 +2527,59 @@ DROP TABLE time_narrow_tbl
-- !query analysis
DropTable false, false
+- ResolvedIdentifier V2SessionCatalog(spark_catalog), default.time_narrow_tbl


-- !query
select width_bucket(TIME'12:00:00', TIME'09:00:00', TIME'17:00:00', 8)
-- !query analysis
Project [width_bucket(12:00:00, 09:00:00, 17:00:00, cast(8 as bigint)) AS width_bucket(TIME '12:00:00', TIME '09:00:00', TIME '17:00:00', 8)#xL]
+- OneRowRelation


-- !query
select width_bucket(TIME'09:00:00', TIME'09:00:00', TIME'17:00:00', 8)
-- !query analysis
Project [width_bucket(09:00:00, 09:00:00, 17:00:00, cast(8 as bigint)) AS width_bucket(TIME '09:00:00', TIME '09:00:00', TIME '17:00:00', 8)#xL]
+- OneRowRelation


-- !query
select width_bucket(TIME'17:00:00', TIME'09:00:00', TIME'17:00:00', 8)
-- !query analysis
Project [width_bucket(17:00:00, 09:00:00, 17:00:00, cast(8 as bigint)) AS width_bucket(TIME '17:00:00', TIME '09:00:00', TIME '17:00:00', 8)#xL]
+- OneRowRelation


-- !query
select width_bucket(TIME'08:00:00', TIME'09:00:00', TIME'17:00:00', 8)
-- !query analysis
Project [width_bucket(08:00:00, 09:00:00, 17:00:00, cast(8 as bigint)) AS width_bucket(TIME '08:00:00', TIME '09:00:00', TIME '17:00:00', 8)#xL]
+- OneRowRelation


-- !query
select width_bucket(TIME'12:00:00', TIME'17:00:00', TIME'09:00:00', 8)
-- !query analysis
Project [width_bucket(12:00:00, 17:00:00, 09:00:00, cast(8 as bigint)) AS width_bucket(TIME '12:00:00', TIME '17:00:00', TIME '09:00:00', 8)#xL]
+- OneRowRelation


-- !query
select width_bucket(TIME'12:00:00', CAST(TIME'09:00:00' AS TIME(3)), CAST(TIME'17:00:00' AS TIME(9)), 8)
-- !query analysis
Project [width_bucket(12:00:00, cast(09:00:00 as time(3)), cast(17:00:00 as time(9)), cast(8 as bigint)) AS width_bucket(TIME '12:00:00', CAST(TIME '09:00:00' AS TIME(3)), CAST(TIME '17:00:00' AS TIME(9)), 8)#xL]
+- OneRowRelation


-- !query
select width_bucket(CAST(null AS TIME), TIME'09:00:00', TIME'17:00:00', 8)
-- !query analysis
Project [width_bucket(cast(null as time(6)), 09:00:00, 17:00:00, cast(8 as bigint)) AS width_bucket(CAST(NULL AS TIME(6)), TIME '09:00:00', TIME '17:00:00', 8)#xL]
+- OneRowRelation


-- !query
select width_bucket(TIME'12:00:00', TIME'09:00:00', TIME'09:00:00', 8)
-- !query analysis
Project [width_bucket(12:00:00, 09:00:00, 09:00:00, cast(8 as bigint)) AS width_bucket(TIME '12:00:00', TIME '09:00:00', TIME '09:00:00', 8)#xL]
+- OneRowRelation
13 changes: 13 additions & 0 deletions sql/core/src/test/resources/sql-tests/inputs/time.sql
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,16 @@ INSERT INTO time_narrow_tbl SELECT '01:02:03.456789' :: TIME(6);
INSERT INTO time_narrow_tbl SELECT CAST('01:02:03.456789' :: TIME(6) AS TIME(3));
SELECT typeof(t3), t3 FROM time_narrow_tbl;
DROP TABLE time_narrow_tbl;

-- width_bucket over the TIME type: 09:00:00 .. 17:00:00 split into 8 one-hour buckets
select width_bucket(TIME'12:00:00', TIME'09:00:00', TIME'17:00:00', 8);
select width_bucket(TIME'09:00:00', TIME'09:00:00', TIME'17:00:00', 8);
select width_bucket(TIME'17:00:00', TIME'09:00:00', TIME'17:00:00', 8);
select width_bucket(TIME'08:00:00', TIME'09:00:00', TIME'17:00:00', 8);
-- reversed range (min > max)
select width_bucket(TIME'12:00:00', TIME'17:00:00', TIME'09:00:00', 8);
-- mixed precisions are allowed
select width_bucket(TIME'12:00:00', CAST(TIME'09:00:00' AS TIME(3)), CAST(TIME'17:00:00' AS TIME(9)), 8);
-- null and degenerate inputs
select width_bucket(CAST(null AS TIME), TIME'09:00:00', TIME'17:00:00', 8);
select width_bucket(TIME'12:00:00', TIME'09:00:00', TIME'09:00:00', 8);
64 changes: 64 additions & 0 deletions sql/core/src/test/resources/sql-tests/results/time.sql.out
Original file line number Diff line number Diff line change
Expand Up @@ -3034,3 +3034,67 @@ DROP TABLE time_narrow_tbl
struct<>
-- !query output



-- !query
select width_bucket(TIME'12:00:00', TIME'09:00:00', TIME'17:00:00', 8)
-- !query schema
struct<width_bucket(TIME '12:00:00', TIME '09:00:00', TIME '17:00:00', 8):bigint>
-- !query output
4


-- !query
select width_bucket(TIME'09:00:00', TIME'09:00:00', TIME'17:00:00', 8)
-- !query schema
struct<width_bucket(TIME '09:00:00', TIME '09:00:00', TIME '17:00:00', 8):bigint>
-- !query output
1


-- !query
select width_bucket(TIME'17:00:00', TIME'09:00:00', TIME'17:00:00', 8)
-- !query schema
struct<width_bucket(TIME '17:00:00', TIME '09:00:00', TIME '17:00:00', 8):bigint>
-- !query output
9


-- !query
select width_bucket(TIME'08:00:00', TIME'09:00:00', TIME'17:00:00', 8)
-- !query schema
struct<width_bucket(TIME '08:00:00', TIME '09:00:00', TIME '17:00:00', 8):bigint>
-- !query output
0


-- !query
select width_bucket(TIME'12:00:00', TIME'17:00:00', TIME'09:00:00', 8)
-- !query schema
struct<width_bucket(TIME '12:00:00', TIME '17:00:00', TIME '09:00:00', 8):bigint>
-- !query output
6


-- !query
select width_bucket(TIME'12:00:00', CAST(TIME'09:00:00' AS TIME(3)), CAST(TIME'17:00:00' AS TIME(9)), 8)
-- !query schema
struct<width_bucket(TIME '12:00:00', CAST(TIME '09:00:00' AS TIME(3)), CAST(TIME '17:00:00' AS TIME(9)), 8):bigint>
-- !query output
4


-- !query
select width_bucket(CAST(null AS TIME), TIME'09:00:00', TIME'17:00:00', 8)
-- !query schema
struct<width_bucket(CAST(NULL AS TIME(6)), TIME '09:00:00', TIME '17:00:00', 8):bigint>
-- !query output
NULL


-- !query
select width_bucket(TIME'12:00:00', TIME'09:00:00', TIME'09:00:00', 8)
-- !query schema
struct<width_bucket(TIME '12:00:00', TIME '09:00:00', TIME '09:00:00', 8):bigint>
-- !query output
NULL