Unverified 提交 9ba3f6ea authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1382 from katzyn/datetime

Parse more date-time literals for compatibility with other databases
......@@ -17,6 +17,7 @@ import java.util.HashMap;
import java.util.Map;
import org.h2.engine.Constants;
import org.h2.util.StringUtils;
/**
* Utility methods
......@@ -931,9 +932,8 @@ public final class DataUtils {
if (m != null && m.endsWith("]")) {
int dash = m.lastIndexOf('/');
if (dash >= 0) {
String s = m.substring(dash + 1, m.length() - 1);
try {
return Integer.parseInt(s);
return StringUtils.parseUInt31(m, dash + 1, m.length() - 1);
} catch (NumberFormatException e) {
// no error code
}
......
......@@ -33,6 +33,7 @@ import org.h2.store.InDoubtTransaction;
import org.h2.store.fs.FileChannelInputStream;
import org.h2.store.fs.FileUtils;
import org.h2.table.TableBase;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
/**
......@@ -267,7 +268,7 @@ public class MVTableEngine implements TableEngine {
if (mapName.startsWith("temp.")) {
mvStore.removeMap(mapName);
} else if (mapName.startsWith("table.") || mapName.startsWith("index.")) {
int id = Integer.parseInt(mapName.substring(1 + mapName.indexOf('.')));
int id = StringUtils.parseUInt31(mapName, mapName.indexOf('.') + 1, mapName.length());
if (!objectIds.get(id)) {
mvStore.removeMap(mapName);
}
......
......@@ -19,6 +19,7 @@ import org.h2.mvstore.MVStore;
import org.h2.mvstore.WriteBuffer;
import org.h2.mvstore.type.DataType;
import org.h2.mvstore.type.ObjectDataType;
import org.h2.util.StringUtils;
/**
* A store that supports concurrent MVCC read-committed transactions.
......@@ -153,7 +154,8 @@ public class TransactionStore {
if (mapName.length() > UNDO_LOG_NAME_PREFIX.length()) {
boolean committed = mapName.charAt(UNDO_LOG_NAME_PREFIX.length()) == UNDO_LOG_COMMITTED;
if (store.hasData(mapName) || committed) {
int transactionId = Integer.parseInt(mapName.substring(UNDO_LOG_NAME_PREFIX.length() + 1));
int transactionId = StringUtils.parseUInt31(mapName, UNDO_LOG_NAME_PREFIX.length() + 1,
mapName.length());
VersionedBitSet openTxBitSet = openTransactions.get();
if (!openTxBitSet.get(transactionId)) {
Object[] data = preparedTransactions.get(transactionId);
......
......@@ -336,6 +336,7 @@ public class DateTimeUtils {
/**
* Parse a date string. The format is: [+|-]year-month-day
* or [+|-]yyyyMMdd.
*
* @param s the string to parse
* @param start the parse index start
......@@ -349,14 +350,28 @@ public class DateTimeUtils {
start++;
}
// start at position 1 to support "-year"
int s1 = s.indexOf('-', start + 1);
int s2 = s.indexOf('-', s1 + 1);
if (s1 <= 0 || s2 <= s1) {
int yEnd = s.indexOf('-', start + 1);
int mStart, mEnd, dStart;
if (yEnd > 0) {
// Standard [+|-]year-month-day format
mStart = yEnd + 1;
mEnd = s.indexOf('-', mStart);
if (mEnd <= mStart) {
throw new IllegalArgumentException(s);
}
int year = Integer.parseInt(s.substring(start, s1));
int month = Integer.parseInt(s.substring(s1 + 1, s2));
int day = Integer.parseInt(s.substring(s2 + 1, end));
dStart = mEnd + 1;
} else {
// Additional [+|-]yyyyMMdd format for compatibility
mEnd = dStart = end - 2;
yEnd = mStart = mEnd - 2;
// Accept only 3 or more digits in year for now
if (yEnd < start + 3) {
throw new IllegalArgumentException(s);
}
}
int year = Integer.parseInt(s.substring(start, yEnd));
int month = StringUtils.parseUInt31(s, mStart, mEnd);
int day = StringUtils.parseUInt31(s, dStart, end);
if (!isValidDate(year, month, day)) {
throw new IllegalArgumentException(year + "-" + month + "-" + day);
}
......@@ -364,8 +379,8 @@ public class DateTimeUtils {
}
/**
* Parse a time string. The format is: hour:minute:second[.nanos] or
* alternatively hour.minute.second[.nanos].
* Parse a time string. The format is: hour:minute[:second[.nanos]],
* hhmm[ss[.nanos]], or hour.minute.second[.nanos].
*
* @param s the string to parse
* @param start the parse index start
......@@ -375,39 +390,93 @@ public class DateTimeUtils {
*/
public static long parseTimeNanos(String s, int start, int end) {
int hour, minute, second, nanos;
int s1 = s.indexOf(':', start);
int s2 = s.indexOf(':', s1 + 1);
int s3 = s.indexOf('.', s2 + 1);
if (s1 <= 0 || s2 <= s1) {
// if first try fails try to use IBM DB2 time format
// [-]hour.minute.second[.nanos]
s1 = s.indexOf('.', start);
s2 = s.indexOf('.', s1 + 1);
s3 = s.indexOf('.', s2 + 1);
if (s1 <= 0 || s2 <= s1) {
int hEnd = s.indexOf(':', start);
int mStart, mEnd, sStart, sEnd;
if (hEnd > 0) {
mStart = hEnd + 1;
mEnd = s.indexOf(':', mStart);
if (mEnd >= mStart) {
// Standard hour:minute:second[.nanos] format
sStart = mEnd + 1;
sEnd = s.indexOf('.', sStart);
} else {
// Additional hour:minute format for compatibility
mEnd = end;
sStart = sEnd = -1;
}
} else {
int t = s.indexOf('.', start);
if (t < 0) {
// Additional hhmm[ss] format for compatibility
hEnd = mStart = start + 2;
mEnd = mStart + 2;
int len = end - start;
if (len == 6) {
sStart = mEnd;
sEnd = -1;
} else if (len == 4) {
sStart = sEnd = -1;
} else {
throw new IllegalArgumentException(s);
}
} else if (t >= start + 6) {
// Additional hhmmss.nanos format for compatibility
if (t - start != 6) {
throw new IllegalArgumentException(s);
}
hEnd = mStart = start + 2;
mEnd = sStart = mStart + 2;
sEnd = t;
} else {
// Additional hour.minute.second[.nanos] IBM DB2 time format
hEnd = t;
mStart = hEnd + 1;
mEnd = s.indexOf('.', mStart);
if (mEnd <= mStart) {
throw new IllegalArgumentException(s);
}
sStart = mEnd + 1;
sEnd = s.indexOf('.', sStart);
}
hour = Integer.parseInt(s.substring(start, s1));
if (hour < 0 || hour == 0 && s.charAt(0) == '-' || hour >= 24) {
}
hour = StringUtils.parseUInt31(s, start, hEnd);
if (hour >= 24) {
throw new IllegalArgumentException(s);
}
minute = Integer.parseInt(s.substring(s1 + 1, s2));
if (s3 < 0) {
second = Integer.parseInt(s.substring(s2 + 1, end));
minute = StringUtils.parseUInt31(s, mStart, mEnd);
if (sStart > 0) {
if (sEnd < 0) {
second = StringUtils.parseUInt31(s, sStart, end);
nanos = 0;
} else {
second = Integer.parseInt(s.substring(s2 + 1, s3));
nanos = parseNanos(s, s3 + 1, end);
second = StringUtils.parseUInt31(s, sStart, sEnd);
nanos = parseNanos(s, sEnd + 1, end);
}
} else {
second = nanos = 0;
}
if (minute < 0 || minute >= 60 || second < 0 || second >= 60) {
if (minute >= 60 || second >= 60) {
throw new IllegalArgumentException(s);
}
return ((((hour * 60L) + minute) * 60) + second) * NANOS_PER_SECOND + nanos;
}
private static int parseNanos(String s, int start, int end) {
return Integer.parseInt((s.substring(start, end) + "000000000").substring(0, 9));
if (start >= end) {
throw new IllegalArgumentException(s);
}
int nanos = 0, mul = 100_000_000;
do {
char c = s.charAt(start);
if (c < '0' || c > '9') {
throw new IllegalArgumentException(s);
}
nanos += mul * (c - '0');
// mul can become 0, but continue loop anyway to ensure that all
// remaining digits are valid
mul /= 10;
} while (++start < end);
return nanos;
}
/**
......@@ -1787,8 +1856,8 @@ public class DateTimeUtils {
}
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) {
int v = StringUtils.parseUInt31(s, start, end);
if (v > max) {
throw new IllegalArgumentException(s);
}
return v;
......@@ -1798,13 +1867,13 @@ public class DateTimeUtils {
int seconds, nanos;
int dot = s.indexOf('.', start + 1);
if (dot < 0) {
seconds = Integer.parseInt(s.substring(start));
seconds = StringUtils.parseUInt31(s, start, s.length());
nanos = 0;
} else {
seconds = Integer.parseInt(s.substring(start, dot));
seconds = StringUtils.parseUInt31(s, start, dot);
nanos = parseNanos(s, dot + 1, s.length());
}
if (seconds < 0 || seconds > 59) {
if (seconds > 59) {
throw new IllegalArgumentException(s);
}
return seconds * NANOS_PER_SECOND + nanos;
......
......@@ -921,6 +921,38 @@ public class StringUtils {
softCache = null;
}
/**
* Parses an unsigned 31-bit integer. Neither - nor + signs are allowed.
*
* @param s string to parse
* @param start the beginning index, inclusive
* @param end the ending index, exclusive
* @return the unsigned {@code int} not greater than {@link Integer#MAX_VALUE}.
*/
public static int parseUInt31(String s, int start, int end) {
if (end > s.length() || start < 0 || start > end) {
throw new IndexOutOfBoundsException();
}
if (start == end) {
throw new NumberFormatException("");
}
int result = 0;
for (int i = start; i < end; i++) {
char ch = s.charAt(i);
// Ensure that character is valid and that multiplication by 10 will
// be performed without overflow
if (ch < '0' || ch > '9' || result > 214_748_364) {
throw new NumberFormatException(s.substring(start, end));
}
result = result * 10 + ch - '0';
if (result < 0) {
// Overflow
throw new NumberFormatException(s.substring(start, end));
}
}
return result;
}
/**
* Convert a hex encoded string to a byte array.
*
......
......@@ -15,3 +15,19 @@ SELECT COLUMN_NAME, DATA_TYPE, TYPE_NAME, COLUMN_TYPE, NUMERIC_SCALE, DATETIME_P
DROP TABLE TEST;
> ok
SELECT DATE '2000-01-02';
>> 2000-01-02
SELECT DATE '20000102';
>> 2000-01-02
SELECT DATE '-1000102';
>> -100-01-02
SELECT DATE '3001231';
>> 0300-12-31
-- PostgreSQL returns 2020-12-31
SELECT DATE '201231';
> exception INVALID_DATETIME_CONSTANT_2
......@@ -81,3 +81,33 @@ SELECT T0 FROM TEST;
DROP TABLE TEST;
> ok
SELECT TIME '11:22:33';
>> 11:22:33
SELECT TIME '11:22';
>> 11:22:00
SELECT TIME '112233';
>> 11:22:33
SELECT TIME '1122';
>> 11:22:00
SELECT TIME '12233';
> exception INVALID_DATETIME_CONSTANT_2
SELECT TIME '122';
> exception INVALID_DATETIME_CONSTANT_2
SELECT TIME '11:22:33.1';
>> 11:22:33.1
SELECT TIME '112233.1';
>> 11:22:33.1
SELECT TIME '12233.1';
> exception INVALID_DATETIME_CONSTANT_2
SELECT TIME '1122.1';
> exception INVALID_DATETIME_CONSTANT_2
......@@ -129,3 +129,27 @@ select * from test where d= timestamp '2006-01-01 12:00:00.000';
drop table test;
> ok
SELECT TIMESTAMP '2000-01-02 11:22:33';
>> 2000-01-02 11:22:33
SELECT TIMESTAMP '2000-01-02T11:22:33';
>> 2000-01-02 11:22:33
SELECT TIMESTAMP '20000102 11:22:33';
>> 2000-01-02 11:22:33
SELECT TIMESTAMP '20000102T11:22:33';
>> 2000-01-02 11:22:33
SELECT TIMESTAMP '2000-01-02 112233';
>> 2000-01-02 11:22:33
SELECT TIMESTAMP '2000-01-02T112233';
>> 2000-01-02 11:22:33
SELECT TIMESTAMP '20000102 112233';
>> 2000-01-02 11:22:33
SELECT TIMESTAMP '20000102T112233';
>> 2000-01-02 11:22:33
......@@ -32,6 +32,7 @@ public class TestStringUtils extends TestBase {
@Override
public void test() throws Exception {
testParseUInt31();
testHex();
testXML();
testSplit();
......@@ -43,6 +44,32 @@ public class TestStringUtils extends TestBase {
testTrimSubstring();
}
private void testParseUInt31() {
assertEquals(0, StringUtils.parseUInt31("101", 1, 2));
assertEquals(11, StringUtils.parseUInt31("11", 0, 2));
assertEquals(0, StringUtils.parseUInt31("000", 0, 3));
assertEquals(1, StringUtils.parseUInt31("01", 0, 2));
assertEquals(999999999, StringUtils.parseUInt31("X999999999", 1, 10));
assertEquals(2147483647, StringUtils.parseUInt31("2147483647", 0, 10));
testParseUInt31Bad(null, 0, 1);
testParseUInt31Bad("1", -1, 1);
testParseUInt31Bad("1", 0, 0);
testParseUInt31Bad("12", 1, 0);
testParseUInt31Bad("-0", 0, 2);
testParseUInt31Bad("+0", 0, 2);
testParseUInt31Bad("2147483648", 0, 10);
testParseUInt31Bad("21474836470", 0, 11);
}
private void testParseUInt31Bad(String s, int start, int end) {
try {
StringUtils.parseUInt31(s, start, end);
} catch (NullPointerException | IndexOutOfBoundsException | NumberFormatException e) {
return;
}
fail();
}
private void testHex() {
assertEquals("face",
StringUtils.convertBytesToHex(new byte[]
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论