From 42eb923aebcdf6aaa1bbc0d540aeecf913c08358 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20B=C3=A4r?=
Date: Tue, 16 Oct 2018 09:17:45 +0200
Subject: [PATCH 1/8] PMI-97: Convert and parse INTERVALS.
---
.classpath | 45 ++++++---
doc/design.md | 34 +++++++
doc/system_requirements.md | 39 +++++---
pom.xml | 6 ++
.../interval/IntervalDayToSecond.java | 99 +++++++++++++++++++
.../datatype/interval/TestInterval.java | 47 +++++++++
6 files changed, 241 insertions(+), 29 deletions(-)
create mode 100644 src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
create mode 100644 src/test/java/com/exasol/datatype/interval/TestInterval.java
diff --git a/.classpath b/.classpath
index c3b4b716..ebfacabc 100644
--- a/.classpath
+++ b/.classpath
@@ -1,15 +1,34 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/design.md b/doc/design.md
index aa444da3..580a4e20 100644
--- a/doc/design.md
+++ b/doc/design.md
@@ -112,4 +112,38 @@ Covers:
* `req~rendering.sql.confiugrable-identifier-quoting~1`
+Needs: impl, utest
+
+### Exasol Dialect Specific
+
+#### Converting from 64 bit Integers to INTERVAL DAY TO SECOND
+`dsn~exasol.converting-int-to-interval-day-to-second~1`
+
+The data converter converts integers to `INTERVAL DAY TO SECOND`.
+
+Covers:
+
+* `req~integer-interval-conversion~1`
+
+Needs: impl, utest
+
+#### Parsing INTERVAL DAY TO SECOND From Strings
+`dsn~exasol.parsing-interval-day-to-second-from-strings~1`
+
+The data converter can parse `INTERVAL DAY TO SECOND` from strings in the following format:
+
+ inverval-d2s = [ days SP ] hours ":" minutes [ ":" seconds [ "." milliseconds ] ]
+
+ hours = "2" "0" - "3" / [ "0" / "1" ] DIGIT
+
+ minutes = "5" DIGIT / [ "0" - "4" ] DIGIT
+
+ seconds = "5" DIGIT / [ "0" - "4" ] DIGIT
+
+ milliseconds = 1*3DIGIT
+
+Covers:
+
+* `req~integer-interval-conversion~1`
+
Needs: impl, utest
\ No newline at end of file
diff --git a/doc/system_requirements.md b/doc/system_requirements.md
index 109c6efd..824be8d3 100644
--- a/doc/system_requirements.md
+++ b/doc/system_requirements.md
@@ -84,6 +84,17 @@ Making sure at compile time that illegal constructs do not compile make the resu
Needs: req
+### Data Conversion
+`feat~data-conversion~1`
+
+ESB converts between values of compatible data types.
+
+Rationale:
+
+Different databases and related tools use different ways to store and process similar data types. A collection of well-tested converters saves the API users time and trouble.
+
+Needs: req
+
## Functional Requirements
### Statement Structure
@@ -202,7 +213,7 @@ Covers:
Needs: dsn
-###### Configurable Identifier Quoting
+#### Configurable Identifier Quoting
`req~rendering.sql.confiugrable-identifier-quoting~1`
ESB allows users to choose whether the following identifiers should be quoted in the rendered query:
@@ -221,10 +232,6 @@ Covers:
Needs: dsn
-#### TODO
-
-* One line / pretty
-
#### SELECT Statement Rendering
`req~rendering.sql.select~1`
@@ -247,19 +254,19 @@ Covers:
Needs: dsn
-### TODO
+### Exasol Dialect Specific Requirements
+
+###### Integer - Interval Conversion
+`req~integer-interval-conversion~1`
----
+ESB converts values of type `INTERVAL` to integer and vice-versa.
-SELECT
-* Fields
-* Asterisk ("*")
+Rationale:
+
+Neighboring systems of an Exasol database often do to have equivalent data types, so conversion to a primitive data type is required.
-FROM
+Covers:
-( INNER / ( LEFT / RIGHT / FULL ) OUTER ) JOIN
-* ON
+* [feat~data-conversion~1](#data-conversion)
-LIMIT
-* offset
-* count
+Needs: dsn
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index efa0541c..d891ef7e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,12 @@
${junit.platform.version}
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit.version}
+ test
+
org.hamcrest
hamcrest-all
diff --git a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
new file mode 100644
index 00000000..d77f5eea
--- /dev/null
+++ b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
@@ -0,0 +1,99 @@
+package com.exasol.datatype.interval;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class IntervalDayToSecond {
+ private static final long MILLIS_PER_SECOND = 1000L;
+ private static final long SECONDS_PER_MINUTE = 60L;
+ private static final long MINUTES_PER_HOUR = 60L;
+ private static final long HOURS_PER_DAY = 24L;
+ private static final long MILLIS_PER_MINUTE = SECONDS_PER_MINUTE * MILLIS_PER_SECOND;
+ private static final long MILLIS_PER_HOUR = MINUTES_PER_HOUR * MILLIS_PER_MINUTE;
+ private static final long MILLIS_PER_DAY = HOURS_PER_DAY * MILLIS_PER_HOUR;
+ private final long value;
+ private static final int DAYS_MATCHING_GROUP = 1;
+ private static final int HOURS_MATCHING_GROUP = 2;
+ private static final int MINUTES_MATCHING_GROUP = 3;
+ private static final int SECONDS_MATCHING_GROUP = 4;
+ private static final int MILLIS_MATCHING_GROUP = 5;
+ private static final Pattern INTERVAL_PATTERN = Pattern.compile("(?:(\\d{1,9})\\s+)?" // days
+ + "(\\d{1,2})" // hours
+ + ":(\\d{1,2})" // minutes
+ + "(?::(\\d{1,2})" // seconds
+ + "(?:\\.(\\d{1,3}))?)?" // milliseconds
+ );
+
+ private IntervalDayToSecond(final long value) {
+ this.value = value;
+ }
+
+ private IntervalDayToSecond(final String text) {
+ final Matcher matcher = INTERVAL_PATTERN.matcher(text);
+ if (matcher.matches()) {
+ this.value = MILLIS_PER_DAY * parseMatchingGroupToLong(matcher, DAYS_MATCHING_GROUP) //
+ + MILLIS_PER_HOUR * parseMatchingGroupToLong(matcher, HOURS_MATCHING_GROUP) //
+ + MILLIS_PER_MINUTE * parseMatchingGroupToLong(matcher, MINUTES_MATCHING_GROUP) //
+ + MILLIS_PER_SECOND * parseMatchingGroupToLong(matcher, SECONDS_MATCHING_GROUP) //
+ + parseMatchingGroupToLong(matcher, MILLIS_MATCHING_GROUP);
+ } else {
+ throw new IllegalArgumentException(
+ "Text \"" + text + "\" cannot be parsed to an INTERVAL. Must match \"" + INTERVAL_PATTERN + "\"");
+ }
+ }
+
+ private long parseMatchingGroupToLong(final Matcher matcher, final int groupNumber) {
+ return (matcher.group(groupNumber) == null) ? 0 : Long.parseLong(matcher.group(groupNumber));
+ }
+
+ @Override
+ public String toString() {
+ return hasDays() //
+ ? String.format("%d %d:%02d:%02d.%03d", getDays(), getHours(), getMinutes(), getSeconds(), getMillis()) //
+ : String.format("%d:%02d:%02d.%03d", getHours(), getMinutes(), getSeconds(), getMillis());
+ }
+
+ private boolean hasDays() {
+ return this.value >= MILLIS_PER_DAY;
+ }
+
+ private long getDays() {
+ return this.value / MILLIS_PER_DAY;
+ }
+
+ private long getHours() {
+ return this.value / MILLIS_PER_HOUR % HOURS_PER_DAY;
+ }
+
+ private long getMinutes() {
+ return this.value / MILLIS_PER_MINUTE % MINUTES_PER_HOUR;
+ }
+
+ private long getSeconds() {
+ return this.value / MILLIS_PER_SECOND % SECONDS_PER_MINUTE;
+ }
+
+ private long getMillis() {
+ return this.value % MILLIS_PER_SECOND;
+ }
+
+ /**
+ * Create an {@link IntervalDayToSecond} from a number of milliseconds
+ *
+ * @param value milliseconds
+ * @return interval
+ */
+ public static IntervalDayToSecond ofMillis(final long value) {
+ return new IntervalDayToSecond(value);
+ }
+
+ /**
+ * Parse an {@link IntervalDayToSecond} from a string
+ *
+ * @param text
+ * @return
+ */
+ public static IntervalDayToSecond parse(final String text) {
+ return new IntervalDayToSecond(text);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/exasol/datatype/interval/TestInterval.java b/src/test/java/com/exasol/datatype/interval/TestInterval.java
new file mode 100644
index 00000000..915f907e
--- /dev/null
+++ b/src/test/java/com/exasol/datatype/interval/TestInterval.java
@@ -0,0 +1,47 @@
+package com.exasol.datatype.interval;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class TestInterval {
+ // [utest->req~integer-interval-conversion~1]
+ @ParameterizedTest
+ @CsvSource({ //
+ 0L + ", '0:00:00.000'", //
+ 999L + ", '0:00:00.999'", //
+ 59L * 1000 + ", '0:00:59.000'", //
+ 59L * 60 * 1000 + ", '0:59:00.000'", //
+ 23L * 60 * 60 * 1000 + ", '23:00:00.000'", //
+ 999999999L * 24 * 60 * 60 * 1000 + ", '999999999 0:00:00.000'", //
+ 1L * 24 * 60 * 60 * 1000 + 1 * 60 * 60 * 1000 + 1 * 60 * 1000 + 1 * 1000 + 1 + ", '1 1:01:01.001'" //
+ })
+ void testMillisecondsToIntervalDayToSecond(final long value, final String expected) {
+ assertThat(IntervalDayToSecond.ofMillis(value).toString(), equalTo(expected));
+ }
+
+ // [utest->dsn~exasol.parsing-interval-day-to-second-from-strings~1]
+ @ParameterizedTest
+ @CsvSource({ "'0:0', '0:00:00.000'", //
+ "'1:2:3', '1:02:03.000'", //
+ "'11:22:33.444', '11:22:33.444'", //
+ "'1 22:33:44.555', '1 22:33:44.555'", //
+ "'999999999 22:33:44', '999999999 22:33:44.000'" //
+ })
+ void testParse(final String text, final String expected) {
+ assertThat(IntervalDayToSecond.parse(text).toString(), equalTo(expected));
+ }
+
+ // [utest->dsn~exasol.parsing-interval-day-to-second-from-strings~1]
+ @ParameterizedTest
+ @ValueSource(strings = { "0", ":0", "1.0", "123:45", "12:234", "12:34:567", "12:34:56:7890", //
+ "1000000000 0:0" //
+ })
+ void testParseIllegalInputThrowsException(final String text) {
+ assertThrows(IllegalArgumentException.class, () -> IntervalDayToSecond.parse(text));
+ }
+}
\ No newline at end of file
From fafa12f5e380ba7ba56ea747af1e3b5a134f4d21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20B=C3=A4r?=
Date: Tue, 16 Oct 2018 12:29:09 +0200
Subject: [PATCH 2/8] Added missing documentation.
---
.../com/exasol/datatype/interval/IntervalDayToSecond.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
index d77f5eea..4907a250 100644
--- a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
+++ b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
@@ -3,6 +3,10 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+/**
+ * This class implements the Exasol-proprietary data type INTERVAL DAY(x) TO SECONDS(y). It supports
+ * conversions to and from strings and from milliseconds.
+ */
public class IntervalDayToSecond {
private static final long MILLIS_PER_SECOND = 1000L;
private static final long SECONDS_PER_MINUTE = 60L;
@@ -11,7 +15,6 @@ public class IntervalDayToSecond {
private static final long MILLIS_PER_MINUTE = SECONDS_PER_MINUTE * MILLIS_PER_SECOND;
private static final long MILLIS_PER_HOUR = MINUTES_PER_HOUR * MILLIS_PER_MINUTE;
private static final long MILLIS_PER_DAY = HOURS_PER_DAY * MILLIS_PER_HOUR;
- private final long value;
private static final int DAYS_MATCHING_GROUP = 1;
private static final int HOURS_MATCHING_GROUP = 2;
private static final int MINUTES_MATCHING_GROUP = 3;
@@ -23,6 +26,7 @@ public class IntervalDayToSecond {
+ "(?::(\\d{1,2})" // seconds
+ "(?:\\.(\\d{1,3}))?)?" // milliseconds
);
+ private final long value;
private IntervalDayToSecond(final long value) {
this.value = value;
From aaf4bf6ba7a2055e647ea093f3391b4512c1e0e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20B=C3=A4r?=
Date: Tue, 16 Oct 2018 13:00:25 +0200
Subject: [PATCH 3/8] PMI-97: Improved API documentation.
---
.../interval/IntervalDayToSecond.java | 39 +++++++++++++++++--
1 file changed, 35 insertions(+), 4 deletions(-)
diff --git a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
index 4907a250..0e682801 100644
--- a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
+++ b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
@@ -6,6 +6,21 @@
/**
* This class implements the Exasol-proprietary data type INTERVAL DAY(x) TO SECONDS(y). It supports
* conversions to and from strings and from milliseconds.
+ *
+ *
+ * In Exasol this data type represents a time difference consisting of the following components:
+ *
+ *
+ * - days
+ * - hours
+ * - minutes
+ * - seconds
+ * - milliseconds (or fraction of seconds)
+ *
+ *
+ * Since milliseconds are the highest resolution, each interval can also be expresses as a total number of milliseconds.
+ * This is also the recommended way to represent the interval values in other systems which do not natively support this
+ * data type.
*/
public class IntervalDayToSecond {
private static final long MILLIS_PER_SECOND = 1000L;
@@ -84,8 +99,8 @@ private long getMillis() {
/**
* Create an {@link IntervalDayToSecond} from a number of milliseconds
*
- * @param value milliseconds
- * @return interval
+ * @param value total length of the interval in milliseconds
+ * @return interval with milliseconds resolution
*/
public static IntervalDayToSecond ofMillis(final long value) {
return new IntervalDayToSecond(value);
@@ -94,8 +109,24 @@ public static IntervalDayToSecond ofMillis(final long value) {
/**
* Parse an {@link IntervalDayToSecond} from a string
*
- * @param text
- * @return
+ *
+ * The accepted format is:
+ *
+ *
+ * [dddddddd ]hh:mm[:ss[.SSS]]
+ *
+ * Where
+ *
+ *
+ * - d: day, 1-9 digits, optional
+ * - h: hours, 1-2 digits, mandatory
+ * - m: minutes, 1-2 digits, mandatory
+ * - s: seconds, 1-2 digits, optional
+ * - S: milliseconds, 1-3 digits, optional
+ *
+ *
+ * @param text string representing an interval
+ * @return interval with milliseconds resolution
*/
public static IntervalDayToSecond parse(final String text) {
return new IntervalDayToSecond(text);
From c06e3b49c5fd642f6f26b0cec4aa25d8541ef9ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20B=C3=A4r?=
Date: Tue, 16 Oct 2018 13:09:16 +0200
Subject: [PATCH 4/8] PMI-97: Improved API documentation.
---
.../interval/IntervalDayToSecond.java | 21 ++++++++++++-------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
index 0e682801..492a3333 100644
--- a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
+++ b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
@@ -113,17 +113,22 @@ public static IntervalDayToSecond ofMillis(final long value) {
* The accepted format is:
*
*
- * [dddddddd ]hh:mm[:ss[.SSS]]
+ * [dddddddd ]hh:mm[:ss[.SSS]]
*
* Where
*
- *
- * - d: day, 1-9 digits, optional
- * - h: hours, 1-2 digits, mandatory
- * - m: minutes, 1-2 digits, mandatory
- * - s: seconds, 1-2 digits, optional
- * - S: milliseconds, 1-3 digits, optional
- *
+ *
+ * - d
+ * - day, 1-9 digits, optional
+ * - h
+ * - hours, 1-2 digits, mandatory
+ * - m
+ * - minutes, 1-2 digits, mandatory
+ * - s
+ * - seconds, 1-2 digits, optional
+ * - S
+ * - milliseconds, 1-3 digits, optional
+ *
*
* @param text string representing an interval
* @return interval with milliseconds resolution
From 19c0face7f295714f8010da7270e19c9802f0f6d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20B=C3=A4r?=
Date: Tue, 16 Oct 2018 14:05:44 +0200
Subject: [PATCH 5/8] PMI-97: Fixed requirement trace.
---
.../java/com/exasol/datatype/interval/IntervalDayToSecond.java | 2 ++
src/test/java/com/exasol/datatype/interval/TestInterval.java | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
index 492a3333..905e55e3 100644
--- a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
+++ b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
@@ -102,6 +102,7 @@ private long getMillis() {
* @param value total length of the interval in milliseconds
* @return interval with milliseconds resolution
*/
+ // [impl->dsn~exasol.converting-int-to-interval-day-to-second~1]
public static IntervalDayToSecond ofMillis(final long value) {
return new IntervalDayToSecond(value);
}
@@ -133,6 +134,7 @@ public static IntervalDayToSecond ofMillis(final long value) {
* @param text string representing an interval
* @return interval with milliseconds resolution
*/
+ // [impl->dsn~exasol.parsing-interval-day-to-second-from-strings~1]
public static IntervalDayToSecond parse(final String text) {
return new IntervalDayToSecond(text);
}
diff --git a/src/test/java/com/exasol/datatype/interval/TestInterval.java b/src/test/java/com/exasol/datatype/interval/TestInterval.java
index 915f907e..5b4f04ea 100644
--- a/src/test/java/com/exasol/datatype/interval/TestInterval.java
+++ b/src/test/java/com/exasol/datatype/interval/TestInterval.java
@@ -9,7 +9,7 @@
import org.junit.jupiter.params.provider.ValueSource;
class TestInterval {
- // [utest->req~integer-interval-conversion~1]
+ // [utest->dsn~exasol.converting-int-to-interval-day-to-second~1]
@ParameterizedTest
@CsvSource({ //
0L + ", '0:00:00.000'", //
From 9eef6c27162de399ee52a17b1ea31e953b0db66e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20B=C3=A4r?=
Date: Tue, 16 Oct 2018 14:42:36 +0200
Subject: [PATCH 6/8] PMI-97: Added support for INTERVAL YEAR TO MONTH. Fixed
documentation
---
doc/design.md | 36 ++++++-
.../interval/IntervalDayToSecond.java | 2 +-
.../interval/IntervalYearToMonth.java | 95 +++++++++++++++++++
...rval.java => TestIntervalDayToSecond.java} | 4 +-
.../interval/TestIntervalYearToMonth.java | 42 ++++++++
5 files changed, 172 insertions(+), 7 deletions(-)
create mode 100644 src/main/java/com/exasol/datatype/interval/IntervalYearToMonth.java
rename src/test/java/com/exasol/datatype/interval/{TestInterval.java => TestIntervalDayToSecond.java} (94%)
create mode 100644 src/test/java/com/exasol/datatype/interval/TestIntervalYearToMonth.java
diff --git a/doc/design.md b/doc/design.md
index 580a4e20..4e4368f3 100644
--- a/doc/design.md
+++ b/doc/design.md
@@ -11,7 +11,7 @@ The Data Query Language (DQL) building block is responsible for managing `SELECT
### Fluent Programming
-###### Statement Construction With Fluent Programming
+#### Statement Construction With Fluent Programming
`dsn~statement-construction-with-fluent-programming~1`
All statement builders use the "fluent programming" model, where the return type of each builder step determines the possible next structural elements that can be added.
@@ -134,11 +134,11 @@ The data converter can parse `INTERVAL DAY TO SECOND` from strings in the follow
inverval-d2s = [ days SP ] hours ":" minutes [ ":" seconds [ "." milliseconds ] ]
- hours = "2" "0" - "3" / [ "0" / "1" ] DIGIT
+ hours = ( "2" "0" - "3" ) / ( [ "0" / "1" ] DIGIT )
- minutes = "5" DIGIT / [ "0" - "4" ] DIGIT
+ minutes = ( "5" DIGIT ) / ( [ "0" - "4" ] DIGIT )
- seconds = "5" DIGIT / [ "0" - "4" ] DIGIT
+ seconds = ( "5" DIGIT ) / ( [ "0" - "4" ] DIGIT )
milliseconds = 1*3DIGIT
@@ -146,4 +146,32 @@ Covers:
* `req~integer-interval-conversion~1`
+Needs: impl, utest
+
+#### Converting from 64 bit Integers to INTERVAL YEAR TO MONTH
+`dsn~exasol.converting-int-to-interval-year-to-month~1`
+
+The data converter converts integers to `INTERVAL YEAR TO MONTH`.
+
+Covers:
+
+* `req~integer-interval-conversion~1`
+
+Needs: impl, utest
+
+#### Parsing INTERVAL YEAR TO MONTH From Strings
+`dsn~exasol.parsing-interval-year-to-month-from-strings~1`
+
+The data converter can parse `INTERVAL YEAR TO MONTH` from strings in the following format:
+
+ inverval-y2m = days "-" months
+
+ days = 1*9DIGIT
+
+ months = ( "1" "0" - "2" ) / DIGIT
+
+Covers:
+
+* `req~integer-interval-conversion~1`
+
Needs: impl, utest
\ No newline at end of file
diff --git a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
index 905e55e3..bb55524b 100644
--- a/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
+++ b/src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
@@ -18,7 +18,7 @@
* milliseconds (or fraction of seconds)
*
*
- * Since milliseconds are the highest resolution, each interval can also be expresses as a total number of milliseconds.
+ * Since milliseconds are the highest resolution, each interval can also be expressed as a total number of milliseconds.
* This is also the recommended way to represent the interval values in other systems which do not natively support this
* data type.
*/
diff --git a/src/main/java/com/exasol/datatype/interval/IntervalYearToMonth.java b/src/main/java/com/exasol/datatype/interval/IntervalYearToMonth.java
new file mode 100644
index 00000000..14e1dbc3
--- /dev/null
+++ b/src/main/java/com/exasol/datatype/interval/IntervalYearToMonth.java
@@ -0,0 +1,95 @@
+package com.exasol.datatype.interval;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class implements the Exasol-proprietary data type INTERVAL YEAR(x) TO MONTH(y). It supports
+ * conversions to and from strings and from a number of months.
+ *
+ *
+ * In Exasol this data type represents a time difference consisting of the following components:
+ *
+ *
+ *
+ * Since months are the highest resolution, each interval can also be expressed as a total number of months. This is
+ * also the recommended way to represent the interval values in other systems which do not natively support this data
+ * type.
+ */
+public class IntervalYearToMonth {
+ private static final long MONTHS_PER_YEAR = 12L;
+ private static final int YEARS_MATCHING_GROUP = 1;
+ private static final int MONTHS_MATCHING_GROUP = 2;
+ private static final Pattern INTERVAL_PATTERN = Pattern.compile("(\\d{1,9})-(\\d{1,2})");
+ private final long value;
+
+ private IntervalYearToMonth(final long value) {
+ this.value = value;
+ }
+
+ private IntervalYearToMonth(final String text) {
+ final Matcher matcher = INTERVAL_PATTERN.matcher(text);
+ if (matcher.matches()) {
+ this.value = MONTHS_PER_YEAR * parseMatchingGroupToLong(matcher, YEARS_MATCHING_GROUP) //
+ + parseMatchingGroupToLong(matcher, MONTHS_MATCHING_GROUP);
+ } else {
+ throw new IllegalArgumentException(
+ "Text \"" + text + "\" cannot be parsed to an INTERVAL. Must match \"" + INTERVAL_PATTERN + "\"");
+ }
+ }
+
+ private long parseMatchingGroupToLong(final Matcher matcher, final int groupNumber) {
+ return Long.parseLong(matcher.group(groupNumber));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%d-%02d", getYears(), getMonths());
+ }
+
+ private long getYears() {
+ return this.value / MONTHS_PER_YEAR;
+ }
+
+ private long getMonths() {
+ return this.value % MONTHS_PER_YEAR;
+ }
+
+ /**
+ * Create an {@link IntervalDayToSecond} from a number of months
+ *
+ * @param value total length of the interval in months
+ * @return interval with months resolution
+ */
+ public static IntervalYearToMonth ofMonths(final long value) {
+ return new IntervalYearToMonth(value);
+ }
+
+ /**
+ * Parse an {@link IntervalDayToSecond} from a string
+ *
+ *
+ * The accepted format is:
+ *
+ *
+ * YYYYYYYYY:MM
+ *
+ * Where
+ *
+ *
+ * - Y
+ * - years, 1-9 digits, mandatory
+ * - M
+ * - months, 1-2 digits, mandatory
+ *
+ *
+ * @param text string representing an interval
+ * @return interval with months resolution
+ */
+ public static IntervalYearToMonth parse(final String text) {
+ return new IntervalYearToMonth(text);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/exasol/datatype/interval/TestInterval.java b/src/test/java/com/exasol/datatype/interval/TestIntervalDayToSecond.java
similarity index 94%
rename from src/test/java/com/exasol/datatype/interval/TestInterval.java
rename to src/test/java/com/exasol/datatype/interval/TestIntervalDayToSecond.java
index 5b4f04ea..285b5461 100644
--- a/src/test/java/com/exasol/datatype/interval/TestInterval.java
+++ b/src/test/java/com/exasol/datatype/interval/TestIntervalDayToSecond.java
@@ -8,7 +8,7 @@
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
-class TestInterval {
+class TestIntervalDayToSecond {
// [utest->dsn~exasol.converting-int-to-interval-day-to-second~1]
@ParameterizedTest
@CsvSource({ //
@@ -20,7 +20,7 @@ class TestInterval {
999999999L * 24 * 60 * 60 * 1000 + ", '999999999 0:00:00.000'", //
1L * 24 * 60 * 60 * 1000 + 1 * 60 * 60 * 1000 + 1 * 60 * 1000 + 1 * 1000 + 1 + ", '1 1:01:01.001'" //
})
- void testMillisecondsToIntervalDayToSecond(final long value, final String expected) {
+ void testofMillis(final long value, final String expected) {
assertThat(IntervalDayToSecond.ofMillis(value).toString(), equalTo(expected));
}
diff --git a/src/test/java/com/exasol/datatype/interval/TestIntervalYearToMonth.java b/src/test/java/com/exasol/datatype/interval/TestIntervalYearToMonth.java
new file mode 100644
index 00000000..03cff591
--- /dev/null
+++ b/src/test/java/com/exasol/datatype/interval/TestIntervalYearToMonth.java
@@ -0,0 +1,42 @@
+package com.exasol.datatype.interval;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class TestIntervalYearToMonth {
+ // [utest->dsn~exasol.converting-int-to-interval-year-to-month~1]
+ @ParameterizedTest
+ @CsvSource({ //
+ 0L + ", '0-00'", //
+ 11L + ", '0-11'", //
+ 999999999L * 12 + ", '999999999-00'", //
+ 999999999L * 12 + 11 + ", '999999999-11'", //
+ 1L * 12 + 1 + ", '1-01'" //
+ })
+ void testOfMonths(final long value, final String expected) {
+ assertThat(IntervalYearToMonth.ofMonths(value).toString(), equalTo(expected));
+ }
+
+ // [utest->dsn~exasol.parsing-interval-year-to-month-from-strings~1]
+ @ParameterizedTest
+ @CsvSource({ "'0-0', '0-00'", //
+ "'1-2', '1-02'", //
+ "'22-11', '22-11'", //
+ "'999999999-11', '999999999-11'" //
+ })
+ void testParse(final String text, final String expected) {
+ assertThat(IntervalYearToMonth.parse(text).toString(), equalTo(expected));
+ }
+
+ // [utest->dsn~exasol.parsing-interval-day-to-second-from-strings~1]
+ @ParameterizedTest
+ @ValueSource(strings = { "0", "-0", "0-", "0-123", "1000000000-0" })
+ void testParseIllegalInputThrowsException(final String text) {
+ assertThrows(IllegalArgumentException.class, () -> IntervalYearToMonth.parse(text));
+ }
+}
\ No newline at end of file
From 804a4cf5555eb7778919a6f2807fe3ba22d933db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20B=C3=A4r?=
Date: Tue, 16 Oct 2018 14:56:45 +0200
Subject: [PATCH 7/8] PMI-97: Fixed requirement trace.
---
.../java/com/exasol/datatype/interval/IntervalYearToMonth.java | 2 ++
.../com/exasol/datatype/interval/TestIntervalYearToMonth.java | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/main/java/com/exasol/datatype/interval/IntervalYearToMonth.java b/src/main/java/com/exasol/datatype/interval/IntervalYearToMonth.java
index 14e1dbc3..613a615a 100644
--- a/src/main/java/com/exasol/datatype/interval/IntervalYearToMonth.java
+++ b/src/main/java/com/exasol/datatype/interval/IntervalYearToMonth.java
@@ -64,6 +64,7 @@ private long getMonths() {
* @param value total length of the interval in months
* @return interval with months resolution
*/
+ // [impl->dsn~exasol.converting-int-to-interval-year-to-month~1]
public static IntervalYearToMonth ofMonths(final long value) {
return new IntervalYearToMonth(value);
}
@@ -89,6 +90,7 @@ public static IntervalYearToMonth ofMonths(final long value) {
* @param text string representing an interval
* @return interval with months resolution
*/
+ // [impl->dsn~exasol.parsing-interval-year-to-month-from-strings~1]
public static IntervalYearToMonth parse(final String text) {
return new IntervalYearToMonth(text);
}
diff --git a/src/test/java/com/exasol/datatype/interval/TestIntervalYearToMonth.java b/src/test/java/com/exasol/datatype/interval/TestIntervalYearToMonth.java
index 03cff591..8f25d81e 100644
--- a/src/test/java/com/exasol/datatype/interval/TestIntervalYearToMonth.java
+++ b/src/test/java/com/exasol/datatype/interval/TestIntervalYearToMonth.java
@@ -33,7 +33,7 @@ void testParse(final String text, final String expected) {
assertThat(IntervalYearToMonth.parse(text).toString(), equalTo(expected));
}
- // [utest->dsn~exasol.parsing-interval-day-to-second-from-strings~1]
+ // [utest->dsn~exasol.parsing-interval-year-to-month-from-strings~1]
@ParameterizedTest
@ValueSource(strings = { "0", "-0", "0-", "0-123", "1000000000-0" })
void testParseIllegalInputThrowsException(final String text) {
From 091fa79a75bee57795e2a27b2ac90ef95345d343 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20B=C3=A4r?=
Date: Tue, 16 Oct 2018 15:04:00 +0200
Subject: [PATCH 8/8] PMI-97: Worked in review findings of @bobkodex.
---
doc/design.md | 8 ++++++--
doc/system_requirements.md | 2 +-
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/doc/design.md b/doc/design.md
index 4e4368f3..1b8837ef 100644
--- a/doc/design.md
+++ b/doc/design.md
@@ -132,7 +132,7 @@ Needs: impl, utest
The data converter can parse `INTERVAL DAY TO SECOND` from strings in the following format:
- inverval-d2s = [ days SP ] hours ":" minutes [ ":" seconds [ "." milliseconds ] ]
+ interval-d2s = [ days SP ] hours ":" minutes [ ":" seconds [ "." milliseconds ] ]
hours = ( "2" "0" - "3" ) / ( [ "0" / "1" ] DIGIT )
@@ -142,6 +142,8 @@ The data converter can parse `INTERVAL DAY TO SECOND` from strings in the follow
milliseconds = 1*3DIGIT
+Examples are `12:30`, `12:30.081` or `100 12:30:00.081`.
+
Covers:
* `req~integer-interval-conversion~1`
@@ -164,12 +166,14 @@ Needs: impl, utest
The data converter can parse `INTERVAL YEAR TO MONTH` from strings in the following format:
- inverval-y2m = days "-" months
+ interval-y2m = days "-" months
days = 1*9DIGIT
months = ( "1" "0" - "2" ) / DIGIT
+Examples are `0-1` and `100-11`.
+
Covers:
* `req~integer-interval-conversion~1`
diff --git a/doc/system_requirements.md b/doc/system_requirements.md
index 824be8d3..f6287272 100644
--- a/doc/system_requirements.md
+++ b/doc/system_requirements.md
@@ -263,7 +263,7 @@ ESB converts values of type `INTERVAL` to integer and vice-versa.
Rationale:
-Neighboring systems of an Exasol database often do to have equivalent data types, so conversion to a primitive data type is required.
+Neighboring systems of an Exasol database often do not have equivalent data types, so conversion to a primitive data type is required.
Covers: