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
