@@ -22,7 +22,6 @@ import java.util.concurrent.TimeUnit
22
22
23
23
import scala .util .control .NonFatal
24
24
25
- import org .apache .spark .sql .catalyst .parser .{CatalystSqlParser , ParseException }
26
25
import org .apache .spark .sql .catalyst .util .DateTimeConstants ._
27
26
import org .apache .spark .sql .types .Decimal
28
27
import org .apache .spark .unsafe .types .{CalendarInterval , UTF8String }
@@ -102,34 +101,6 @@ object IntervalUtils {
102
101
Decimal (result, 18 , 6 )
103
102
}
104
103
105
- /**
106
- * Converts a string to [[CalendarInterval ]] case-insensitively.
107
- *
108
- * @throws IllegalArgumentException if the input string is not in valid interval format.
109
- */
110
- def fromString (str : String ): CalendarInterval = {
111
- if (str == null ) throw new IllegalArgumentException (" Interval string cannot be null" )
112
- try {
113
- CatalystSqlParser .parseInterval(str)
114
- } catch {
115
- case e : ParseException =>
116
- val ex = new IllegalArgumentException (s " Invalid interval string: $str\n " + e.message)
117
- ex.setStackTrace(e.getStackTrace)
118
- throw ex
119
- }
120
- }
121
-
122
- /**
123
- * A safe version of `fromString`. It returns null for invalid input string.
124
- */
125
- def safeFromString (str : String ): CalendarInterval = {
126
- try {
127
- fromString(str)
128
- } catch {
129
- case _ : IllegalArgumentException => null
130
- }
131
- }
132
-
133
104
private def toLongWithRange (
134
105
fieldName : IntervalUnit ,
135
106
s : String ,
@@ -251,46 +222,6 @@ object IntervalUtils {
251
222
}
252
223
}
253
224
254
- def fromUnitStrings (units : Array [IntervalUnit ], values : Array [String ]): CalendarInterval = {
255
- assert(units.length == values.length)
256
- var months : Int = 0
257
- var days : Int = 0
258
- var microseconds : Long = 0
259
- var i = 0
260
- while (i < units.length) {
261
- try {
262
- units(i) match {
263
- case YEAR =>
264
- months = Math .addExact(months, Math .multiplyExact(values(i).toInt, 12 ))
265
- case MONTH =>
266
- months = Math .addExact(months, values(i).toInt)
267
- case WEEK =>
268
- days = Math .addExact(days, Math .multiplyExact(values(i).toInt, 7 ))
269
- case DAY =>
270
- days = Math .addExact(days, values(i).toInt)
271
- case HOUR =>
272
- val hoursUs = Math .multiplyExact(values(i).toLong, MICROS_PER_HOUR )
273
- microseconds = Math .addExact(microseconds, hoursUs)
274
- case MINUTE =>
275
- val minutesUs = Math .multiplyExact(values(i).toLong, MICROS_PER_MINUTE )
276
- microseconds = Math .addExact(microseconds, minutesUs)
277
- case SECOND =>
278
- microseconds = Math .addExact(microseconds, parseSecondNano(values(i)))
279
- case MILLISECOND =>
280
- val millisUs = Math .multiplyExact(values(i).toLong, MICROS_PER_MILLIS )
281
- microseconds = Math .addExact(microseconds, millisUs)
282
- case MICROSECOND =>
283
- microseconds = Math .addExact(microseconds, values(i).toLong)
284
- }
285
- } catch {
286
- case e : Exception =>
287
- throw new IllegalArgumentException (s " Error parsing interval string: ${e.getMessage}" , e)
288
- }
289
- i += 1
290
- }
291
- new CalendarInterval (months, days, microseconds)
292
- }
293
-
294
225
// Parses a string with nanoseconds, truncates the result and returns microseconds
295
226
private def parseNanos (nanosStr : String , isNegative : Boolean ): Long = {
296
227
if (nanosStr != null ) {
@@ -306,30 +237,6 @@ object IntervalUtils {
306
237
}
307
238
}
308
239
309
- /**
310
- * Parse second_nano string in ss.nnnnnnnnn format to microseconds
311
- */
312
- private def parseSecondNano (secondNano : String ): Long = {
313
- def parseSeconds (secondsStr : String ): Long = {
314
- toLongWithRange(
315
- SECOND ,
316
- secondsStr,
317
- Long .MinValue / MICROS_PER_SECOND ,
318
- Long .MaxValue / MICROS_PER_SECOND ) * MICROS_PER_SECOND
319
- }
320
-
321
- secondNano.split(" \\ ." ) match {
322
- case Array (secondsStr) => parseSeconds(secondsStr)
323
- case Array (" " , nanosStr) => parseNanos(nanosStr, false )
324
- case Array (secondsStr, nanosStr) =>
325
- val seconds = parseSeconds(secondsStr)
326
- Math .addExact(seconds, parseNanos(nanosStr, seconds < 0 ))
327
- case _ =>
328
- throw new IllegalArgumentException (
329
- " Interval string does not match second-nano format of ss.nnnnnnnnn" )
330
- }
331
- }
332
-
333
240
/**
334
241
* Gets interval duration
335
242
*
@@ -558,18 +465,37 @@ object IntervalUtils {
558
465
private final val millisStr = unitToUtf8(MILLISECOND )
559
466
private final val microsStr = unitToUtf8(MICROSECOND )
560
467
468
+ /**
469
+ * A safe version of `stringToInterval`. It returns null for invalid input string.
470
+ */
471
+ def safeStringToInterval (input : UTF8String ): CalendarInterval = {
472
+ try {
473
+ stringToInterval(input)
474
+ } catch {
475
+ case _ : IllegalArgumentException => null
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Converts a string to [[CalendarInterval ]] case-insensitively.
481
+ *
482
+ * @throws IllegalArgumentException if the input string is not in valid interval format.
483
+ */
561
484
def stringToInterval (input : UTF8String ): CalendarInterval = {
562
485
import ParseState ._
486
+ def throwIAE (msg : String , e : Exception = null ) = {
487
+ throw new IllegalArgumentException (s " Error parsing ' $input' to interval, $msg" , e)
488
+ }
563
489
564
490
if (input == null ) {
565
- return null
491
+ throwIAE( " interval string cannot be null" )
566
492
}
567
493
// scalastyle:off caselocale .toLowerCase
568
494
val s = input.trim.toLowerCase
569
495
// scalastyle:on
570
496
val bytes = s.getBytes
571
497
if (bytes.isEmpty) {
572
- return null
498
+ throwIAE( " interval string cannot be empty " )
573
499
}
574
500
var state = PREFIX
575
501
var i = 0
@@ -588,13 +514,19 @@ object IntervalUtils {
588
514
}
589
515
}
590
516
517
+ def currentWord : UTF8String = {
518
+ val strings = s.split(UTF8String .blankString(1 ), - 1 )
519
+ val lenRight = s.substring(i, s.numBytes()).split(UTF8String .blankString(1 ), - 1 ).length
520
+ strings(strings.length - lenRight)
521
+ }
522
+
591
523
while (i < bytes.length) {
592
524
val b = bytes(i)
593
525
state match {
594
526
case PREFIX =>
595
527
if (s.startsWith(intervalStr)) {
596
528
if (s.numBytes() == intervalStr.numBytes()) {
597
- return null
529
+ throwIAE( " interval string cannot be empty " )
598
530
} else {
599
531
i += intervalStr.numBytes()
600
532
}
@@ -627,7 +559,7 @@ object IntervalUtils {
627
559
fractionScale = (NANOS_PER_SECOND / 10 ).toInt
628
560
i += 1
629
561
state = VALUE_FRACTIONAL_PART
630
- case _ => return null
562
+ case _ => throwIAE( s " unrecognized number ' $currentWord ' " )
631
563
}
632
564
case TRIM_BEFORE_VALUE => trimToNextState(b, VALUE )
633
565
case VALUE =>
@@ -636,13 +568,13 @@ object IntervalUtils {
636
568
try {
637
569
currentValue = Math .addExact(Math .multiplyExact(10 , currentValue), (b - '0' ))
638
570
} catch {
639
- case _ : ArithmeticException => return null
571
+ case e : ArithmeticException => throwIAE(e.getMessage, e)
640
572
}
641
573
case ' ' => state = TRIM_BEFORE_UNIT
642
574
case '.' =>
643
575
fractionScale = (NANOS_PER_SECOND / 10 ).toInt
644
576
state = VALUE_FRACTIONAL_PART
645
- case _ => return null
577
+ case _ => throwIAE( s " invalid value ' $currentWord ' " )
646
578
}
647
579
i += 1
648
580
case VALUE_FRACTIONAL_PART =>
@@ -653,14 +585,17 @@ object IntervalUtils {
653
585
case ' ' =>
654
586
fraction /= NANOS_PER_MICROS .toInt
655
587
state = TRIM_BEFORE_UNIT
656
- case _ => return null
588
+ case _ if '0' <= b && b <= '9' =>
589
+ throwIAE(s " interval can only support nanosecond precision, ' $currentWord' is out " +
590
+ s " of range " )
591
+ case _ => throwIAE(s " invalid value ' $currentWord' " )
657
592
}
658
593
i += 1
659
594
case TRIM_BEFORE_UNIT => trimToNextState(b, UNIT_BEGIN )
660
595
case UNIT_BEGIN =>
661
596
// Checks that only seconds can have the fractional part
662
597
if (b != 's' && fractionScale >= 0 ) {
663
- return null
598
+ throwIAE( s " ' $currentWord ' cannot have fractional part " )
664
599
}
665
600
if (isNegative) {
666
601
currentValue = - currentValue
@@ -704,26 +639,26 @@ object IntervalUtils {
704
639
} else if (s.matchAt(microsStr, i)) {
705
640
microseconds = Math .addExact(microseconds, currentValue)
706
641
i += microsStr.numBytes()
707
- } else return null
708
- case _ => return null
642
+ } else throwIAE( s " invalid unit ' $currentWord ' " )
643
+ case _ => throwIAE( s " invalid unit ' $currentWord ' " )
709
644
}
710
645
} catch {
711
- case _ : ArithmeticException => return null
646
+ case e : ArithmeticException => throwIAE(e.getMessage, e)
712
647
}
713
648
state = UNIT_SUFFIX
714
649
case UNIT_SUFFIX =>
715
650
b match {
716
651
case 's' => state = UNIT_END
717
652
case ' ' => state = TRIM_BEFORE_SIGN
718
- case _ => return null
653
+ case _ => throwIAE( s " invalid unit ' $currentWord ' " )
719
654
}
720
655
i += 1
721
656
case UNIT_END =>
722
657
b match {
723
658
case ' ' =>
724
659
i += 1
725
660
state = TRIM_BEFORE_SIGN
726
- case _ => return null
661
+ case _ => throwIAE( s " invalid unit ' $currentWord ' " )
727
662
}
728
663
}
729
664
}
0 commit comments