Repository: calcite
Updated Branches:
  refs/heads/master 75e838fab -> 0d10336a3


[CALCITE-2055] Check year, month, day, hour, minute and second ranges for date 
and time literals (Volodymyr Vysotskyi)

Close apache/calcite#567


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/237b6277
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/237b6277
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/237b6277

Branch: refs/heads/master
Commit: 237b627779a137b6d442e51543571961377862c6
Parents: 75e838f
Author: Volodymyr Vysotskyi <[email protected]>
Authored: Wed Nov 15 14:43:41 2017 +0200
Committer: Julian Hyde <[email protected]>
Committed: Mon Nov 20 14:43:29 2017 -0800

----------------------------------------------------------------------
 .../org/apache/calcite/util/DateString.java     |  52 +++++++--
 .../calcite/util/DateTimeStringUtils.java       |   4 +
 .../org/apache/calcite/util/TimeString.java     |  43 ++++++-
 .../org/apache/calcite/rex/RexBuilderTest.java  | 115 +++++++++++++++++++
 4 files changed, 203 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/237b6277/core/src/main/java/org/apache/calcite/util/DateString.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/DateString.java 
b/core/src/main/java/org/apache/calcite/util/DateString.java
index 5aaa1b8..3a704ff 100644
--- a/core/src/main/java/org/apache/calcite/util/DateString.java
+++ b/core/src/main/java/org/apache/calcite/util/DateString.java
@@ -22,6 +22,7 @@ import com.google.common.base.Preconditions;
 
 import java.util.Calendar;
 import java.util.regex.Pattern;
