diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala index f55b0545ee9c..67e79297b64f 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/IntervalUtils.scala @@ -218,27 +218,22 @@ object IntervalUtils { minutes = toLongWithRange("second", m.group(7), 0, 59) } // Hive allow nanosecond precision interval - val nanoStr = if (m.group(9) == null) { - null - } else { - (m.group(9) + "000000000").substring(0, 9) - } - var nanos = toLongWithRange("nanosecond", nanoStr, 0L, 999999999L) + var secondsFraction = parseNanos(m.group(9), seconds < 0) to match { case "hour" => minutes = 0 seconds = 0 - nanos = 0 + secondsFraction = 0 case "minute" => seconds = 0 - nanos = 0 + secondsFraction = 0 case "second" => // No-op case _ => throw new IllegalArgumentException( s"Cannot support (interval '$input' $from to $to) expression") } - var micros = nanos / DateTimeUtils.NANOS_PER_MICROS + var micros = secondsFraction micros = Math.addExact(micros, Math.multiplyExact(days, DateTimeUtils.MICROS_PER_DAY)) micros = Math.addExact(micros, Math.multiplyExact(hours, MICROS_PER_HOUR)) micros = Math.addExact(micros, Math.multiplyExact(minutes, MICROS_PER_MINUTE)) @@ -292,6 +287,21 @@ object IntervalUtils { new CalendarInterval(months, microseconds) } + // Parses a string with nanoseconds, truncates the result and returns microseconds + private def parseNanos(nanosStr: String, isNegative: Boolean): Long = { + if (nanosStr != null) { + val maxNanosLen = 9 + val alignedStr = if (nanosStr.length < maxNanosLen) { + (nanosStr + "000000000").substring(0, maxNanosLen) + } else nanosStr + val nanos = toLongWithRange("nanosecond", alignedStr, 0L, 999999999L) + val micros = nanos / DateTimeUtils.NANOS_PER_MICROS + if (isNegative) -micros else micros + } else { + 0L + } + } + /** * Parse second_nano string in ss.nnnnnnnnn format to microseconds */ @@ -303,15 +313,13 @@ object IntervalUtils { Long.MinValue / DateTimeUtils.MICROS_PER_SECOND, Long.MaxValue / DateTimeUtils.MICROS_PER_SECOND) * DateTimeUtils.MICROS_PER_SECOND } - def parseNanos(nanosStr: String): Long = { - toLongWithRange("nanosecond", nanosStr, 0L, 999999999L) / DateTimeUtils.NANOS_PER_MICROS - } secondNano.split("\\.") match { case Array(secondsStr) => parseSeconds(secondsStr) - case Array("", nanosStr) => parseNanos(nanosStr) + case Array("", nanosStr) => parseNanos(nanosStr, false) case Array(secondsStr, nanosStr) => - Math.addExact(parseSeconds(secondsStr), parseNanos(nanosStr)) + val seconds = parseSeconds(secondsStr) + Math.addExact(seconds, parseNanos(nanosStr, seconds < 0)) case _ => throw new IllegalArgumentException( "Interval string does not match second-nano format of ss.nnnnnnnnn") diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/ExpressionParserSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/ExpressionParserSuite.scala index 5a7b3ffec53f..5423f7516c2e 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/ExpressionParserSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/parser/ExpressionParserSuite.scala @@ -24,7 +24,7 @@ import org.apache.spark.sql.catalyst.FunctionIdentifier import org.apache.spark.sql.catalyst.analysis.{UnresolvedAttribute, _} import org.apache.spark.sql.catalyst.expressions._ import org.apache.spark.sql.catalyst.expressions.aggregate.{First, Last} -import org.apache.spark.sql.catalyst.util.{DateTimeTestUtils, IntervalUtils} +import org.apache.spark.sql.catalyst.util.{DateTimeTestUtils, DateTimeUtils, IntervalUtils} import org.apache.spark.sql.internal.SQLConf import org.apache.spark.sql.types._ import org.apache.spark.unsafe.types.CalendarInterval @@ -628,7 +628,17 @@ class ExpressionParserSuite extends AnalysisTest { // Hive nanosecond notation. checkIntervals("13.123456789 seconds", intervalLiteral("second", "13.123456789")) - checkIntervals("-13.123456789 second", intervalLiteral("second", "-13.123456789")) + checkIntervals( + "-13.123456789 second", + Literal(new CalendarInterval( + 0, + -13 * DateTimeUtils.MICROS_PER_SECOND - 123 * DateTimeUtils.MICROS_PER_MILLIS - 456))) + checkIntervals( + "13.123456 second", + Literal(new CalendarInterval( + 0, + 13 * DateTimeUtils.MICROS_PER_SECOND + 123 * DateTimeUtils.MICROS_PER_MILLIS + 456))) + checkIntervals("1.001 second", Literal(IntervalUtils.fromString("1 second 1 millisecond"))) // Non Existing unit intercept("interval 10 nanoseconds", diff --git a/sql/core/src/test/resources/sql-tests/results/literals.sql.out b/sql/core/src/test/resources/sql-tests/results/literals.sql.out index 115287821bf4..550b9bd936a0 100644 --- a/sql/core/src/test/resources/sql-tests/results/literals.sql.out +++ b/sql/core/src/test/resources/sql-tests/results/literals.sql.out @@ -323,9 +323,9 @@ select timestamp '2016-33-11 20:54:00.000' -- !query 34 select interval 13.123456789 seconds, interval -13.123456789 second -- !query 34 schema -struct +struct -- !query 34 output -interval 13 seconds 123 milliseconds 456 microseconds interval -12 seconds -876 milliseconds -544 microseconds +interval 13 seconds 123 milliseconds 456 microseconds interval -13 seconds -123 milliseconds -456 microseconds -- !query 35