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 @@ -148,8 +148,10 @@ class ToSparkExpression(
override def visit(
expr: SExpression.IntervalYearLiteral,
context: EmptyVisitationContext): Literal = {
// Spark uses a single months Int as the "physical" type for YearMonthInterval
val months = expr.years() * 12 + expr.months()
// Spark uses a single months Int as the "physical" type for YearMonthInterval. Both operands
// are int32 on the wire, so unlike the day-time case nothing widens the arithmetic and a large
// year count wraps a positive interval into a negative one; report it instead.
val months = Math.addExact(Math.multiplyExact(expr.years(), 12), expr.months())
Literal(months, ToSparkType.convert(expr.getType))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,22 @@ class TypesAndLiteralsSuite extends SparkFunSuite {
sparkLiteral(ExpressionCreator.intervalDay(false, Int.MaxValue, 0, 0L, 6)))
}

test("a year-month interval reports overflow instead of wrapping") {
// years and months are int32 on the wire and Spark's physical type is a months Int, so there
// is no Long operand to widen this the way the day-time case is widened. Far outside the
// spec's 10,000-year bound, but it used to turn a large positive interval into a negative one:
// 178,956,971 years came out as -2,147,483,644 months, and Int.MaxValue years as -12.
intercept[ArithmeticException](
sparkLiteral(ExpressionCreator.intervalYear(false, 178956971, 0)))
intercept[ArithmeticException](
sparkLiteral(ExpressionCreator.intervalYear(false, Int.MaxValue, 0)))

// The spec's maximum still converts, and the months carry through.
val atTheBound = sparkLiteral(ExpressionCreator.intervalYear(false, 10000, 0))
assert(atTheBound.value === 120000)
assert(atTheBound.dataType === YearMonthIntervalType.DEFAULT)
}

test("a coarser precision on a type is rejected, since a type has no value to rescale") {
// A Spark type carries no precision of its own, so mapping precision_timestamp<3> onto
// TimestampNTZType would reinterpret millisecond counts as microsecond ones. Only the literal
Expand Down
Loading