+import javax.annotation.Nonnull;
 
 /**
  * Date literal.
@@ -34,15 +35,40 @@ public class DateString implements Comparable<DateString> {
 
   final String v;
 
+  /** Internal constructor, no validation. */
+  private DateString(String v, @SuppressWarnings("unused") boolean ignore) {
+    this.v = v;
+  }
+
   /** Creates a DateString. */
   public DateString(String v) {
-    this.v = v;
-    Preconditions.checkArgument(PATTERN.matcher(v).matches(), v);
+    this(v, false);
+    Preconditions.checkArgument(PATTERN.matcher(v).matches(),
+        "Invalid date format:", v);
+    Preconditions.checkArgument(getYear() >= 1 && getYear() <= 9999,
+        "Year out of range:", getYear());
+    Preconditions.checkArgument(getMonth() >= 1 && getMonth() <= 12,
+        "Month out of range:", getMonth());
+    Preconditions.checkArgument(getDay() >= 1 && getDay() <= 31,
+        "Day out of range:", getDay());
   }
 
   /** Creates a DateString for year, month, day values. */
   public DateString(int year, int month, int day) {
-    this(DateTimeStringUtils.ymd(new StringBuilder(), year, month, 
day).toString());
+    this(ymd(year, month, day), true);
+  }
+
+  /** Validates a year-month-date and converts to a string. */
+  private static String ymd(int year, int month, int day) {
+    Preconditions.checkArgument(year >= 1 && year <= 9999,
+        "Year out of range:", year);
+    Preconditions.checkArgument(month >= 1 && month <= 12,
+        "Month out of range:", month);
+    Preconditions.checkArgument(day >= 1 && day <= 31,
+        "Day out of range:", day);
+    final StringBuilder b = new StringBuilder();
+    DateTimeStringUtils.ymd(b, year, month, day);
+    return b.toString();
   }
 
   @Override public String toString() {
@@ -60,7 +86,7 @@ public class DateString implements Comparable<DateString> {
     return v.hashCode();
   }
 
-  @Override public int compareTo(DateString o) {
+  @Override public int compareTo(@Nonnull DateString o) {
     return v.compareTo(o.v);
   }
 
@@ -73,12 +99,24 @@ public class DateString implements Comparable<DateString> {
 
   /** Returns the number of days since the epoch. */
   public int getDaysSinceEpoch() {
-    int year = Integer.valueOf(v.substring(0, 4));
-    int month = Integer.valueOf(v.substring(5, 7));
-    int day = Integer.valueOf(v.substring(8, 10));
+    int year = getYear();
+    int month = getMonth();
+    int day = getDay();
     return DateTimeUtils.ymdToUnixDate(year, month, day);
   }
 
+  private int getYear() {
+    return Integer.parseInt(v.substring(0, 4));
+  }
+
+  private int getMonth() {
+    return Integer.parseInt(v.substring(5, 7));
+  }
+
+  private int getDay() {
+    return Integer.parseInt(v.substring(8, 10));
+  }
+
   /** Creates a DateString that is a given number of days since the epoch. */
   public static DateString fromDaysSinceEpoch(int days) {
     return new DateString(DateTimeUtils.unixDateToString(days));

http://git-wip-us.apache.org/repos/asf/calcite/blob/237b6277/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java 
b/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java
index c0d38df..cb58c1a 100644
--- a/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java
+++ b/core/src/main/java/org/apache/calcite/util/DateTimeStringUtils.java
@@ -33,6 +33,7 @@ public class DateTimeStringUtils {
     return s.toString();
   }
 
+  /** Appends hour:minute:second to a buffer; assumes they are valid. */
   static StringBuilder hms(StringBuilder b, int h, int m, int s) {
     int2(b, h);
     b.append(':');
@@ -42,6 +43,8 @@ public class DateTimeStringUtils {
     return b;
   }
 
+  /** Appends year-month-day and hour:minute:second to a buffer; assumes they
+   * are valid. */
   static StringBuilder ymdhms(StringBuilder b, int year, int month, int day,
       int h, int m, int s) {
     ymd(b, year, month, day);
@@ -50,6 +53,7 @@ public class DateTimeStringUtils {
     return b;
   }
 
+  /** Appends year-month-day to a buffer; assumes they are valid. */
   static StringBuilder ymd(StringBuilder b, int year, int month, int day) {
     int4(b, year);
     b.append('-');

http://git-wip-us.apache.org/repos/asf/calcite/blob/237b6277/core/src/main/java/org/apache/calcite/util/TimeString.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/TimeString.java 
b/core/src/main/java/org/apache/calcite/util/TimeString.java
index ce03d87..8527f67 100644
--- a/core/src/main/java/org/apache/calcite/util/TimeString.java
+++ b/core/src/main/java/org/apache/calcite/util/TimeString.java
@@ -23,6 +23,7 @@ import com.google.common.base.Strings;
 
 import java.util.Calendar;
 import java.util.regex.Pattern;
+import javax.annotation.Nonnull;
 
 /**
  * Time literal.
@@ -36,15 +37,37 @@ public class TimeString implements Comparable<TimeString> {
 
   final String v;
 
+  /** Internal constructor, no validation. */
+  private TimeString(String v, @SuppressWarnings("unused") boolean ignore) {
+    this.v = v;
+  }
+
   /** Creates a TimeString. */
   public TimeString(String v) {
-    this.v = v;
-    Preconditions.checkArgument(PATTERN.matcher(v).matches(), v);
+    this(v, false);
+    Preconditions.checkArgument(PATTERN.matcher(v).matches(),
+        "Invalid time format:", v);
+    Preconditions.checkArgument(getHour() >= 0 && getHour() < 24,
+        "Hour out of range:", getHour());
+    Preconditions.checkArgument(getMinute() >= 0 && getMinute() < 60,
+        "Minute out of range:", getMinute());
+    Preconditions.checkArgument(getSecond() >= 0 && getSecond() < 60,
+        "Second out of range:", getSecond());
   }
 
   /** Creates a TimeString for hour, minute, second and millisecond values. */
   public TimeString(int h, int m, int s) {
-    this(DateTimeStringUtils.hms(new StringBuilder(), h, m, s).toString());
+    this(hms(h, m, s), false);
+  }
+
+  /** Validates an hour-minute-second value and converts to a string. */
+  private static String hms(int h, int m, int s) {
+    Preconditions.checkArgument(h >= 0 && h < 24, "Hour out of range:", h);
+    Preconditions.checkArgument(m >= 0 && m < 60, "Minute out of range:", m);
+    Preconditions.checkArgument(s >= 0 && s < 60, "Second out of range:", s);
+    final StringBuilder b = new StringBuilder();
+    DateTimeStringUtils.hms(b, h, m, s);
+    return b.toString();
   }
 
   /** Sets the fraction field of a {@code TimeString} to a given number
@@ -106,7 +129,7 @@ public class TimeString implements Comparable<TimeString> {
     return v.hashCode();
   }
 
-  @Override public int compareTo(TimeString o) {
+  @Override public int compareTo(@Nonnull TimeString o) {
     return v.compareTo(o.v);
   }
 
@@ -162,6 +185,18 @@ public class TimeString implements Comparable<TimeString> {
     }
   }
 
+  private int getHour() {
+    return Integer.parseInt(v.substring(0, 2));
+  }
+
+  private int getMinute() {
+    return Integer.parseInt(this.v.substring(3, 5));
+  }
+
+  private int getSecond() {
+    return Integer.parseInt(this.v.substring(6, 8));
+  }
+
   public Calendar toCalendar() {
     return Util.calendar(getMillisOfDay());
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/237b6277/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java 
b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
index 02bb385..2edddf8 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
@@ -32,11 +32,13 @@ import org.junit.Test;
 import java.util.Calendar;
 import java.util.TimeZone;
 
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
 
 /**
  * Test for {@link RexBuilder}.
@@ -360,6 +362,119 @@ public class RexBuilderTest {
     assertThat(literal.getValueAs(DateString.class), notNullValue());
   }
 
+  /** Tests {@link DateString} year range. */
+  @Test public void testDateStringYearError() {
+    try {
+      final DateString dateString = new DateString(11969, 7, 21);
+      fail("expected exception, got " + dateString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Year out of range: [11969]"));
+    }
+    try {
+      final DateString dateString = new DateString("12345-01-23");
+      fail("expected exception, got " + dateString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(),
+          containsString("Invalid date format: [12345-01-23]"));
+    }
+  }
+
+  /** Tests {@link DateString} month range. */
+  @Test public void testDateStringMonthError() {
+    try {
+      final DateString dateString = new DateString(1969, 27, 21);
+      fail("expected exception, got " + dateString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Month out of range: [27]"));
+    }
+    try {
+      final DateString dateString = new DateString("1234-13-02");
+      fail("expected exception, got " + dateString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Month out of range: [13]"));
+    }
+  }
+
+  /** Tests {@link DateString} day range. */
+  @Test public void testDateStringDayError() {
+    try {
+      final DateString dateString = new DateString(1969, 7, 41);
+      fail("expected exception, got " + dateString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Day out of range: [41]"));
+    }
+    try {
+      final DateString dateString = new DateString("1234-01-32");
+      fail("expected exception, got " + dateString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Day out of range: [32]"));
+    }
+    // We don't worry about the number of days in a month. 30 is in range.
+    final DateString dateString = new DateString("1234-02-30");
+    assertThat(dateString, notNullValue());
+  }
+
+  /** Tests {@link TimeString} hour range. */
+  @Test public void testTimeStringHourError() {
+    try {
+      final TimeString timeString = new TimeString(111, 34, 56);
+      fail("expected exception, got " + timeString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Hour out of range: [111]"));
+    }
+    try {
+      final TimeString timeString = new TimeString("24:00:00");
+      fail("expected exception, got " + timeString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Hour out of range: [24]"));
+    }
+    try {
+      final TimeString timeString = new TimeString("24:00");
+      fail("expected exception, got " + timeString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(),
+          containsString("Invalid time format: [24:00]"));
+    }
+  }
+
+  /** Tests {@link TimeString} minute range. */
+  @Test public void testTimeStringMinuteError() {
+    try {
+      final TimeString timeString = new TimeString(12, 334, 56);
+      fail("expected exception, got " + timeString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Minute out of range: [334]"));
+    }
+    try {
+      final TimeString timeString = new TimeString("12:60:23");
+      fail("expected exception, got " + timeString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Minute out of range: [60]"));
+    }
+  }
+
+  /** Tests {@link TimeString} second range. */
+  @Test public void testTimeStringSecondError() {
+    try {
+      final TimeString timeString = new TimeString(12, 34, 567);
+      fail("expected exception, got " + timeString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Second out of range: [567]"));
+    }
+    try {
+      final TimeString timeString = new TimeString(12, 34, -4);
+      fail("expected exception, got " + timeString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Second out of range: [-4]"));
+    }
+    try {
+      final TimeString timeString = new TimeString("12:34:60");
+      fail("expected exception, got " + timeString);
+    } catch (IllegalArgumentException e) {
+      assertThat(e.getMessage(), containsString("Second out of range: [60]"));
+    }
+  }
+
 }
 
 // End RexBuilderTest.java

Reply via email to