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

Parse fractional seconds precision as described in SQL standard

上级 e47fbaa9
......@@ -2446,9 +2446,10 @@ REAL
"
"Data Types","TIME Type","
TIME [ WITHOUT TIME ZONE ]
TIME [ ( precisionInt ) ] [ WITHOUT TIME ZONE ]
","
The time data type. The format is hh:mm:ss[.nnnnnnnnn].
If fractional seconds precision is specified it should be from 0 to 9, 0 is default.
Mapped to ""java.sql.Time"". When converted to a ""java.sql.Date"", the date is set to ""1970-01-01"".
""java.time.LocalTime"" is also supported on Java 8 and later versions.
......@@ -2470,10 +2471,11 @@ DATE
"
"Data Types","TIMESTAMP Type","
{ TIMESTAMP [ WITHOUT TIME ZONE ] | DATETIME | SMALLDATETIME }
{ TIMESTAMP [ ( precisionInt ) ] [ WITHOUT TIME ZONE ] | DATETIME | SMALLDATETIME }
","
The timestamp data type. The format is yyyy-MM-dd hh:mm:ss[.nnnnnnnnn].
Stored internally as a BCD-encoded date, and nanoseconds since midnight.
If fractional seconds precision is specified it should be from 0 to 9, 6 is default.
Mapped to ""java.sql.Timestamp"" (""java.util.Date"" may be used too).
""java.time.LocalDateTime"" is also supported on Java 8 and later versions.
......@@ -2482,10 +2484,11 @@ TIMESTAMP
"
"Data Types","TIMESTAMP WITH TIME ZONE Type","
TIMESTAMP WITH TIME ZONE
TIMESTAMP [ ( precisionInt ) ] WITH TIME ZONE
","
The timestamp with time zone data type.
Stored internally as a BCD-encoded date, nanoseconds since midnight, and time zone offset in minutes.
If fractional seconds precision is specified it should be from 0 to 9, 6 is default.
Mapped to ""org.h2.api.TimestampWithTimeZone"".
""java.time.OffsetDateTime"" and ""java.time.Instant"" are also supported on Java 8 and later versions.
......
......@@ -4338,6 +4338,7 @@ public class Parser {
private Column parseColumnWithType(String columnName) {
String original = currentToken;
boolean regular = false;
int originalScale = -1;
if (readIf("LONG")) {
if (readIf("RAW")) {
original += " RAW";
......@@ -4351,12 +4352,30 @@ public class Parser {
original += " VARYING";
}
} else if (readIf("TIME")) {
if (readIf("(")) {
originalScale = readPositiveInt();
if (originalScale > ValueTime.MAXIMUM_SCALE) {
throw DbException.get(ErrorCode.INVALID_VALUE_SCALE_PRECISION, Integer.toString(originalScale));
}
read(")");
}
if (readIf("WITHOUT")) {
read("TIME");
read("ZONE");
original += " WITHOUT TIME ZONE";
}
} else if (readIf("TIMESTAMP")) {
if (readIf("(")) {
originalScale = readPositiveInt();
// Allow non-standard TIMESTAMP(..., ...) syntax
if (readIf(",")) {
originalScale = readPositiveInt();
}
if (originalScale > ValueTimestamp.MAXIMUM_SCALE) {
throw DbException.get(ErrorCode.INVALID_VALUE_SCALE_PRECISION, Integer.toString(originalScale));
}
read(")");
}
if (readIf("WITH")) {
read("TIME");
read("ZONE");
......@@ -4410,7 +4429,35 @@ public class Parser {
: displaySize;
scale = scale == -1 ? dataType.defaultScale : scale;
if (dataType.supportsPrecision || dataType.supportsScale) {
if (readIf("(")) {
int t = dataType.type;
if (t == Value.TIME || t == Value.TIMESTAMP || t == Value.TIMESTAMP_TZ) {
if (originalScale >= 0) {
scale = originalScale;
precision = dataType.defaultPrecision;
switch (t) {
case Value.TIME:
if (original.equals("TIME WITHOUT TIME ZONE")) {
original = "TIME(" + originalScale + ") WITHOUT TIME ZONE";
} else {
original = original + '(' + originalScale + ')';
}
displaySize = ValueTime.getDisplaySize(originalScale);
break;
case Value.TIMESTAMP:
if (original.equals("TIMESTAMP WITHOUT TIME ZONE")) {
original = "TIMESTAMP(" + originalScale + ") WITHOUT TIME ZONE";
} else {
original = original + '(' + originalScale + ')';
}
displaySize = ValueTimestamp.getDisplaySize(originalScale);
break;
case Value.TIMESTAMP_TZ:
original = "TIMESTAMP(" + originalScale + ") WITH TIME ZONE";
displaySize = ValueTimestampTimeZone.getDisplaySize(originalScale);
break;
}
}
} else if (readIf("(")) {
if (!readIf("MAX")) {
long p = readLong();
if (readIf("K")) {
......@@ -4431,14 +4478,7 @@ public class Parser {
scale = readInt();
original += ", " + scale;
} else {
// special case: TIMESTAMP(5) actually means
// TIMESTAMP(23, 5)
if (dataType.type == Value.TIMESTAMP) {
scale = MathUtils.convertLongToInt(p);
p = precision;
} else {
scale = 0;
}
scale = 0;
}
}
precision = p;
......
......@@ -282,20 +282,24 @@ public class DataType {
24
);
add(Value.TIME, Types.TIME,
createDate(ValueTime.PRECISION, "TIME", 0, ValueTime.DISPLAY_SIZE),
createDate(ValueTime.PRECISION, "TIME",
true, ValueTime.DEFAULT_SCALE, ValueTime.MAXIMUM_SCALE,
ValueTime.DISPLAY_SIZE),
new String[]{"TIME", "TIME WITHOUT TIME ZONE"},
// 24 for ValueTime, 32 for java.sql.Time
56
);
add(Value.DATE, Types.DATE,
createDate(ValueDate.PRECISION, "DATE", 0, ValueDate.DISPLAY_SIZE),
createDate(ValueDate.PRECISION, "DATE",
false, 0, 0, ValueDate.DISPLAY_SIZE),
new String[]{"DATE"},
// 24 for ValueDate, 32 for java.sql.Data
56
);
add(Value.TIMESTAMP, Types.TIMESTAMP,
createDate(ValueTimestamp.PRECISION, "TIMESTAMP",
ValueTimestamp.DEFAULT_SCALE, ValueTimestamp.DISPLAY_SIZE),
true, ValueTimestamp.DEFAULT_SCALE, ValueTimestamp.MAXIMUM_SCALE,
ValueTimestamp.DISPLAY_SIZE),
new String[]{"TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE",
"DATETIME", "DATETIME2", "SMALLDATETIME"},
// 24 for ValueTimestamp, 32 for java.sql.Timestamp
......@@ -307,7 +311,8 @@ public class DataType {
// Types.TIMESTAMP_WITH_TIMEZONE once Java 1.8 is required.
add(Value.TIMESTAMP_TZ, 2014,
createDate(ValueTimestampTimeZone.PRECISION, "TIMESTAMP_TZ",
ValueTimestampTimeZone.DEFAULT_SCALE, ValueTimestampTimeZone.DISPLAY_SIZE),
true, ValueTimestampTimeZone.DEFAULT_SCALE, ValueTimestampTimeZone.MAXIMUM_SCALE,
ValueTimestampTimeZone.DISPLAY_SIZE),
new String[]{"TIMESTAMP WITH TIME ZONE"},
// 26 for ValueTimestampUtc, 32 for java.sql.Timestamp
58
......@@ -461,14 +466,14 @@ public class DataType {
return dataType;
}
private static DataType createDate(int precision, String prefix, int scale,
private static DataType createDate(int precision, String prefix, boolean supportsScale, int scale, int maxScale,
int displaySize) {
DataType dataType = new DataType();
dataType.prefix = prefix + " '";
dataType.suffix = "'";
dataType.maxPrecision = precision;
dataType.supportsScale = scale != 0;
dataType.maxScale = scale;
dataType.supportsScale = supportsScale;
dataType.maxScale = maxScale;
dataType.defaultPrecision = precision;
dataType.defaultScale = scale;
dataType.defaultDisplaySize = displaySize;
......
......@@ -21,7 +21,7 @@ public class ValueTime extends Value {
/**
* The precision in digits.
*/
public static final int PRECISION = 6;
public static final int PRECISION = 9;
/**
* The display size of the textual representation of a time.
......@@ -29,6 +29,26 @@ public class ValueTime extends Value {
*/
static final int DISPLAY_SIZE = 8;
/**
* The default scale for time.
*/
static final int DEFAULT_SCALE = 0;
/**
* The maximum scale for time.
*/
public static final int MAXIMUM_SCALE = 9;
/**
* Get display size for the specified scale.
*
* @param scale scale
* @return display size
*/
public static int getDisplaySize(int scale) {
return scale == 0 ? 8 : 9 + scale;
}
/**
* Nanoseconds since midnight
*/
......
......@@ -33,7 +33,22 @@ public class ValueTimestamp extends Value {
/**
* The default scale for timestamps.
*/
static final int DEFAULT_SCALE = 10;
static final int DEFAULT_SCALE = 6;
/**
* The maximum scale for timestamps.
*/
public static final int MAXIMUM_SCALE = 9;
/**
* Get display size for the specified scale.
*
* @param scale scale
* @return display size
*/
public static int getDisplaySize(int scale) {
return scale == 0 ? 19 : 20 + scale;
}
/**
* A bit field with bits for the year, month, and day (see DateTimeUtils for
......@@ -184,7 +199,7 @@ public class ValueTimestamp extends Value {
@Override
public int getScale() {
return DEFAULT_SCALE;
return MAXIMUM_SCALE;
}
@Override
......@@ -194,7 +209,7 @@ public class ValueTimestamp extends Value {
@Override
public Value convertScale(boolean onlyToSmallerScale, int targetScale) {
if (targetScale >= DEFAULT_SCALE) {
if (targetScale >= MAXIMUM_SCALE) {
return this;
}
if (targetScale < 0) {
......
......@@ -36,7 +36,22 @@ public class ValueTimestampTimeZone extends Value {
/**
* The default scale for timestamps.
*/
static final int DEFAULT_SCALE = 10;
static final int DEFAULT_SCALE = ValueTimestamp.DEFAULT_SCALE;
/**
* The default scale for timestamps.
*/
static final int MAXIMUM_SCALE = ValueTimestamp.MAXIMUM_SCALE;
/**
* Get display size for the specified scale.
*
* @param scale scale
* @return display size
*/
public static int getDisplaySize(int scale) {
return scale == 0 ? 25 : 26 + scale;
}
/**
* A bit field with bits for the year, month, and day (see DateTimeUtils for
......@@ -173,7 +188,7 @@ public class ValueTimestampTimeZone extends Value {
@Override
public int getScale() {
return DEFAULT_SCALE;
return MAXIMUM_SCALE;
}
@Override
......@@ -183,7 +198,7 @@ public class ValueTimestampTimeZone extends Value {
@Override
public Value convertScale(boolean onlyToSmallerScale, int targetScale) {
if (targetScale >= DEFAULT_SCALE) {
if (targetScale >= MAXIMUM_SCALE) {
return this;
}
if (targetScale < 0) {
......
......@@ -723,7 +723,7 @@ public class TestMetaData extends TestBase {
"" + DatabaseMetaData.columnNullable, "", null,
"" + numericType, "0", "12", "3", "YES" },
{ CATALOG, Constants.SCHEMA_MAIN, "TEST", "DATE_V",
"" + Types.TIMESTAMP, "TIMESTAMP", "23", "23", "10",
"" + Types.TIMESTAMP, "TIMESTAMP", "23", "23", "6",
"10", "" + DatabaseMetaData.columnNullable, "", null,
"" + Types.TIMESTAMP, "0", "23", "4", "YES" },
{ CATALOG, Constants.SCHEMA_MAIN, "TEST", "BLOB_V",
......
......@@ -1280,11 +1280,11 @@ public class TestResultSet extends TestBase {
"TIMESTAMP '9999-12-31 23:59:59' VALUE FROM TEST ORDER BY ID");
assertResultSetMeta(rs, 2, new String[] { "ID", "VALUE" },
new int[] { Types.INTEGER, Types.TIMESTAMP },
new int[] { 10, 23 }, new int[] { 0, 10 });
new int[] { 10, 23 }, new int[] { 0, 9 });
rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID");
assertResultSetMeta(rs, 2, new String[] { "ID", "VALUE" },
new int[] { Types.INTEGER, Types.TIMESTAMP },
new int[] { 10, 23 }, new int[] { 0, 10 });
new int[] { 10, 23 }, new int[] { 0, 6 });
rs.next();
java.sql.Date date;
java.sql.Time time;
......@@ -1431,7 +1431,7 @@ public class TestResultSet extends TestBase {
ResultSet rs;
stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, " +
"D DATE, T TIME, TS TIMESTAMP)");
"D DATE, T TIME, TS TIMESTAMP(9))");
PreparedStatement prep = conn.prepareStatement(
"INSERT INTO TEST VALUES(?, ?, ?, ?)");
Calendar regular = DateTimeUtils.createGregorianCalendar();
......@@ -1494,7 +1494,7 @@ public class TestResultSet extends TestBase {
new String[] { "ID", "D", "T", "TS" },
new int[] { Types.INTEGER, Types.DATE,
Types.TIME, Types.TIMESTAMP },
new int[] { 10, 8, 6, 23 }, new int[] { 0, 0, 0, 10 });
new int[] { 10, 8, 9, 23 }, new int[] { 0, 0, 0, 9 });
rs.next();
assertEquals(0, rs.getInt(1));
......
......@@ -296,7 +296,7 @@ public class TestUpdatableResultSet extends TestBase {
ResultSet.CONCUR_UPDATABLE);
stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255), "
+ "DEC DECIMAL(10,2), BOO BIT, BYE TINYINT, BIN BINARY(100), "
+ "D DATE, T TIME, TS TIMESTAMP, DB DOUBLE, R REAL, L BIGINT, "
+ "D DATE, T TIME, TS TIMESTAMP(9), DB DOUBLE, R REAL, L BIGINT, "
+ "O_I INT, SH SMALLINT, CL CLOB, BL BLOB)");
ResultSet rs = stat.executeQuery("SELECT * FROM TEST");
ResultSetMetaData meta = rs.getMetaData();
......
......@@ -23,6 +23,22 @@ SELECT COLUMN_NAME, DATA_TYPE, TYPE_NAME, COLUMN_TYPE FROM INFORMATION_SCHEMA.CO
> T2 92 TIME TIME WITHOUT TIME ZONE
> rows (ordered): 2
ALTER TABLE TEST ADD (T3 TIME(0), T4 TIME(9) WITHOUT TIME ZONE);
> ok
SELECT COLUMN_NAME, DATA_TYPE, TYPE_NAME, COLUMN_TYPE, NUMERIC_SCALE FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION;
> COLUMN_NAME DATA_TYPE TYPE_NAME COLUMN_TYPE NUMERIC_SCALE
> ----------- --------- --------- ------------------------- -------------
> T1 92 TIME TIME 0
> T2 92 TIME TIME WITHOUT TIME ZONE 0
> T3 92 TIME TIME(0) 0
> T4 92 TIME TIME(9) WITHOUT TIME ZONE 9
> rows (ordered): 4
ALTER TABLE TEST ADD T5 TIME(10);
> exception
DROP TABLE TEST;
> ok
......
......@@ -35,3 +35,21 @@ SELECT TIMESTAMP WITH TIME ZONE '2000-01-10 00:00:00 -02' AS A,
> ------------------------ ------------------------ ------------------------ ------------------------
> 2000-01-10 00:00:00.0-02 2000-01-10 00:00:00.0+02 2000-01-10 00:00:00.0+02 2000-01-10 00:00:00.0+09
> rows: 1
CREATE TABLE TEST(T1 TIMESTAMP WITH TIME ZONE, T2 TIMESTAMP(0) WITH TIME ZONE, T3 TIMESTAMP(9) WITH TIME ZONE);
> ok
SELECT COLUMN_NAME, DATA_TYPE, TYPE_NAME, COLUMN_TYPE, NUMERIC_SCALE FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION;
> COLUMN_NAME DATA_TYPE TYPE_NAME COLUMN_TYPE NUMERIC_SCALE
> ----------- --------- ------------------------ --------------------------- -------------
> T1 2014 TIMESTAMP WITH TIME ZONE TIMESTAMP WITH TIME ZONE 6
> T2 2014 TIMESTAMP WITH TIME ZONE TIMESTAMP(0) WITH TIME ZONE 0
> T3 2014 TIMESTAMP WITH TIME ZONE TIMESTAMP(9) WITH TIME ZONE 9
> rows (ordered): 3
ALTER TABLE TEST ADD T4 TIMESTAMP (10) WITH TIME ZONE;
> exception
DROP TABLE TEST;
> ok
......@@ -15,19 +15,26 @@ SELECT T1, T2, T1 = T2 FROM TEST;
> 2010-01-01 10:00:00.0 2010-01-01 10:00:00.0 TRUE
> rows: 1
SELECT COLUMN_NAME, DATA_TYPE, TYPE_NAME, COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS
ALTER TABLE TEST ADD (T3 TIMESTAMP(0), T4 TIMESTAMP(9) WITHOUT TIME ZONE);
SELECT COLUMN_NAME, DATA_TYPE, TYPE_NAME, COLUMN_TYPE, NUMERIC_SCALE FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION;
> COLUMN_NAME DATA_TYPE TYPE_NAME COLUMN_TYPE
> ----------- --------- --------- ---------------------------
> T1 93 TIMESTAMP TIMESTAMP
> T2 93 TIMESTAMP TIMESTAMP WITHOUT TIME ZONE
> rows (ordered): 2
> COLUMN_NAME DATA_TYPE TYPE_NAME COLUMN_TYPE NUMERIC_SCALE
> ----------- --------- --------- ------------------------------ -------------
> T1 93 TIMESTAMP TIMESTAMP 6
> T2 93 TIMESTAMP TIMESTAMP WITHOUT TIME ZONE 6
> T3 93 TIMESTAMP TIMESTAMP(0) 0
> T4 93 TIMESTAMP TIMESTAMP(9) WITHOUT TIME ZONE 9
> rows (ordered): 4
ALTER TABLE TEST ADD T5 TIMESTAMP(10);
> exception
DROP TABLE TEST;
> ok
-- Check that TIMESTAMP is allowed as a column name
CREATE TABLE TEST(TIMESTAMP TIMESTAMP);
CREATE TABLE TEST(TIMESTAMP TIMESTAMP(0));
> ok
INSERT INTO TEST VALUES (TIMESTAMP '1999-12-31 08:00:00');
......
......@@ -6884,7 +6884,7 @@ DROP TABLE TEST;
> ok
--- data types (date and time) ----------------------------------------------------------------------------------------------
CREATE MEMORY TABLE TEST(ID INT, XT TIME, XD DATE, XTS TIMESTAMP);
CREATE MEMORY TABLE TEST(ID INT, XT TIME, XD DATE, XTS TIMESTAMP(9));
> ok
INSERT INTO TEST VALUES(0, '0:0:0','1-2-3','2-3-4 0:0:0');
......@@ -6941,7 +6941,7 @@ SCRIPT SIMPLE NOPASSWORDS NOSETTINGS;
> SCRIPT
> ----------------------------------------------------------------------------------------------------------------------------------
> -- 4 +/- SELECT COUNT(*) FROM PUBLIC.TEST;
> CREATE MEMORY TABLE PUBLIC.TEST( ID INT, XT TIME, XD DATE, XTS TIMESTAMP );
> CREATE MEMORY TABLE PUBLIC.TEST( ID INT, XT TIME, XD DATE, XTS TIMESTAMP(9) );
> CREATE USER IF NOT EXISTS SA PASSWORD '' ADMIN;
> INSERT INTO PUBLIC.TEST(ID, XT, XD, XTS) VALUES(0, TIME '00:00:00', DATE '0001-02-03', TIMESTAMP '0002-03-04 00:00:00.0');
> INSERT INTO PUBLIC.TEST(ID, XT, XD, XTS) VALUES(1, TIME '01:02:03', DATE '0004-05-06', TIMESTAMP '0007-08-09 00:01:02.0');
......
......@@ -259,7 +259,7 @@ public class TestDate extends TestBase {
t1.hashCode());
assertEquals(t1.getString().length(), t1.getDisplaySize());
assertEquals(ValueTimestamp.PRECISION, t1.getPrecision());
assertEquals(10, t1.getScale());
assertEquals(9, t1.getScale());
assertEquals("java.sql.Timestamp", t1.getObject().getClass().getName());
ValueTimestamp t1b = ValueTimestamp.parse("2001-01-01 01:01:01.111");
assertTrue(t1 == t1b);
......
......@@ -52,7 +52,7 @@ public class TestTimeStampWithTimeZone extends TestBase {
private void test1() throws SQLException {
Connection conn = getConnection(getTestName());
Statement stat = conn.createStatement();
stat.execute("create table test(id identity, t1 timestamp with time zone)");
stat.execute("create table test(id identity, t1 timestamp(9) with time zone)");
stat.execute("insert into test(t1) values('1970-01-01 12:00:00.00+00:15')");
// verify NanosSinceMidnight is in local time and not UTC
stat.execute("insert into test(t1) values('2016-09-24 00:00:00.000000001+00:01')");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论