Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 32 additions & 13 deletions .classpath
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/test/java" output="target/test-classes" including="**/*.java"/>
<classpathentry kind="src" path="src/main/java" including="**/*.java"/>
<classpathentry kind="output" path="target/classes"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="var" path="M2_REPO/org/junit/jupiter/junit-jupiter-api/5.3.1/junit-jupiter-api-5.3.1.jar" sourcepath="M2_REPO/org/junit/jupiter/junit-jupiter-api/5.3.1/junit-jupiter-api-5.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/apiguardian/apiguardian-api/1.0.0/apiguardian-api-1.0.0.jar" sourcepath="M2_REPO/org/apiguardian/apiguardian-api/1.0.0/apiguardian-api-1.0.0-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/opentest4j/opentest4j/1.1.1/opentest4j-1.1.1.jar" sourcepath="M2_REPO/org/opentest4j/opentest4j/1.1.1/opentest4j-1.1.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/junit/platform/junit-platform-commons/1.3.1/junit-platform-commons-1.3.1.jar" sourcepath="M2_REPO/org/junit/platform/junit-platform-commons/1.3.1/junit-platform-commons-1.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/junit/jupiter/junit-jupiter-engine/5.3.1/junit-jupiter-engine-5.3.1.jar" sourcepath="M2_REPO/org/junit/jupiter/junit-jupiter-engine/5.3.1/junit-jupiter-engine-5.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/junit/platform/junit-platform-engine/1.3.1/junit-platform-engine-1.3.1.jar" sourcepath="M2_REPO/org/junit/platform/junit-platform-engine/1.3.1/junit-platform-engine-1.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/junit/platform/junit-platform-launcher/1.3.1/junit-platform-launcher-1.3.1.jar" sourcepath="M2_REPO/org/junit/platform/junit-platform-launcher/1.3.1/junit-platform-launcher-1.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/hamcrest/hamcrest-all/1.3/hamcrest-all-1.3.jar" sourcepath="M2_REPO/org/hamcrest/hamcrest-all/1.3/hamcrest-all-1.3-sources.jar"/>
</classpath>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="var" path="M2_REPO/org/junit/jupiter/junit-jupiter-api/5.3.1/junit-jupiter-api-5.3.1.jar" sourcepath="M2_REPO/org/junit/jupiter/junit-jupiter-api/5.3.1/junit-jupiter-api-5.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/apiguardian/apiguardian-api/1.0.0/apiguardian-api-1.0.0.jar" sourcepath="M2_REPO/org/apiguardian/apiguardian-api/1.0.0/apiguardian-api-1.0.0-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/opentest4j/opentest4j/1.1.1/opentest4j-1.1.1.jar" sourcepath="M2_REPO/org/opentest4j/opentest4j/1.1.1/opentest4j-1.1.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/junit/platform/junit-platform-commons/1.3.1/junit-platform-commons-1.3.1.jar" sourcepath="M2_REPO/org/junit/platform/junit-platform-commons/1.3.1/junit-platform-commons-1.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/junit/jupiter/junit-jupiter-engine/5.3.1/junit-jupiter-engine-5.3.1.jar" sourcepath="M2_REPO/org/junit/jupiter/junit-jupiter-engine/5.3.1/junit-jupiter-engine-5.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/junit/platform/junit-platform-engine/1.3.1/junit-platform-engine-1.3.1.jar" sourcepath="M2_REPO/org/junit/platform/junit-platform-engine/1.3.1/junit-platform-engine-1.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/junit/platform/junit-platform-launcher/1.3.1/junit-platform-launcher-1.3.1.jar" sourcepath="M2_REPO/org/junit/platform/junit-platform-launcher/1.3.1/junit-platform-launcher-1.3.1-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/hamcrest/hamcrest-all/1.3/hamcrest-all-1.3.jar" sourcepath="M2_REPO/org/hamcrest/hamcrest-all/1.3/hamcrest-all-1.3-sources.jar"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
68 changes: 67 additions & 1 deletion doc/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -112,4 +112,70 @@ 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:

interval-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

Examples are `12:30`, `12:30.081` or `100 12:30:00.081`.

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:

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`

Needs: impl, utest
39 changes: 23 additions & 16 deletions doc/system_requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -221,10 +232,6 @@ Covers:

Needs: dsn

#### TODO

* One line / pretty

#### SELECT Statement Rendering
`req~rendering.sql.select~1`

Expand All @@ -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 not 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
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
<version>${junit.platform.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
Expand Down
141 changes: 141 additions & 0 deletions src/main/java/com/exasol/datatype/interval/IntervalDayToSecond.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.exasol.datatype.interval;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* This class implements the Exasol-proprietary data type <code>INTERVAL DAY(x) TO SECONDS(y)</code>. It supports
* conversions to and from strings and from milliseconds.
*
* <p>
* In Exasol this data type represents a time difference consisting of the following components:
* </p>
* <ul>
* <li>days</li>
* <li>hours</li>
* <li>minutes</li>
* <li>seconds</li>
* <li>milliseconds (or fraction of seconds)</li>
* </ul>
*
* 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.
*/
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 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 final long value;

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 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);
}

/**
* Parse an {@link IntervalDayToSecond} from a string
*
* <p>
* The accepted format is:
* </p>
* <p>
* <code>[dddddddd ]<strong>hh:mm</strong>[:ss[.SSS]]</code>
* <p>
* Where
* </p>
* <dl>
* <dt>d</dt>
* <dd>day, 1-9 digits, optional</dd>
* <dt>h</dt>
* <dd>hours, 1-2 digits, mandatory</dd>
* <dt>m</dt>
* <dd>minutes, 1-2 digits, mandatory</dd>
* <dt>s</dt>
* <dd>seconds, 1-2 digits, optional</dd>
* <dt>S</dt>
* <dd>milliseconds, 1-3 digits, optional</dd>
* </dl>
*
* @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);
}
}
Loading