提交 e403cc08 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Initial partial implementation of INTERVAL data type

上级 217abf45
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.api;
import org.h2.util.DateTimeUtils;
/**
* {@code INTERVAL} representation for result sets.
*/
public final class Interval {
private final IntervalQualifier qualifier;
private final long leading;
private final long remaining;
/**
* @param qualifier
* qualifier
* @param leading
* value of leading field
* @param remaining
* values of all remaining fields
*/
public Interval(IntervalQualifier qualifier, long leading, long remaining) {
if (qualifier == null) {
throw new NullPointerException();
}
this.qualifier = qualifier;
this.leading = leading;
this.remaining = remaining;
}
/**
* Return qualifier of this interval.
*
* @return qualifier
*/
public IntervalQualifier getQualifier() {
return qualifier;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + qualifier.hashCode();
result = prime * result + (int) (leading ^ (leading >>> 32));
result = prime * result + (int) (remaining ^ (remaining >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Interval)) {
return false;
}
Interval other = (Interval) obj;
return qualifier == other.qualifier && leading == other.leading || remaining == other.remaining;
}
@Override
public String toString() {
return DateTimeUtils.intervalToString(qualifier, leading, remaining);
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.api;
/**
* Interval qualifier.
*/
public enum IntervalQualifier {
/**
* {@code YEAR}
*/
YEAR,
/**
* {@code MONTH}
*/
MONTH,
/**
* {@code DAY}
*/
DAY,
/**
* {@code HOUR}
*/
HOUR,
/**
* {@code MINUTE}
*/
MINUTE,
/**
* {@code SECOND}
*/
SECOND,
/**
* {@code YEAR TO MONTH}
*/
YEAR_TO_MONTH,
/**
* {@code DAY TO HOUR}
*/
DAY_TO_HOUR,
/**
* {@code DAY TO MINUTE}
*/
DAY_TO_MINUTE,
/**
* {@code DAY TO SECOND}
*/
DAY_TO_SECOND,
/**
* {@code HOUR TO MINUTE}
*/
HOUR_TO_MINUTE,
/**
* {@code HOUR TO SECOND}
*/
HOUR_TO_SECOND,
/**
* {@code MINUTE TO SECOND}
*/
MINUTE_TO_SECOND;
private final String string;
/**
* Returns the interval qualifier with the specified ordinal value.
*
* @param ordinal
* Java ordinal value (0-based)
* @return interval qualifier with the specified ordinal value
*/
public static IntervalQualifier valueOf(int ordinal) {
switch (ordinal) {
case 0:
return YEAR;
case 1:
return MONTH;
case 2:
return DAY;
case 3:
return HOUR;
case 4:
return MINUTE;
case 5:
return SECOND;
case 6:
return YEAR_TO_MONTH;
case 7:
return DAY_TO_HOUR;
case 8:
return DAY_TO_MINUTE;
case 9:
return DAY_TO_SECOND;
case 10:
return HOUR_TO_MINUTE;
case 11:
return HOUR_TO_SECOND;
case 12:
return MINUTE_TO_SECOND;
default:
throw new IllegalArgumentException();
}
}
private IntervalQualifier() {
string = name().replace('_', ' ').intern();
}
/**
* Returns whether interval with this qualifier is a year-month interval.
*
* @return whether interval with this qualifier is a year-month interval
*/
public boolean isYearMonth() {
return this == YEAR || this == MONTH || this == YEAR_TO_MONTH;
}
/**
* Returns whether interval with this qualifier is a day-time interval.
*
* @return whether interval with this qualifier is a day-time interval
*/
public boolean isDayTime() {
return !isYearMonth();
}
/**
* Returns whether interval with this qualifier has years.
*
* @return whether interval with this qualifier has years
*/
public boolean hasYears() {
return this == YEAR || this == YEAR_TO_MONTH;
}
/**
* Returns whether interval with this qualifier has months.
*
* @return whether interval with this qualifier has months
*/
public boolean hasMonths() {
return this == MONTH || this == YEAR_TO_MONTH;
}
/**
* Returns whether interval with this qualifier has days.
*
* @return whether interval with this qualifier has days
*/
public boolean hasDays() {
switch (this) {
case DAY:
case DAY_TO_HOUR:
case DAY_TO_MINUTE:
case DAY_TO_SECOND:
return true;
default:
return false;
}
}
/**
* Returns whether interval with this qualifier has hours.
*
* @return whether interval with this qualifier has hours
*/
public boolean hasHours() {
switch (this) {
case HOUR:
case DAY_TO_HOUR:
case DAY_TO_MINUTE:
case DAY_TO_SECOND:
case HOUR_TO_MINUTE:
case HOUR_TO_SECOND:
return true;
default:
return false;
}
}
/**
* Returns whether interval with this qualifier has minutes.
*
* @return whether interval with this qualifier has minutes
*/
public boolean hasMinutes() {
switch (this) {
case MINUTE:
case DAY_TO_MINUTE:
case DAY_TO_SECOND:
case HOUR_TO_MINUTE:
case HOUR_TO_SECOND:
case MINUTE_TO_SECOND:
return true;
default:
return false;
}
}
/**
* Returns whether interval with this qualifier has seconds.
*
* @return whether interval with this qualifier has seconds
*/
public boolean hasSeconds() {
switch (this) {
case SECOND:
case DAY_TO_SECOND:
case HOUR_TO_SECOND:
case MINUTE_TO_SECOND:
return true;
default:
return false;
}
}
/**
* Returns whether interval with this qualifier has multiple fields.
*
* @return whether interval with this qualifier has multiple fields
*/
public boolean hasMultipleFields() {
return ordinal() > 5;
}
@Override
public String toString() {
return string;
}
/**
* Returns full type name.
*
* @param precision precision, or {@code -1}
* @param scale fractional seconds precision, or {@code -1}
* @return full type name
*/
public String getTypeName(int precision, int scale) {
StringBuilder b = new StringBuilder("INTERVAL ");
switch (this) {
case YEAR:
case MONTH:
case DAY:
case HOUR:
case MINUTE:
b.append(string);
if (precision > 0) {
b.append('(').append(precision).append(')');
}
break;
case SECOND:
b.append(string);
if (precision > 0 || scale >= 0) {
b.append('(').append(precision > 0 ? precision : 2);
if (scale >= 0) {
b.append(", ").append(scale);
}
b.append(')');
}
break;
case YEAR_TO_MONTH:
b.append("YEAR");
if (precision > 0) {
b.append('(').append(precision).append(')');
}
b.append(" TO MONTH");
break;
case DAY_TO_HOUR:
b.append("DAY");
if (precision > 0) {
b.append('(').append(precision).append(')');
}
b.append(" TO HOUR");
break;
case DAY_TO_MINUTE:
b.append("DAY");
if (precision > 0) {
b.append('(').append(precision).append(')');
}
b.append(" TO MINUTE");
break;
case DAY_TO_SECOND:
b.append("DAY");
if (precision > 0) {
b.append('(').append(precision).append(')');
}
b.append(" TO SECOND");
if (scale >= 0) {
b.append('(').append(scale).append(')');
}
break;
case HOUR_TO_MINUTE:
b.append("HOUR");
if (precision > 0) {
b.append('(').append(precision).append(')');
}
b.append(" TO MINUTE");
break;
case HOUR_TO_SECOND:
b.append("HOUR");
if (precision > 0) {
b.append('(').append(precision).append(')');
}
b.append(" TO SECOND");
if (scale >= 0) {
b.append('(').append(scale).append(')');
}
break;
case MINUTE_TO_SECOND:
b.append("MINUTE");
if (precision > 0) {
b.append('(').append(precision).append(')');
}
b.append(" TO SECOND");
if (scale >= 0) {
b.append('(').append(scale).append(')');
}
}
return b.toString();
}
}
...@@ -60,6 +60,7 @@ import java.util.LinkedHashSet; ...@@ -60,6 +60,7 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.IntervalQualifier;
import org.h2.api.Trigger; import org.h2.api.Trigger;
import org.h2.command.ddl.AlterIndexRename; import org.h2.command.ddl.AlterIndexRename;
import org.h2.command.ddl.AlterSchemaRename; import org.h2.command.ddl.AlterSchemaRename;
...@@ -201,6 +202,7 @@ import org.h2.value.ValueDate; ...@@ -201,6 +202,7 @@ import org.h2.value.ValueDate;
import org.h2.value.ValueDecimal; import org.h2.value.ValueDecimal;
import org.h2.value.ValueEnum; import org.h2.value.ValueEnum;
import org.h2.value.ValueInt; import org.h2.value.ValueInt;
import org.h2.value.ValueInterval;
import org.h2.value.ValueLong; import org.h2.value.ValueLong;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
import org.h2.value.ValueString; import org.h2.value.ValueString;
...@@ -4685,7 +4687,7 @@ public class Parser { ...@@ -4685,7 +4687,7 @@ public class Parser {
private Column parseColumnWithType(String columnName, boolean forTable) { private Column parseColumnWithType(String columnName, boolean forTable) {
String original = currentToken; String original = currentToken;
boolean regular = false; boolean regular = false;
int originalScale = -1; int originalPrecision = -1, originalScale = -1;
if (readIf("LONG")) { if (readIf("LONG")) {
if (readIf("RAW")) { if (readIf("RAW")) {
original = "LONG RAW"; original = "LONG RAW";
...@@ -4742,6 +4744,90 @@ public class Parser { ...@@ -4742,6 +4744,90 @@ public class Parser {
read("ZONE"); read("ZONE");
original = "TIMESTAMP WITHOUT TIME ZONE"; original = "TIMESTAMP WITHOUT TIME ZONE";
} }
} else if (readIf("INTERVAL")) {
if (readIf("YEAR")) {
if (readIf(OPEN_PAREN)) {
originalPrecision = readNonNegativeInt();
read(CLOSE_PAREN);
}
if (readIf("TO")) {
read("MONTH");
original = "INTERVAL YEAR TO MONTH";
} else {
original = "INTERVAL YEAR";
}
} else if (readIf("MONTH")) {
if (readIf(OPEN_PAREN)) {
originalPrecision = readNonNegativeInt();
read(CLOSE_PAREN);
}
original = "INTERVAL MONTH";
} else if (readIf("DAY")) {
if (readIf(OPEN_PAREN)) {
originalPrecision = readNonNegativeInt();
read(CLOSE_PAREN);
}
if (readIf("TO")) {
if (readIf("HOUR")) {
original = "INTERVAL DAY TO HOUR";
} else if (readIf("MINUTE")) {
original = "INTERVAL DAY TO MINUTE";
} else {
read("SECOND");
if (readIf(OPEN_PAREN)) {
originalScale = readNonNegativeInt();
read(CLOSE_PAREN);
}
original = "INTERVAL DAY TO SECOND";
}
} else {
original = "INTERVAL DAY";
}
} else if (readIf("HOUR")) {
if (readIf(OPEN_PAREN)) {
originalPrecision = readNonNegativeInt();
read(CLOSE_PAREN);
}
if (readIf("TO")) {
if (readIf("MINUTE")) {
original = "INTERVAL HOUR TO MINUTE";
} else {
read("SECOND");
if (readIf(OPEN_PAREN)) {
originalScale = readNonNegativeInt();
read(CLOSE_PAREN);
}
original = "INTERVAL HOUR TO SECOND";
}
} else {
original = "INTERVAL HOUR";
}
} else if (readIf("MINUTE")) {
if (readIf(OPEN_PAREN)) {
originalPrecision = readNonNegativeInt();
read(CLOSE_PAREN);
}
if (readIf("TO")) {
read("SECOND");
if (readIf(OPEN_PAREN)) {
originalScale = readNonNegativeInt();
read(CLOSE_PAREN);
}
original = "INTERVAL MINUTE TO SECOND";
} else {
original = "INTERVAL MINUTE";
}
} else {
read("SECOND");
if (readIf(OPEN_PAREN)) {
originalPrecision = readNonNegativeInt();
if (readIf(COMMA)) {
originalScale = readNonNegativeInt();
}
read(CLOSE_PAREN);
}
original = "INTERVAL SECOND";
}
} else { } else {
regular = true; regular = true;
} }
...@@ -4828,6 +4914,24 @@ public class Parser { ...@@ -4828,6 +4914,24 @@ public class Parser {
scale = 0; scale = 0;
precision = displaySize = ValueTimestamp.getDisplaySize(0); precision = displaySize = ValueTimestamp.getDisplaySize(0);
} }
} else if (DataType.isIntervalType(t)) {
if (originalPrecision >= 0 || originalScale >= 0) {
IntervalQualifier qualifier = IntervalQualifier.valueOf(t - Value.INTERVAL_YEAR);
original = qualifier.getTypeName(originalPrecision, originalScale);
if (originalPrecision >= 0) {
if (originalPrecision <= 0 || originalPrecision > ValueInterval.MAXIMUM_PRECISION) {
throw DbException.get(ErrorCode.INVALID_VALUE_SCALE_PRECISION,
Integer.toString(originalPrecision));
}
precision = originalPrecision;
}
if (originalScale >= 0) {
if (originalScale > ValueInterval.MAXIMUM_SCALE) {
throw DbException.get(ErrorCode.INVALID_VALUE_SCALE_PRECISION,
Integer.toString(originalScale));
}
}
}
} else if (readIf(OPEN_PAREN)) { } else if (readIf(OPEN_PAREN)) {
if (!readIf("MAX")) { if (!readIf("MAX")) {
long p = readLong(); long p = readLong();
...@@ -4909,7 +5013,7 @@ public class Parser { ...@@ -4909,7 +5013,7 @@ public class Parser {
// MySQL compatibility // MySQL compatibility
readIf("UNSIGNED"); readIf("UNSIGNED");
int type = dataType.type; int type = dataType.type;
if (scale > precision) { if (scale > precision && !DataType.isIntervalType(type)) {
throw DbException.get(ErrorCode.INVALID_VALUE_SCALE_PRECISION, throw DbException.get(ErrorCode.INVALID_VALUE_SCALE_PRECISION,
Integer.toString(scale), Long.toString(precision)); Integer.toString(scale), Long.toString(precision));
} }
......
...@@ -175,6 +175,8 @@ public class MetaTable extends Table { ...@@ -175,6 +175,8 @@ public class MetaTable extends Table {
"NUMERIC_PRECISION_RADIX INT", "NUMERIC_PRECISION_RADIX INT",
"NUMERIC_SCALE INT", "NUMERIC_SCALE INT",
"DATETIME_PRECISION INT", "DATETIME_PRECISION INT",
"INTERVAL_TYPE",
"INTERVAL_PRECISION INT",
"CHARACTER_SET_NAME", "CHARACTER_SET_NAME",
"COLLATION_NAME", "COLLATION_NAME",
// extensions // extensions
...@@ -845,6 +847,24 @@ public class MetaTable extends Table { ...@@ -845,6 +847,24 @@ public class MetaTable extends Table {
ValueInt precision = ValueInt.get(c.getPrecisionAsInt()); ValueInt precision = ValueInt.get(c.getPrecisionAsInt());
ValueInt scale = ValueInt.get(c.getScale()); ValueInt scale = ValueInt.get(c.getScale());
Sequence sequence = c.getSequence(); Sequence sequence = c.getSequence();
boolean hasDateTimePrecision;
int type = dataType.type;
switch (type) {
case Value.TIME:
case Value.DATE:
case Value.TIMESTAMP:
case Value.TIMESTAMP_TZ:
case Value.INTERVAL_SECOND:
case Value.INTERVAL_DAY_TO_SECOND:
case Value.INTERVAL_HOUR_TO_SECOND:
case Value.INTERVAL_MINUTE_TO_SECOND:
hasDateTimePrecision = true;
break;
default:
hasDateTimePrecision = false;
}
boolean isInterval = DataType.isIntervalType(type);
String createSQLWithoutName = c.getCreateSQLWithoutName();
add(rows, add(rows,
// TABLE_CATALOG // TABLE_CATALOG
catalog, catalog,
...@@ -873,13 +893,17 @@ public class MetaTable extends Table { ...@@ -873,13 +893,17 @@ public class MetaTable extends Table {
// NUMERIC_SCALE // NUMERIC_SCALE
scale, scale,
// DATETIME_PRECISION // DATETIME_PRECISION
DataType.isDateTimeType(dataType.type) ? scale : null, hasDateTimePrecision ? scale : null,
// INTERVAL_TYPE
isInterval ? createSQLWithoutName.substring(9) : null,
// INTERVAL_PRECISION
isInterval ? precision : null,
// CHARACTER_SET_NAME // CHARACTER_SET_NAME
CHARACTER_SET_NAME, CHARACTER_SET_NAME,
// COLLATION_NAME // COLLATION_NAME
collation, collation,
// TYPE_NAME // TYPE_NAME
identifier(dataType.name), identifier(isInterval ? "INTERVAL" : dataType.name),
// NULLABLE // NULLABLE
ValueInt.get(c.isNullable() ValueInt.get(c.isNullable()
? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls), ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls),
...@@ -897,7 +921,7 @@ public class MetaTable extends Table { ...@@ -897,7 +921,7 @@ public class MetaTable extends Table {
// SMALLINT // SMALLINT
null, null,
// COLUMN_TYPE // COLUMN_TYPE
c.getCreateSQLWithoutName(), createSQLWithoutName,
// COLUMN_ON_UPDATE // COLUMN_ON_UPDATE
c.getOnUpdateSQL() c.getOnUpdateSQL()
); );
......
...@@ -12,9 +12,11 @@ import java.sql.Timestamp; ...@@ -12,9 +12,11 @@ import java.sql.Timestamp;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.TimeZone; import java.util.TimeZone;
import org.h2.api.IntervalQualifier;
import org.h2.engine.Mode; import org.h2.engine.Mode;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueDate; import org.h2.value.ValueDate;
import org.h2.value.ValueInterval;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
import org.h2.value.ValueTime; import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp; import org.h2.value.ValueTimestamp;
...@@ -392,8 +394,7 @@ public class DateTimeUtils { ...@@ -392,8 +394,7 @@ public class DateTimeUtils {
second = Integer.parseInt(s.substring(s2 + 1, end)); second = Integer.parseInt(s.substring(s2 + 1, end));
} else { } else {
second = Integer.parseInt(s.substring(s2 + 1, s3)); second = Integer.parseInt(s.substring(s2 + 1, s3));
String n = (s.substring(s3 + 1, end) + "000000000").substring(0, 9); nanos = parseNanos(s, s3 + 1, end);
nanos = Integer.parseInt(n);
} }
if (hour >= 2_000_000 || minute < 0 || minute >= 60 || second < 0 if (hour >= 2_000_000 || minute < 0 || minute >= 60 || second < 0
|| second >= 60) { || second >= 60) {
...@@ -406,6 +407,10 @@ public class DateTimeUtils { ...@@ -406,6 +407,10 @@ public class DateTimeUtils {
return negative ? -nanos : nanos; return negative ? -nanos : nanos;
} }
private static int parseNanos(String s, int start, int end) {
return Integer.parseInt((s.substring(start, end) + "000000000").substring(0, 9));
}
/** /**
* See: * See:
* https://stackoverflow.com/questions/3976616/how-to-find-nth-occurrence-of-character-in-a-string#answer-3976656 * https://stackoverflow.com/questions/3976616/how-to-find-nth-occurrence-of-character-in-a-string#answer-3976656
...@@ -1399,17 +1404,21 @@ public class DateTimeUtils { ...@@ -1399,17 +1404,21 @@ public class DateTimeUtils {
StringUtils.appendZeroPadded(buff, 2, s); StringUtils.appendZeroPadded(buff, 2, s);
if (ms > 0 || nanos > 0) { if (ms > 0 || nanos > 0) {
buff.append('.'); buff.append('.');
int start = buff.length();
StringUtils.appendZeroPadded(buff, 3, ms); StringUtils.appendZeroPadded(buff, 3, ms);
if (nanos > 0) { if (nanos > 0) {
StringUtils.appendZeroPadded(buff, 6, nanos); StringUtils.appendZeroPadded(buff, 6, nanos);
} }
for (int i = buff.length() - 1; i > start; i--) { stripTrailingZeroes(buff);
if (buff.charAt(i) != '0') {
break;
} }
buff.deleteCharAt(i);
} }
private static void stripTrailingZeroes(StringBuilder buff) {
int i = buff.length() - 1;
if (buff.charAt(i) == '0') {
while (buff.charAt(--i) == '0') {
// do nothing
}
buff.setLength(i + 1);
} }
} }
...@@ -1478,6 +1487,243 @@ public class DateTimeUtils { ...@@ -1478,6 +1487,243 @@ public class DateTimeUtils {
return b.toString(); return b.toString();
} }
/**
* Parses the specified string as {@code INTERVAL} value.
*
* @param qualifier the qualifier of interval
* @param negative whether the interval is negative
* @param s the string to parse
* @return the interval value
*/
public static ValueInterval parseInterval(IntervalQualifier qualifier, boolean negative, String s) {
long leading, remaining;
switch (qualifier) {
case YEAR:
case MONTH:
case DAY:
case HOUR:
case MINUTE:
leading = parseIntervalLeading(s, 0, s.length());
remaining = 0;
break;
case SECOND: {
int dot = s.indexOf('.');
if (dot < 0) {
leading = parseIntervalLeading(s, 0, s.length());
remaining = 0;
} else {
leading = parseIntervalLeading(s, 0, dot);
remaining = parseNanos(s, dot + 1, s.length());
}
break;
}
case YEAR_TO_MONTH:
return parseInterval2(qualifier, s, '-', 11);
case DAY_TO_HOUR:
return parseInterval2(qualifier, s, ' ', 23);
case DAY_TO_MINUTE: {
int space = s.indexOf(' ');
if (space < 0) {
leading = parseIntervalLeading(s, 0, s.length());
remaining = 0;
} else {
leading = parseIntervalLeading(s, 0, space);
int colon = s.indexOf(':', space + 1);
if (colon < 0) {
remaining = parseIntervalRemaining(s, space + 1, s.length(), 23) * 60;
} else {
remaining = parseIntervalRemaining(s, space + 1, colon, 23) * 60
+ parseIntervalRemaining(s, colon + 1, s.length(), 59);
}
}
break;
}
case DAY_TO_SECOND: {
int space = s.indexOf(' ');
if (space < 0) {
leading = parseIntervalLeading(s, 0, s.length());
remaining = 0;
} else {
leading = parseIntervalLeading(s, 0, space);
int colon = s.indexOf(':', space + 1);
if (colon < 0) {
remaining = parseIntervalRemaining(s, space + 1, s.length(), 23) * 3_600_000_000_000L;
} else {
int colon2 = s.indexOf(':', colon + 1);
if (colon2 < 0) {
remaining = parseIntervalRemaining(s, space + 1, colon, 23) * 3_600_000_000_000L
+ parseIntervalRemaining(s, colon + 1, s.length(), 59) * 60_000_000_000L;
} else {
remaining = parseIntervalRemaining(s, space + 1, colon, 23) * 3_600_000_000_000L
+ parseIntervalRemaining(s, colon + 1, colon2, 59) * 60_000_000_000L
+ parseIntervalRemainingSeconds(s, colon2 + 1);
}
}
}
break;
}
case HOUR_TO_MINUTE:
return parseInterval2(qualifier, s, ':', 59);
case HOUR_TO_SECOND: {
int colon = s.indexOf(':');
if (colon < 0) {
leading = parseIntervalLeading(s, 0, s.length());
remaining = 0;
} else {
leading = parseIntervalLeading(s, 0, colon);
int colon2 = s.indexOf(':', colon + 1);
if (colon2 < 0) {
remaining = parseIntervalRemaining(s, colon + 1, s.length(), 59) * 60_000_000_000L;
} else {
remaining = parseIntervalRemaining(s, colon + 1, colon2, 59) * 60_000_000_000L
+ parseIntervalRemainingSeconds(s, colon2 + 1);
}
}
break;
}
case MINUTE_TO_SECOND: {
int dash = s.indexOf(':');
if (dash < 0) {
leading = parseIntervalLeading(s, 0, s.length());
remaining = 0;
} else {
leading = parseIntervalLeading(s, 0, dash);
remaining = parseIntervalRemainingSeconds(s, dash + 1);
}
return ValueInterval.from(qualifier, leading, remaining);
}
default:
throw new IllegalArgumentException();
}
return ValueInterval.from(qualifier, leading, remaining);
}
static ValueInterval parseInterval2(IntervalQualifier qualifier, String s, char ch, int max) {
long leading;
long remaining;
int dash = s.indexOf(ch);
if (dash < 0) {
leading = parseIntervalLeading(s, 0, s.length());
remaining = 0;
} else {
leading = parseIntervalLeading(s, 0, dash);
remaining = parseIntervalRemaining(s, dash + 1, s.length(), max);
}
return ValueInterval.from(qualifier, leading, remaining);
}
private static long parseIntervalLeading(String s, int start, int end) {
return Long.parseLong(s.substring(start, end));
}
private static long parseIntervalRemaining(String s, int start, int end, int max) {
long v = Integer.parseInt(s.substring(start, end));
if (v < 0 || v > max) {
throw new IllegalArgumentException(s);
}
return v;
}
private static long parseIntervalRemainingSeconds(String s, int start) {
int seconds, nanos;
int dot = s.indexOf('.', start + 1);
if (dot < 0) {
seconds = Integer.parseInt(s.substring(start));
nanos = 0;
} else {
seconds = Integer.parseInt(s.substring(start, dot));
nanos = parseNanos(s, dot + 1, s.length());
}
if (seconds < 0 || seconds > 59) {
throw new IllegalArgumentException(s);
}
return seconds * 1_000_000_000L + nanos;
}
/**
* Formats interval as a string.
*
* @param qualifier qualifier of the interval
* @param leading the value of leading field
* @param remaining the value of all remaining fields
* @return string representation of the specified interval
*/
public static String intervalToString(IntervalQualifier qualifier, long leading, long remaining) {
StringBuilder buff = new StringBuilder().append("INTERVAL ");
boolean negative = leading < 0;
if (negative) {
leading = -leading;
buff.append('-');
}
buff.append('\'');
switch (qualifier) {
case YEAR:
case MONTH:
case DAY:
case HOUR:
case MINUTE:
buff.append(leading);
break;
case SECOND:
buff.append(leading);
appendNanos(buff, remaining);
break;
case YEAR_TO_MONTH:
buff.append(leading).append('-').append(remaining);
break;
case DAY_TO_HOUR:
buff.append(leading).append(' ');
StringUtils.appendZeroPadded(buff, 2, remaining);
break;
case DAY_TO_MINUTE:
buff.append(leading).append(' ');
StringUtils.appendZeroPadded(buff, 2, remaining / 60);
buff.append(':');
StringUtils.appendZeroPadded(buff, 2, remaining % 60);
break;
case DAY_TO_SECOND: {
long nanos = remaining % 60_000_000_000L;
remaining /= 60_000_000_000L;
buff.append(leading).append(' ');
StringUtils.appendZeroPadded(buff, 2, remaining / 60);
buff.append(':');
StringUtils.appendZeroPadded(buff, 2, remaining % 60);
buff.append(':');
appendSecondsWithNanos(buff, nanos);
break;
}
case HOUR_TO_MINUTE:
buff.append(leading).append(':');
StringUtils.appendZeroPadded(buff, 2, remaining);
break;
case HOUR_TO_SECOND:
buff.append(leading).append(':');
StringUtils.appendZeroPadded(buff, 2, remaining / 60_000_000_000L);
buff.append(':');
appendSecondsWithNanos(buff, remaining % 60_000_000_000L);
break;
case MINUTE_TO_SECOND:
buff.append(leading).append(':');
appendSecondsWithNanos(buff, remaining);
break;
}
buff.append("' ").append(qualifier);
return buff.toString();
}
private static void appendSecondsWithNanos(StringBuilder buff, long nanos) {
StringUtils.appendZeroPadded(buff, 2, nanos / 1_000_000_000);
appendNanos(buff, nanos % 1_000_000_000);
}
private static void appendNanos(StringBuilder buff, long nanos) {
if (nanos > 0) {
buff.append('.');
StringUtils.appendZeroPadded(buff, 9, nanos);
stripTrailingZeroes(buff);
}
}
/** /**
* Converts scale of nanoseconds. * Converts scale of nanoseconds.
* *
......
...@@ -26,6 +26,7 @@ import java.util.HashMap; ...@@ -26,6 +26,7 @@ import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.IntervalQualifier;
import org.h2.api.TimestampWithTimeZone; import org.h2.api.TimestampWithTimeZone;
import org.h2.engine.Mode; import org.h2.engine.Mode;
import org.h2.engine.SessionInterface; import org.h2.engine.SessionInterface;
...@@ -181,9 +182,7 @@ public class DataType { ...@@ -181,9 +182,7 @@ public class DataType {
g = null; g = null;
} }
GEOMETRY_CLASS = g; GEOMETRY_CLASS = g;
}
static {
add(Value.NULL, Types.NULL, add(Value.NULL, Types.NULL,
new DataType(), new DataType(),
new String[]{"NULL"}, new String[]{"NULL"},
...@@ -294,7 +293,7 @@ public class DataType { ...@@ -294,7 +293,7 @@ public class DataType {
createDate(ValueDate.PRECISION, ValueDate.PRECISION, createDate(ValueDate.PRECISION, ValueDate.PRECISION,
"DATE", false, 0, 0), "DATE", false, 0, 0),
new String[]{"DATE"}, new String[]{"DATE"},
// 24 for ValueDate, 32 for java.sql.Data // 24 for ValueDate, 32 for java.sql.Date
56 56
); );
add(Value.TIMESTAMP, Types.TIMESTAMP, add(Value.TIMESTAMP, Types.TIMESTAMP,
...@@ -314,7 +313,7 @@ public class DataType { ...@@ -314,7 +313,7 @@ public class DataType {
"TIMESTAMP_TZ", true, ValueTimestampTimeZone.DEFAULT_SCALE, "TIMESTAMP_TZ", true, ValueTimestampTimeZone.DEFAULT_SCALE,
ValueTimestampTimeZone.MAXIMUM_SCALE), ValueTimestampTimeZone.MAXIMUM_SCALE),
new String[]{"TIMESTAMP WITH TIME ZONE"}, new String[]{"TIMESTAMP WITH TIME ZONE"},
// 26 for ValueTimestampUtc, 32 for java.sql.Timestamp // 26 for ValueTimestampTimeZone, 32 for java.sql.Timestamp
58 58
); );
add(Value.BYTES, Types.VARBINARY, add(Value.BYTES, Types.VARBINARY,
...@@ -384,6 +383,9 @@ public class DataType { ...@@ -384,6 +383,9 @@ public class DataType {
new String[]{"ENUM"}, new String[]{"ENUM"},
48 48
); );
for (int i = Value.INTERVAL_YEAR; i <= Value.INTERVAL_MINUTE_TO_SECOND; i++) {
addInterval(i);
}
for (Integer i : TYPES_BY_VALUE_TYPE.keySet()) { for (Integer i : TYPES_BY_VALUE_TYPE.keySet()) {
Value.getOrder(i); Value.getOrder(i);
} }
...@@ -412,6 +414,27 @@ public class DataType { ...@@ -412,6 +414,27 @@ public class DataType {
); );
} }
private static void addInterval(int type) {
IntervalQualifier qualifier = IntervalQualifier.valueOf(type - Value.INTERVAL_YEAR);
String name = qualifier.toString();
DataType dataType = new DataType();
dataType.prefix = "INTERVAL ";
dataType.suffix = ' ' + name;
dataType.supportsPrecision = true;
dataType.defaultPrecision = ValueInterval.DEFAULT_PRECISION;
dataType.maxPrecision = ValueInterval.MAXIMUM_PRECISION;
if (qualifier.hasSeconds()) {
dataType.supportsScale = true;
dataType.defaultScale = ValueInterval.DEFAULT_SCALE;
dataType.maxScale = ValueInterval.MAXIMUM_SCALE;
}
dataType.defaultDisplaySize = Integer.MAX_VALUE;
add(type, Types.OTHER, dataType,
new String[]{("INTERVAL " + name).intern()},
36
);
}
private static void add(int type, int sqlType, private static void add(int type, int sqlType,
DataType dataType, String[] names, int memory) { DataType dataType, String[] names, int memory) {
for (int i = 0; i < names.length; i++) { for (int i = 0; i < names.length; i++) {
...@@ -1286,6 +1309,16 @@ public class DataType { ...@@ -1286,6 +1309,16 @@ public class DataType {
} }
} }
/**
* Check if the given value type is an interval type.
*
* @param type the value type
* @return true if the value type is an interval type
*/
public static boolean isIntervalType(int type) {
return type >= Value.INTERVAL_YEAR && type <= Value.INTERVAL_MINUTE_TO_SECOND;
}
/** /**
* Check if the given value type is a large object (BLOB or CLOB). * Check if the given value type is a large object (BLOB or CLOB).
* *
...@@ -1321,6 +1354,19 @@ public class DataType { ...@@ -1321,6 +1354,19 @@ public class DataType {
case Value.INT: case Value.INT:
case Value.LONG: case Value.LONG:
case Value.SHORT: case Value.SHORT:
case Value.INTERVAL_YEAR:
case Value.INTERVAL_MONTH:
case Value.INTERVAL_DAY:
case Value.INTERVAL_HOUR:
case Value.INTERVAL_MINUTE:
case Value.INTERVAL_SECOND:
case Value.INTERVAL_YEAR_TO_MONTH:
case Value.INTERVAL_DAY_TO_HOUR:
case Value.INTERVAL_DAY_TO_MINUTE:
case Value.INTERVAL_DAY_TO_SECOND:
case Value.INTERVAL_HOUR_TO_MINUTE:
case Value.INTERVAL_HOUR_TO_SECOND:
case Value.INTERVAL_MINUTE_TO_SECOND:
return true; return true;
case Value.BOOLEAN: case Value.BOOLEAN:
case Value.TIME: case Value.TIME:
...@@ -1388,6 +1434,19 @@ public class DataType { ...@@ -1388,6 +1434,19 @@ public class DataType {
case Value.ARRAY: case Value.ARRAY:
case Value.RESULT_SET: case Value.RESULT_SET:
case Value.GEOMETRY: case Value.GEOMETRY:
case Value.INTERVAL_YEAR:
case Value.INTERVAL_MONTH:
case Value.INTERVAL_DAY:
case Value.INTERVAL_HOUR:
case Value.INTERVAL_MINUTE:
case Value.INTERVAL_SECOND:
case Value.INTERVAL_YEAR_TO_MONTH:
case Value.INTERVAL_DAY_TO_HOUR:
case Value.INTERVAL_DAY_TO_MINUTE:
case Value.INTERVAL_DAY_TO_SECOND:
case Value.INTERVAL_HOUR_TO_MINUTE:
case Value.INTERVAL_HOUR_TO_SECOND:
case Value.INTERVAL_MINUTE_TO_SECOND:
return type; return type;
default: default:
if (JdbcUtils.customDataTypesHandler != null) { if (JdbcUtils.customDataTypesHandler != null) {
......
...@@ -174,10 +174,75 @@ public abstract class Value { ...@@ -174,10 +174,75 @@ public abstract class Value {
*/ */
public static final int ENUM = 25; public static final int ENUM = 25;
/**
* The value type for {@code INTERVAL YEAR} values.
*/
public static final int INTERVAL_YEAR = 26;
/**
* The value type for {@code INTERVAL MONTH} values.
*/
public static final int INTERVAL_MONTH = 27;
/**
* The value type for {@code INTERVAL DAY} values.
*/
public static final int INTERVAL_DAY = 28;
/**
* The value type for {@code INTERVAL HOUR} values.
*/
public static final int INTERVAL_HOUR = 29;
/**
* The value type for {@code INTERVAL MINUTE} values.
*/
public static final int INTERVAL_MINUTE = 30;
/**
* The value type for {@code INTERVAL SECOND} values.
*/
public static final int INTERVAL_SECOND = 31;
/**
* The value type for {@code INTERVAL YEAR TO MONTH} values.
*/
public static final int INTERVAL_YEAR_TO_MONTH = 32;
/**
* The value type for {@code INTERVAL DAY TO HOUR} values.
*/
public static final int INTERVAL_DAY_TO_HOUR = 33;
/**
* The value type for {@code INTERVAL DAY TO MINUTE} values.
*/
public static final int INTERVAL_DAY_TO_MINUTE = 34;
/**
* The value type for {@code INTERVAL DAY TO SECOND} values.
*/
public static final int INTERVAL_DAY_TO_SECOND = 35;
/**
* The value type for {@code INTERVAL HOUR TO MINUTE} values.
*/
public static final int INTERVAL_HOUR_TO_MINUTE = 36;
/**
* The value type for {@code INTERVAL HOUR TO SECOND} values.
*/
public static final int INTERVAL_HOUR_TO_SECOND = 37;
/**
* The value type for {@code INTERVAL MINUTE TO SECOND} values.
*/
public static final int INTERVAL_MINUTE_TO_SECOND = 38;
/** /**
* The number of value types. * The number of value types.
*/ */
public static final int TYPE_COUNT = ENUM; public static final int TYPE_COUNT = INTERVAL_MINUTE_TO_SECOND;
private static SoftReference<Value[]> softCache; private static SoftReference<Value[]> softCache;
private static final BigDecimal MAX_LONG_DECIMAL = private static final BigDecimal MAX_LONG_DECIMAL =
...@@ -316,6 +381,32 @@ public abstract class Value { ...@@ -316,6 +381,32 @@ public abstract class Value {
return 26_000; return 26_000;
case DOUBLE: case DOUBLE:
return 27_000; return 27_000;
case INTERVAL_YEAR:
return 28_000;
case INTERVAL_MONTH:
return 28_100;
case INTERVAL_YEAR_TO_MONTH:
return 28_200;
case INTERVAL_DAY:
return 29_000;
case INTERVAL_HOUR:
return 29_100;
case INTERVAL_DAY_TO_HOUR:
return 29_200;
case INTERVAL_MINUTE:
return 29_300;
case INTERVAL_HOUR_TO_MINUTE:
return 29_400;
case INTERVAL_DAY_TO_MINUTE:
return 29_500;
case INTERVAL_SECOND:
return 29_600;
case INTERVAL_MINUTE_TO_SECOND:
return 29_700;
case INTERVAL_HOUR_TO_SECOND:
return 29_800;
case INTERVAL_DAY_TO_SECOND:
return 29_900;
case TIME: case TIME:
return 30_000; return 30_000;
case DATE: case DATE:
......
package org.h2.value;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.h2.api.Interval;
import org.h2.api.IntervalQualifier;
import org.h2.util.DateTimeUtils;
/**
* Implementation of the INTERVAL data type.
*/
public class ValueInterval extends Value {
/**
* The default leading field precision for intervals.
*/
public static final int DEFAULT_PRECISION = 2;
/**
* The maximum leading field precision for intervals.
*/
public static final int MAXIMUM_PRECISION = 18;
/**
* The default scale for intervals with seconds.
*/
static final int DEFAULT_SCALE = 6;
/**
* The maximum scale for intervals with seconds.
*/
public static final int MAXIMUM_SCALE = 9;
private final int type;
private final long leading;
private final long remaining;
/**
* @param qualifier
* qualifier
* @param leading
* value of leading field
* @param remaining
* values of all remaining fields
* @return interval value
*/
public static ValueInterval from(IntervalQualifier qualifier, long leading, long remaining) {
return (ValueInterval) Value.cache(new ValueInterval(qualifier.ordinal() + INTERVAL_YEAR, leading, remaining));
}
private ValueInterval(int type, long leading, long remaining) {
this.type = type;
this.leading = leading;
this.remaining = remaining;
}
@Override
public String getSQL() {
return getString();
}
@Override
public int getType() {
return type;
}
@Override
public long getPrecision() {
return MAXIMUM_PRECISION;
}
@Override
public int getDisplaySize() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getString() {
return DateTimeUtils.intervalToString(getQualifier(), leading, remaining);
}
@Override
public Object getObject() {
return new Interval(getQualifier(), leading, remaining);
}
/**
* Returns the interval qualifier.
*
* @return the interval qualifier
*/
public IntervalQualifier getQualifier() {
return IntervalQualifier.valueOf(type - INTERVAL_YEAR);
}
@Override
public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
prep.setString(parameterIndex, getString());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + type;
result = prime * result + (int) (leading ^ (leading >>> 32));
result = prime * result + (int) (remaining ^ (remaining >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ValueInterval)) {
return false;
}
ValueInterval other = (ValueInterval) obj;
return type == other.type && leading == other.leading || remaining == other.remaining;
}
@Override
public int compareTypeSafe(Value v, CompareMode mode) {
ValueInterval other = (ValueInterval) v;
int cmp = Long.compare(leading, other.leading);
if (cmp == 0) {
cmp = Long.compare(remaining, other.remaining);
}
return cmp;
}
@Override
public Value negate() {
return from(getQualifier(), -leading, remaining);
}
}
...@@ -118,7 +118,7 @@ public class TestScript extends TestDb { ...@@ -118,7 +118,7 @@ public class TestScript extends TestDb {
for (String s : new String[] { "array", "bigint", "binary", "blob", for (String s : new String[] { "array", "bigint", "binary", "blob",
"boolean", "char", "clob", "date", "decimal", decimal2, "double", "enum", "boolean", "char", "clob", "date", "decimal", decimal2, "double", "enum",
"geometry", "identity", "int", "other", "real", "smallint", "geometry", "identity", "int", "interval", "other", "real", "smallint",
"time", "timestamp-with-timezone", "timestamp", "tinyint", "time", "timestamp-with-timezone", "timestamp", "tinyint",
"uuid", "varchar", "varchar-ignorecase" }) { "uuid", "varchar", "varchar-ignorecase" }) {
testScript("datatypes/" + s + ".sql"); testScript("datatypes/" + s + ".sql");
......
...@@ -9,13 +9,13 @@ create memory table orders ( orderid varchar(10), name varchar(20), customer_id ...@@ -9,13 +9,13 @@ create memory table orders ( orderid varchar(10), name varchar(20), customer_id
> ok > ok
select * from information_schema.columns where table_name = 'ORDERS'; select * from information_schema.columns where table_name = 'ORDERS';
> TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_PRECISION_RADIX NUMERIC_SCALE DATETIME_PRECISION CHARACTER_SET_NAME COLLATION_NAME TYPE_NAME NULLABLE IS_COMPUTED SELECTIVITY CHECK_CONSTRAINT SEQUENCE_NAME REMARKS SOURCE_DATA_TYPE COLUMN_TYPE COLUMN_ON_UPDATE > TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_PRECISION_RADIX NUMERIC_SCALE DATETIME_PRECISION INTERVAL_TYPE INTERVAL_PRECISION CHARACTER_SET_NAME COLLATION_NAME TYPE_NAME NULLABLE IS_COMPUTED SELECTIVITY CHECK_CONSTRAINT SEQUENCE_NAME REMARKS SOURCE_DATA_TYPE COLUMN_TYPE COLUMN_ON_UPDATE
> ------------- ------------ ---------- ----------- ---------------- -------------- ----------- --------- ------------------------ ---------------------- ----------------- ----------------------- ------------- ------------------ ------------------ -------------- --------- -------- ----------- ----------- ---------------- ------------- ------- ---------------- ------------------- ---------------- > ------------- ------------ ---------- ----------- ---------------- -------------- ----------- --------- ------------------------ ---------------------- ----------------- ----------------------- ------------- ------------------ ------------- ------------------ ------------------ -------------- --------- -------- ----------- ----------- ---------------- ------------- ------- ---------------- ------------------- ----------------
> SCRIPT PUBLIC ORDERS COMPLETED 4 null NO 3 1 1 1 10 0 null Unicode OFF DECIMAL 0 FALSE 50 null null NUMERIC(1) NOT NULL null > SCRIPT PUBLIC ORDERS COMPLETED 4 null NO 3 1 1 1 10 0 null null null Unicode OFF DECIMAL 0 FALSE 50 null null NUMERIC(1) NOT NULL null
> SCRIPT PUBLIC ORDERS CUSTOMER_ID 3 null YES 12 10 10 10 10 0 null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(10) null > SCRIPT PUBLIC ORDERS CUSTOMER_ID 3 null YES 12 10 10 10 10 0 null null null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(10) null
> SCRIPT PUBLIC ORDERS NAME 2 null YES 12 20 20 20 10 0 null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(20) null > SCRIPT PUBLIC ORDERS NAME 2 null YES 12 20 20 20 10 0 null null null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(20) null
> SCRIPT PUBLIC ORDERS ORDERID 1 null YES 12 10 10 10 10 0 null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(10) null > SCRIPT PUBLIC ORDERS ORDERID 1 null YES 12 10 10 10 10 0 null null null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(10) null
> SCRIPT PUBLIC ORDERS VERIFIED 5 null YES 3 1 1 1 10 0 null Unicode OFF DECIMAL 1 FALSE 50 null null NUMERIC(1) null > SCRIPT PUBLIC ORDERS VERIFIED 5 null YES 3 1 1 1 10 0 null null null Unicode OFF DECIMAL 1 FALSE 50 null null NUMERIC(1) null
> rows: 5 > rows: 5
drop table orders; drop table orders;
......
...@@ -9,13 +9,13 @@ create memory table orders ( orderid varchar(10), name varchar(20), customer_id ...@@ -9,13 +9,13 @@ create memory table orders ( orderid varchar(10), name varchar(20), customer_id
> ok > ok
select * from information_schema.columns where table_name = 'ORDERS'; select * from information_schema.columns where table_name = 'ORDERS';
> TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_PRECISION_RADIX NUMERIC_SCALE DATETIME_PRECISION CHARACTER_SET_NAME COLLATION_NAME TYPE_NAME NULLABLE IS_COMPUTED SELECTIVITY CHECK_CONSTRAINT SEQUENCE_NAME REMARKS SOURCE_DATA_TYPE COLUMN_TYPE COLUMN_ON_UPDATE > TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_PRECISION_RADIX NUMERIC_SCALE DATETIME_PRECISION INTERVAL_TYPE INTERVAL_PRECISION CHARACTER_SET_NAME COLLATION_NAME TYPE_NAME NULLABLE IS_COMPUTED SELECTIVITY CHECK_CONSTRAINT SEQUENCE_NAME REMARKS SOURCE_DATA_TYPE COLUMN_TYPE COLUMN_ON_UPDATE
> ------------- ------------ ---------- ----------- ---------------- -------------- ----------- --------- ------------------------ ---------------------- ----------------- ----------------------- ------------- ------------------ ------------------ -------------- --------- -------- ----------- ----------- ---------------- ------------- ------- ---------------- ------------------- ---------------- > ------------- ------------ ---------- ----------- ---------------- -------------- ----------- --------- ------------------------ ---------------------- ----------------- ----------------------- ------------- ------------------ ------------- ------------------ ------------------ -------------- --------- -------- ----------- ----------- ---------------- ------------- ------- ---------------- ------------------- ----------------
> SCRIPT PUBLIC ORDERS COMPLETED 4 null NO 2 1 1 1 10 0 null Unicode OFF NUMERIC 0 FALSE 50 null null NUMERIC(1) NOT NULL null > SCRIPT PUBLIC ORDERS COMPLETED 4 null NO 2 1 1 1 10 0 null null null Unicode OFF NUMERIC 0 FALSE 50 null null NUMERIC(1) NOT NULL null
> SCRIPT PUBLIC ORDERS CUSTOMER_ID 3 null YES 12 10 10 10 10 0 null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(10) null > SCRIPT PUBLIC ORDERS CUSTOMER_ID 3 null YES 12 10 10 10 10 0 null null null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(10) null
> SCRIPT PUBLIC ORDERS NAME 2 null YES 12 20 20 20 10 0 null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(20) null > SCRIPT PUBLIC ORDERS NAME 2 null YES 12 20 20 20 10 0 null null null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(20) null
> SCRIPT PUBLIC ORDERS ORDERID 1 null YES 12 10 10 10 10 0 null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(10) null > SCRIPT PUBLIC ORDERS ORDERID 1 null YES 12 10 10 10 10 0 null null null Unicode OFF VARCHAR 1 FALSE 50 null null VARCHAR(10) null
> SCRIPT PUBLIC ORDERS VERIFIED 5 null YES 2 1 1 1 10 0 null Unicode OFF NUMERIC 1 FALSE 50 null null NUMERIC(1) null > SCRIPT PUBLIC ORDERS VERIFIED 5 null YES 2 1 1 1 10 0 null null null Unicode OFF NUMERIC 1 FALSE 50 null null NUMERIC(1) null
> rows: 5 > rows: 5
drop table orders; drop table orders;
......
...@@ -242,13 +242,13 @@ SELECT * FROM V3; ...@@ -242,13 +242,13 @@ SELECT * FROM V3;
>> -1 >> -1
SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'E' ORDER BY TABLE_NAME; SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'E' ORDER BY TABLE_NAME;
> TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_PRECISION_RADIX NUMERIC_SCALE DATETIME_PRECISION CHARACTER_SET_NAME COLLATION_NAME TYPE_NAME NULLABLE IS_COMPUTED SELECTIVITY CHECK_CONSTRAINT SEQUENCE_NAME REMARKS SOURCE_DATA_TYPE COLUMN_TYPE COLUMN_ON_UPDATE > TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_PRECISION_RADIX NUMERIC_SCALE DATETIME_PRECISION INTERVAL_TYPE INTERVAL_PRECISION CHARACTER_SET_NAME COLLATION_NAME TYPE_NAME NULLABLE IS_COMPUTED SELECTIVITY CHECK_CONSTRAINT SEQUENCE_NAME REMARKS SOURCE_DATA_TYPE COLUMN_TYPE COLUMN_ON_UPDATE
> ------------- ------------ ---------- ----------- ---------------- -------------- ----------- --------- ------------------------ ---------------------- ----------------- ----------------------- ------------- ------------------ ------------------ -------------- --------- -------- ----------- ----------- ---------------- ------------- ------- ---------------- ------------- ---------------- > ------------- ------------ ---------- ----------- ---------------- -------------- ----------- --------- ------------------------ ---------------------- ----------------- ----------------------- ------------- ------------------ ------------- ------------------ ------------------ -------------- --------- -------- ----------- ----------- ---------------- ------------- ------- ---------------- ------------- ----------------
> SCRIPT PUBLIC TEST E 1 null YES 1111 2147483647 2147483647 2147483647 10 0 null Unicode OFF ENUM 1 FALSE 50 null null ENUM('A','B') null > SCRIPT PUBLIC TEST E 1 null YES 1111 2147483647 2147483647 2147483647 10 0 null null null Unicode OFF ENUM 1 FALSE 50 null null ENUM('A','B') null
> SCRIPT PUBLIC V E 1 null YES 1111 2147483647 2147483647 2147483647 10 0 null Unicode OFF ENUM 1 FALSE 50 null null ENUM('A','B') null > SCRIPT PUBLIC V E 1 null YES 1111 2147483647 2147483647 2147483647 10 0 null null null Unicode OFF ENUM 1 FALSE 50 null null ENUM('A','B') null
> SCRIPT PUBLIC V1 E 1 null YES 4 2147483647 2147483647 2147483647 10 0 null Unicode OFF INTEGER 1 FALSE 50 null null INTEGER null > SCRIPT PUBLIC V1 E 1 null YES 4 2147483647 2147483647 2147483647 10 0 null null null Unicode OFF INTEGER 1 FALSE 50 null null INTEGER null
> SCRIPT PUBLIC V2 E 1 null YES 4 2147483647 2147483647 2147483647 10 0 null Unicode OFF INTEGER 1 FALSE 50 null null INTEGER null > SCRIPT PUBLIC V2 E 1 null YES 4 2147483647 2147483647 2147483647 10 0 null null null Unicode OFF INTEGER 1 FALSE 50 null null INTEGER null
> SCRIPT PUBLIC V3 E 1 null YES 4 2147483647 2147483647 2147483647 10 0 null Unicode OFF INTEGER 1 FALSE 50 null null INTEGER null > SCRIPT PUBLIC V3 E 1 null YES 4 2147483647 2147483647 2147483647 10 0 null null null Unicode OFF INTEGER 1 FALSE 50 null null INTEGER null
> rows (ordered): 5 > rows (ordered): 5
DROP VIEW V; DROP VIEW V;
......
-- Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
CREATE TABLE TEST(I01 INTERVAL YEAR, I02 INTERVAL MONTH, I03 INTERVAL DAY, I04 INTERVAL HOUR, I05 INTERVAL MINUTE,
I06 INTERVAL SECOND, I07 INTERVAL YEAR TO MONTH, I08 INTERVAL DAY TO HOUR, I09 INTERVAL DAY TO MINUTE,
I10 INTERVAL DAY TO SECOND, I11 INTERVAL HOUR TO MINUTE, I12 INTERVAL HOUR TO SECOND,
I13 INTERVAL MINUTE TO SECOND,
J01 INTERVAL YEAR(5), J02 INTERVAL MONTH(5), J03 INTERVAL DAY(5), J04 INTERVAL HOUR(5), J05 INTERVAL MINUTE(5),
J06 INTERVAL SECOND(5, 9), J07 INTERVAL YEAR(5) TO MONTH, J08 INTERVAL DAY(5) TO HOUR,
J09 INTERVAL DAY(5) TO MINUTE, J10 INTERVAL DAY(5) TO SECOND(9), J11 INTERVAL HOUR(5) TO MINUTE,
J12 INTERVAL HOUR(5) TO SECOND(9), J13 INTERVAL MINUTE(5) TO SECOND(9));
> ok
SELECT COLUMN_NAME, DATA_TYPE, TYPE_NAME, COLUMN_TYPE, NUMERIC_PRECISION, NUMERIC_SCALE, DATETIME_PRECISION,
INTERVAL_TYPE, INTERVAL_PRECISION
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION;
> COLUMN_NAME DATA_TYPE TYPE_NAME COLUMN_TYPE NUMERIC_PRECISION NUMERIC_SCALE DATETIME_PRECISION INTERVAL_TYPE INTERVAL_PRECISION
> ----------- --------- --------- ------------------------------- ----------------- ------------- ------------------ ---------------------- ------------------
> I01 1111 INTERVAL INTERVAL YEAR 2 0 null YEAR 2
> I02 1111 INTERVAL INTERVAL MONTH 2 0 null MONTH 2
> I03 1111 INTERVAL INTERVAL DAY 2 0 null DAY 2
> I04 1111 INTERVAL INTERVAL HOUR 2 0 null HOUR 2
> I05 1111 INTERVAL INTERVAL MINUTE 2 0 null MINUTE 2
> I06 1111 INTERVAL INTERVAL SECOND 2 6 6 SECOND 2
> I07 1111 INTERVAL INTERVAL YEAR TO MONTH 2 0 null YEAR TO MONTH 2
> I08 1111 INTERVAL INTERVAL DAY TO HOUR 2 0 null DAY TO HOUR 2
> I09 1111 INTERVAL INTERVAL DAY TO MINUTE 2 0 null DAY TO MINUTE 2
> I10 1111 INTERVAL INTERVAL DAY TO SECOND 2 6 6 DAY TO SECOND 2
> I11 1111 INTERVAL INTERVAL HOUR TO MINUTE 2 0 null HOUR TO MINUTE 2
> I12 1111 INTERVAL INTERVAL HOUR TO SECOND 2 6 6 HOUR TO SECOND 2
> I13 1111 INTERVAL INTERVAL MINUTE TO SECOND 2 6 6 MINUTE TO SECOND 2
> J01 1111 INTERVAL INTERVAL YEAR(5) 5 0 null YEAR(5) 5
> J02 1111 INTERVAL INTERVAL MONTH(5) 5 0 null MONTH(5) 5
> J03 1111 INTERVAL INTERVAL DAY(5) 5 0 null DAY(5) 5
> J04 1111 INTERVAL INTERVAL HOUR(5) 5 0 null HOUR(5) 5
> J05 1111 INTERVAL INTERVAL MINUTE(5) 5 0 null MINUTE(5) 5
> J06 1111 INTERVAL INTERVAL SECOND(5, 9) 5 6 6 SECOND(5, 9) 5
> J07 1111 INTERVAL INTERVAL YEAR(5) TO MONTH 5 0 null YEAR(5) TO MONTH 5
> J08 1111 INTERVAL INTERVAL DAY(5) TO HOUR 5 0 null DAY(5) TO HOUR 5
> J09 1111 INTERVAL INTERVAL DAY(5) TO MINUTE 5 0 null DAY(5) TO MINUTE 5
> J10 1111 INTERVAL INTERVAL DAY(5) TO SECOND(9) 5 6 6 DAY(5) TO SECOND(9) 5
> J11 1111 INTERVAL INTERVAL HOUR(5) TO MINUTE 5 0 null HOUR(5) TO MINUTE 5
> J12 1111 INTERVAL INTERVAL HOUR(5) TO SECOND(9) 5 6 6 HOUR(5) TO SECOND(9) 5
> J13 1111 INTERVAL INTERVAL MINUTE(5) TO SECOND(9) 5 6 6 MINUTE(5) TO SECOND(9) 5
> rows (ordered): 26
DROP TABLE TEST;
> ok
...@@ -12,8 +12,10 @@ import java.util.Calendar; ...@@ -12,8 +12,10 @@ import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.TimeZone; import java.util.TimeZone;
import org.h2.api.IntervalQualifier;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.value.ValueInterval;
import org.h2.value.ValueTimestamp; import org.h2.value.ValueTimestamp;
/** /**
...@@ -47,6 +49,7 @@ public class TestDateTimeUtils extends TestBase { ...@@ -47,6 +49,7 @@ public class TestDateTimeUtils extends TestBase {
testDateValueFromDenormalizedDate(); testDateValueFromDenormalizedDate();
testUTC2Value(false); testUTC2Value(false);
testConvertScale(); testConvertScale();
testParseInterval();
} }
private void testParseTimeNanosDB2Format() { private void testParseTimeNanosDB2Format() {
...@@ -194,4 +197,74 @@ public class TestDateTimeUtils extends TestBase { ...@@ -194,4 +197,74 @@ public class TestDateTimeUtils extends TestBase {
assertEquals(101_000_000_000L, DateTimeUtils.convertScale(100_999_999_999L, 0)); assertEquals(101_000_000_000L, DateTimeUtils.convertScale(100_999_999_999L, 0));
} }
private void testParseInterval() {
testParseIntervalSimple(IntervalQualifier.YEAR);
testParseIntervalSimple(IntervalQualifier.MONTH);
testParseIntervalSimple(IntervalQualifier.DAY);
testParseIntervalSimple(IntervalQualifier.HOUR);
testParseIntervalSimple(IntervalQualifier.MINUTE);
testParseIntervalSimple(IntervalQualifier.SECOND);
testParseInterval(IntervalQualifier.YEAR_TO_MONTH, 10, 0, "10", "10-0");
testParseInterval(IntervalQualifier.YEAR_TO_MONTH, 10, 11, "10-11");
testParseInterval(IntervalQualifier.DAY_TO_HOUR, 10, 0, "10", "10 00");
testParseInterval(IntervalQualifier.DAY_TO_HOUR, 10, 11, "10 11");
testParseInterval(IntervalQualifier.DAY_TO_MINUTE, 10, 0, "10", "10 00:00");
testParseInterval(IntervalQualifier.DAY_TO_MINUTE, 10, 11 * 60, "10 11", "10 11:00");
testParseInterval(IntervalQualifier.DAY_TO_MINUTE, 10, 11 * 60 + 12, "10 11:12");
testParseInterval(IntervalQualifier.DAY_TO_SECOND, 10, 0, "10 00:00:00");
testParseInterval(IntervalQualifier.DAY_TO_SECOND, 10, 11 * 3_600_000_000_000L, "10 11", "10 11:00:00");
testParseInterval(IntervalQualifier.DAY_TO_SECOND, 10, 11 * 3_600_000_000_000L + 12 * 60_000_000_000L,
"10 11:12", "10 11:12:00");
testParseInterval(IntervalQualifier.DAY_TO_SECOND,
10, 11 * 3_600_000_000_000L + 12 * 60_000_000_000L + 13_000_000_000L,
"10 11:12:13");
testParseInterval(IntervalQualifier.DAY_TO_SECOND,
10, 11 * 3_600_000_000_000L + 12 * 60_000_000_000L + 13_123_456_789L,
"10 11:12:13.123456789");
testParseInterval(IntervalQualifier.HOUR_TO_MINUTE, 10, 0, "10", "10:00");
testParseInterval(IntervalQualifier.HOUR_TO_MINUTE, 10, 11, "10:11");
testParseInterval(IntervalQualifier.HOUR_TO_SECOND, 10, 0, "10", "10:00:00");
testParseInterval(IntervalQualifier.HOUR_TO_SECOND, 10, 11 * 60_000_000_000L, "10:11", "10:11:00");
testParseInterval(IntervalQualifier.HOUR_TO_SECOND, 10, 11 * 60_000_000_000L + 12_000_000_000L,
"10:11:12");
testParseInterval(IntervalQualifier.HOUR_TO_SECOND, 10, 11 * 60_000_000_000L + 12_123_456_789L,
"10:11:12.123456789");
testParseInterval(IntervalQualifier.MINUTE_TO_SECOND, 10, 0, "10", "10:00");
testParseInterval(IntervalQualifier.MINUTE_TO_SECOND, 10, 11_000_000_000L, "10:11", "10:11");
testParseInterval(IntervalQualifier.MINUTE_TO_SECOND, 10, 11_123_456_789L, "10:11.123456789");
}
private void testParseIntervalSimple(IntervalQualifier qualifier) {
testParseInterval(qualifier, 10, 0, "10");
}
private void testParseInterval(IntervalQualifier qualifier, long leading, long remaining, String s) {
testParseInterval(qualifier, leading, remaining, s, s);
}
private void testParseInterval(IntervalQualifier qualifier, long leading, long remaining, String s, String full) {
testParseIntervalImpl(qualifier, leading, remaining, false, s, full);
testParseIntervalImpl(qualifier, -leading, remaining, true, s, full);
}
private void testParseIntervalImpl(IntervalQualifier qualifier, long leading, long remaining, boolean negative,
String s, String full) {
ValueInterval expected = ValueInterval.from(qualifier, leading, remaining);
assertEquals(expected, DateTimeUtils.parseInterval(qualifier, negative, s));
StringBuilder b = new StringBuilder();
b.append("INTERVAL ");
if (negative) {
b.append('-');
}
b.append('\'').append(full).append("' ").append(qualifier);
assertEquals(b.toString(), expected.getString());
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论