提交 9e5d9644 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Merge branch 'master' into generated_keys

...@@ -2300,9 +2300,9 @@ Each table has a pseudo-column named ""_ROWID_"" that contains the unique row id ...@@ -2300,9 +2300,9 @@ Each table has a pseudo-column named ""_ROWID_"" that contains the unique row id
" "
"Other Grammar","Time"," "Other Grammar","Time","
TIME 'hh:mm:ss' TIME 'hh:mm:ss[.nnnnnnnnn]'
"," ","
A time literal. A value is between plus and minus 2 million hours A time literal. A value is between 0:00:00 and 23:59:59.999999999
and has nanosecond resolution. and has nanosecond resolution.
"," ","
TIME '23:59:59' TIME '23:59:59'
...@@ -2318,6 +2318,19 @@ minimum and maximum years are 0001 and 9999. ...@@ -2318,6 +2318,19 @@ minimum and maximum years are 0001 and 9999.
TIMESTAMP '2005-12-31 23:59:59' TIMESTAMP '2005-12-31 23:59:59'
" "
"Other Grammar","Timestamp with time zone","
TIMESTAMP 'yyyy-MM-dd hh:mm:ss[.nnnnnnnnn]
[Z | { - | + } timeZoneOffsetString | timeZoneNameString ]'
","
A timestamp with time zone literal.
If name of time zone is specified it will be converted to time zone offset.
","
TIMESTAMP WITH TIME ZONE '2005-12-31 23:59:59Z'
TIMESTAMP WITH TIME ZONE '2005-12-31 23:59:59-10:00'
TIMESTAMP WITH TIME ZONE '2005-12-31 23:59:59.123+05'
TIMESTAMP WITH TIME ZONE '2005-12-31 23:59:59.123456789 Europe/London'
"
"Other Grammar","Value"," "Other Grammar","Value","
string | dollarQuotedString | numeric | date | time | timestamp | boolean | bytes | array | null string | dollarQuotedString | numeric | date | time | timestamp | boolean | bytes | array | null
"," ","
...@@ -3671,7 +3684,7 @@ CURRENT_TIMESTAMP() ...@@ -3671,7 +3684,7 @@ CURRENT_TIMESTAMP()
Adds units to a date-time value. The string indicates the unit. Adds units to a date-time value. The string indicates the unit.
Use negative values to subtract units. Use negative values to subtract units.
addIntLong may be a long value when manipulating milliseconds, addIntLong may be a long value when manipulating milliseconds,
otherwise it's range is restricted to int. microseconds, or nanoseconds otherwise its range is restricted to int.
The same units as in the EXTRACT function are supported. The same units as in the EXTRACT function are supported.
This method returns a value with the same type as specified value if unit is compatible with this value. This method returns a value with the same type as specified value if unit is compatible with this value.
If specified unit is a HOUR, MINUTE, SECOND, MILLISECOND, etc and value is a DATE value DATEADD returns combined TIMESTAMP. If specified unit is a HOUR, MINUTE, SECOND, MILLISECOND, etc and value is a DATE value DATEADD returns combined TIMESTAMP.
...@@ -3727,11 +3740,13 @@ DAY_OF_YEAR(CREATED) ...@@ -3727,11 +3740,13 @@ DAY_OF_YEAR(CREATED)
"Functions (Time and Date)","EXTRACT"," "Functions (Time and Date)","EXTRACT","
EXTRACT ( { YEAR | YY | MONTH | MM | QUARTER | WEEK | ISO_WEEK EXTRACT ( { YEAR | YY | MONTH | MM | QUARTER | WEEK | ISO_WEEK
| DAY | DD | DAY_OF_YEAR | DOY | DAY | DD | DAY_OF_YEAR | DOY
| HOUR | HH | MINUTE | MI | SECOND | SS | MILLISECOND | MS } | HOUR | HH | MINUTE | MI | SECOND | SS | EPOCH
| MILLISECOND | MS | MICROSECOND | MCS | NANOSECOND | NS }
FROM timestamp ) FROM timestamp )
"," ","
Returns a specific value from a timestamps. Returns a specific value from a timestamps.
This method returns an int. This method returns a numeric value with EPOCH unit and
an int for all other time units.
"," ","
EXTRACT(SECOND FROM CURRENT_TIMESTAMP) EXTRACT(SECOND FROM CURRENT_TIMESTAMP)
" "
...@@ -3820,6 +3835,17 @@ This method uses the current system locale. ...@@ -3820,6 +3835,17 @@ This method uses the current system locale.
WEEK(CREATED) WEEK(CREATED)
" "
"Functions (Time and Date)","ISO_WEEK","
ISO_WEEK(timestamp)
","
Returns the week (1-53) from a timestamp.
This method uses the ISO definition when
first week of year should have at least four days
and week is started with Monday.
","
ISO_WEEK(CREATED)
"
"Functions (Time and Date)","YEAR"," "Functions (Time and Date)","YEAR","
YEAR(timestamp) YEAR(timestamp)
"," ","
...@@ -3828,6 +3854,14 @@ Returns the year from a timestamp. ...@@ -3828,6 +3854,14 @@ Returns the year from a timestamp.
YEAR(CREATED) YEAR(CREATED)
" "
"Functions (Time and Date)","ISO_YEAR","
ISO_YEAR(timestamp)
","
Returns the ISO week year from a timestamp.
","
ISO_YEAR(CREATED)
"
"Functions (System)","ARRAY_GET"," "Functions (System)","ARRAY_GET","
ARRAY_GET(arrayExpression, indexExpression) ARRAY_GET(arrayExpression, indexExpression)
"," ","
......
...@@ -10,7 +10,6 @@ import org.h2.engine.DbObject; ...@@ -10,7 +10,6 @@ import org.h2.engine.DbObject;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.expression.ExpressionVisitor; import org.h2.expression.ExpressionVisitor;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.schema.Schema; import org.h2.schema.Schema;
......
...@@ -224,7 +224,8 @@ class AggregateDataMedian extends AggregateData { ...@@ -224,7 +224,8 @@ class AggregateDataMedian extends AggregateData {
case Value.DOUBLE: case Value.DOUBLE:
return ValueDouble.get((v0.getFloat() + v1.getDouble()) / 2); return ValueDouble.get((v0.getFloat() + v1.getDouble()) / 2);
case Value.TIME: { case Value.TIME: {
return ValueTime.fromMillis((v0.getTime().getTime() + v1.getTime().getTime()) / 2); ValueTime t0 = (ValueTime) v0.convertTo(Value.TIME), t1 = (ValueTime) v1.convertTo(Value.TIME);
return ValueTime.fromNanos((t0.getNanos() + t1.getNanos()) / 2);
} }
case Value.DATE: { case Value.DATE: {
ValueDate d0 = (ValueDate) v0.convertTo(Value.DATE), d1 = (ValueDate) v1.convertTo(Value.DATE); ValueDate d0 = (ValueDate) v0.convertTo(Value.DATE), d1 = (ValueDate) v1.convertTo(Value.DATE);
......
...@@ -15,9 +15,8 @@ import java.nio.charset.StandardCharsets; ...@@ -15,9 +15,8 @@ import java.nio.charset.StandardCharsets;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.text.SimpleDateFormat; import java.text.DateFormatSymbols;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
...@@ -99,7 +98,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -99,7 +98,7 @@ public class Function extends Expression implements FunctionCall {
XMLATTR = 83, XMLNODE = 84, XMLCOMMENT = 85, XMLCDATA = 86, XMLATTR = 83, XMLNODE = 84, XMLCOMMENT = 85, XMLCDATA = 86,
XMLSTARTDOC = 87, XMLTEXT = 88, REGEXP_REPLACE = 89, RPAD = 90, XMLSTARTDOC = 87, XMLTEXT = 88, REGEXP_REPLACE = 89, RPAD = 90,
LPAD = 91, CONCAT_WS = 92, TO_CHAR = 93, TRANSLATE = 94, ORA_HASH = 95, LPAD = 91, CONCAT_WS = 92, TO_CHAR = 93, TRANSLATE = 94, ORA_HASH = 95,
TO_DATE = 96, TO_TIMESTAMP = 97, ADD_MONTHS = 98; TO_DATE = 96, TO_TIMESTAMP = 97, ADD_MONTHS = 98, TO_TIMESTAMP_TZ = 99;
public static final int CURDATE = 100, CURTIME = 101, DATE_ADD = 102, public static final int CURDATE = 100, CURTIME = 101, DATE_ADD = 102,
DATE_DIFF = 103, DAY_NAME = 104, DAY_OF_MONTH = 105, DATE_DIFF = 103, DAY_NAME = 104, DAY_OF_MONTH = 105,
...@@ -111,14 +110,9 @@ public class Function extends Expression implements FunctionCall { ...@@ -111,14 +110,9 @@ public class Function extends Expression implements FunctionCall {
ISO_WEEK = 124, ISO_DAY_OF_WEEK = 125; ISO_WEEK = 124, ISO_DAY_OF_WEEK = 125;
/** /**
* Pseudo function for {@code EXTRACT(MILLISECOND FROM ...)}. * Pseudo functions for DATEADD, DATEDIFF, and EXTRACT.
*/ */
public static final int MILLISECOND = 126; public static final int MILLISECOND = 126, EPOCH = 127, MICROSECOND = 128, NANOSECOND = 129;
/**
* Pseudo function for {@code EXTRACT(EPOCH FROM ...)}.
*/
public static final int EPOCH = 127;
public static final int DATABASE = 150, USER = 151, CURRENT_USER = 152, public static final int DATABASE = 150, USER = 151, CURRENT_USER = 152,
IDENTITY = 153, SCOPE_IDENTITY = 154, AUTOCOMMIT = 155, IDENTITY = 153, SCOPE_IDENTITY = 154, AUTOCOMMIT = 155,
...@@ -159,6 +153,11 @@ public class Function extends Expression implements FunctionCall { ...@@ -159,6 +153,11 @@ public class Function extends Expression implements FunctionCall {
private static final HashMap<String, Integer> DATE_PART = new HashMap<>(); private static final HashMap<String, Integer> DATE_PART = new HashMap<>();
private static final char[] SOUNDEX_INDEX = new char[128]; private static final char[] SOUNDEX_INDEX = new char[128];
/**
* English names of months and week days.
*/
private static volatile String[][] MONTHS_AND_WEEKS;
protected Expression[] args; protected Expression[] args;
private final FunctionInfo info; private final FunctionInfo info;
...@@ -206,6 +205,10 @@ public class Function extends Expression implements FunctionCall { ...@@ -206,6 +205,10 @@ public class Function extends Expression implements FunctionCall {
DATE_PART.put("MILLISECOND", MILLISECOND); DATE_PART.put("MILLISECOND", MILLISECOND);
DATE_PART.put("MS", MILLISECOND); DATE_PART.put("MS", MILLISECOND);
DATE_PART.put("EPOCH", EPOCH); DATE_PART.put("EPOCH", EPOCH);
DATE_PART.put("MICROSECOND", MICROSECOND);
DATE_PART.put("MCS", MICROSECOND);
DATE_PART.put("NANOSECOND", NANOSECOND);
DATE_PART.put("NS", NANOSECOND);
// SOUNDEX_INDEX // SOUNDEX_INDEX
String index = "7AEIOUY8HW1BFPV2CGJKQSXZ3DT4L5MN6R"; String index = "7AEIOUY8HW1BFPV2CGJKQSXZ3DT4L5MN6R";
...@@ -337,6 +340,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -337,6 +340,7 @@ public class Function extends Expression implements FunctionCall {
addFunction("TO_DATE", TO_DATE, VAR_ARGS, Value.TIMESTAMP); addFunction("TO_DATE", TO_DATE, VAR_ARGS, Value.TIMESTAMP);
addFunction("TO_TIMESTAMP", TO_TIMESTAMP, VAR_ARGS, Value.TIMESTAMP); addFunction("TO_TIMESTAMP", TO_TIMESTAMP, VAR_ARGS, Value.TIMESTAMP);
addFunction("ADD_MONTHS", ADD_MONTHS, 2, Value.TIMESTAMP); addFunction("ADD_MONTHS", ADD_MONTHS, 2, Value.TIMESTAMP);
addFunction("TO_TIMESTAMP_TZ", TO_TIMESTAMP_TZ, VAR_ARGS, Value.TIMESTAMP_TZ);
// alias for MSSQLServer // alias for MSSQLServer
addFunctionNotDeterministic("GETDATE", CURDATE, addFunctionNotDeterministic("GETDATE", CURDATE,
0, Value.DATE); 0, Value.DATE);
...@@ -845,9 +849,8 @@ public class Function extends Expression implements FunctionCall { ...@@ -845,9 +849,8 @@ public class Function extends Expression implements FunctionCall {
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
break; break;
case DAY_NAME: { case DAY_NAME: {
SimpleDateFormat dayName = new SimpleDateFormat( int dayOfWeek = DateTimeUtils.getSundayDayOfWeek(DateTimeUtils.dateAndTimeFromValue(v0)[0]);
"EEEE", Locale.ENGLISH); result = ValueString.get(getMonthsAndWeeks(1)[dayOfWeek],
result = ValueString.get(dayName.format(v0.getDate()),
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
break; break;
} }
...@@ -864,12 +867,11 @@ public class Function extends Expression implements FunctionCall { ...@@ -864,12 +867,11 @@ public class Function extends Expression implements FunctionCall {
case SECOND: case SECOND:
case WEEK: case WEEK:
case YEAR: case YEAR:
result = ValueInt.get(getDatePart(v0, info.type)); result = ValueInt.get(getIntDatePart(v0, info.type));
break; break;
case MONTH_NAME: { case MONTH_NAME: {
SimpleDateFormat monthName = new SimpleDateFormat("MMMM", int month = DateTimeUtils.monthFromDateValue(DateTimeUtils.dateAndTimeFromValue(v0)[0]);
Locale.ENGLISH); result = ValueString.get(getMonthsAndWeeks(0)[month - 1],
result = ValueString.get(monthName.format(v0.getDate()),
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
break; break;
} }
...@@ -1234,32 +1236,16 @@ public class Function extends Expression implements FunctionCall { ...@@ -1234,32 +1236,16 @@ public class Function extends Expression implements FunctionCall {
} }
case TRUNCATE: { case TRUNCATE: {
if (v0.getType() == Value.TIMESTAMP) { if (v0.getType() == Value.TIMESTAMP) {
java.sql.Timestamp d = v0.getTimestamp(); result = ValueTimestamp.fromDateValueAndNanos(((ValueTimestamp) v0).getDateValue(), 0);
Calendar c = DateTimeUtils.createGregorianCalendar();
c.setTime(d);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
result = ValueTimestamp.fromMillis(c.getTimeInMillis());
} else if (v0.getType() == Value.DATE) { } else if (v0.getType() == Value.DATE) {
ValueDate vd = (ValueDate) v0; result = ValueTimestamp.fromDateValueAndNanos(((ValueDate) v0).getDateValue(), 0);
Calendar c = DateTimeUtils.createGregorianCalendar(); } else if (v0.getType() == Value.TIMESTAMP_TZ) {
c.setTime(vd.getDate()); ValueTimestampTimeZone ts = (ValueTimestampTimeZone) v0;
c.set(Calendar.HOUR_OF_DAY, 0); result = ValueTimestampTimeZone.fromDateValueAndNanos(ts.getDateValue(), 0,
c.set(Calendar.MINUTE, 0); ts.getTimeZoneOffsetMins());
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
result = ValueTimestamp.fromMillis(c.getTimeInMillis());
} else if (v0.getType() == Value.STRING) { } else if (v0.getType() == Value.STRING) {
ValueString vd = (ValueString) v0; ValueTimestamp ts = ValueTimestamp.parse(v0.getString(), session.getDatabase().getMode());
Calendar c = DateTimeUtils.createGregorianCalendar(); result = ValueTimestamp.fromDateValueAndNanos(ts.getDateValue(), 0);
c.setTime(ValueTimestamp.parse(vd.getString(), session.getDatabase().getMode()).getDate());
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
result = ValueTimestamp.fromMillis(c.getTimeInMillis());
} else { } else {
double d = v0.getDouble(); double d = v0.getDouble();
int p = v1 == null ? 0 : v1.getInt(); int p = v1 == null ? 0 : v1.getInt();
...@@ -1450,7 +1436,8 @@ public class Function extends Expression implements FunctionCall { ...@@ -1450,7 +1436,8 @@ public class Function extends Expression implements FunctionCall {
case Value.TIME: case Value.TIME:
case Value.DATE: case Value.DATE:
case Value.TIMESTAMP: case Value.TIMESTAMP:
result = ValueString.get(ToChar.toChar(v0.getTimestamp(), case Value.TIMESTAMP_TZ:
result = ValueString.get(ToChar.toCharDateTime(v0,
v1 == null ? null : v1.getString(), v1 == null ? null : v1.getString(),
v2 == null ? null : v2.getString()), v2 == null ? null : v2.getString()),
database.getMode().treatEmptyStringsAsNull); database.getMode().treatEmptyStringsAsNull);
...@@ -1472,16 +1459,20 @@ public class Function extends Expression implements FunctionCall { ...@@ -1472,16 +1459,20 @@ public class Function extends Expression implements FunctionCall {
} }
break; break;
case TO_DATE: case TO_DATE:
result = ValueTimestamp.get(ToDateParser.toDate(v0.getString(), result = ToDateParser.toDate(v0.getString(),
v1 == null ? null : v1.getString())); v1 == null ? null : v1.getString());
break; break;
case TO_TIMESTAMP: case TO_TIMESTAMP:
result = ValueTimestamp.get(ToDateParser.toTimestamp(v0.getString(), result = ToDateParser.toTimestamp(v0.getString(),
v1 == null ? null : v1.getString())); v1 == null ? null : v1.getString());
break; break;
case ADD_MONTHS: case ADD_MONTHS:
result = dateadd("MONTH", v1.getInt(), v0); result = dateadd("MONTH", v1.getInt(), v0);
break; break;
case TO_TIMESTAMP_TZ:
result = ToDateParser.toTimestampTz(v0.getString(),
v1 == null ? null : v1.getString());
break;
case TRANSLATE: { case TRANSLATE: {
String matching = v1.getString(); String matching = v1.getString();
String replacement = v2.getString(); String replacement = v2.getString();
...@@ -1502,12 +1493,8 @@ public class Function extends Expression implements FunctionCall { ...@@ -1502,12 +1493,8 @@ public class Function extends Expression implements FunctionCall {
break; break;
case EXTRACT: { case EXTRACT: {
int field = getDatePart(v0.getString()); int field = getDatePart(v0.getString());
// Normal case when we don't retrieve the EPOCH time
if (field != EPOCH) { if (field != EPOCH) {
result = ValueInt.get(getIntDatePart(v1, field));
result = ValueInt.get(getDatePart(v1, field));
} else { } else {
// Case where we retrieve the EPOCH time. // Case where we retrieve the EPOCH time.
...@@ -1530,27 +1517,30 @@ public class Function extends Expression implements FunctionCall { ...@@ -1530,27 +1517,30 @@ public class Function extends Expression implements FunctionCall {
} else if (v1 instanceof ValueDate) { } else if (v1 instanceof ValueDate) {
// Case where the value is of type date '2000:01:01', we have to retrieve the total // Case where the value is of type date '2000:01:01', we have to retrieve the
// number of days and multiply it by the number of seconds in a day. // total number of days and multiply it by the number of seconds in a day.
result = ValueDecimal.get(numberOfDays.multiply(secondsPerDay)); result = ValueDecimal.get(numberOfDays.multiply(secondsPerDay));
} else if (v1 instanceof ValueTimestampTimeZone) { } else if (v1 instanceof ValueTimestampTimeZone) {
// Case where the value is a of type ValueTimestampTimeZone ('2000:01:01 10:00:00+05). // Case where the value is a of type ValueTimestampTimeZone
// We retrieve the time zone offset in minute // ('2000:01:01 10:00:00+05').
// We retrieve the time zone offset in minutes
ValueTimestampTimeZone v = (ValueTimestampTimeZone) v1; ValueTimestampTimeZone v = (ValueTimestampTimeZone) v1;
BigDecimal timeZoneOffsetSeconds = new BigDecimal(v.getTimeZoneOffsetMins() * 60); BigDecimal timeZoneOffsetSeconds = new BigDecimal(v.getTimeZoneOffsetMins() * 60);
// Sum the time in nanoseconds and the total number of days in seconds // Sum the time in nanoseconds and the total number of days in seconds
// and adding the timeZone offset in seconds. // and adding the timeZone offset in seconds.
result = ValueDecimal.get(timeNanosBigDecimal.divide(nanosSeconds) result = ValueDecimal.get(timeNanosBigDecimal.divide(nanosSeconds)
.add(numberOfDays.multiply(secondsPerDay)) .add(numberOfDays.multiply(secondsPerDay)).subtract(timeZoneOffsetSeconds));
.subtract(timeZoneOffsetSeconds));
} else { } else {
// By default, we have the date and the time ('2000:01:01 10:00:00) if no type is given. // By default, we have the date and the time ('2000:01:01 10:00:00') if no type
// We just have to sum the time in nanoseconds and the total number of days in seconds. // is given.
result = ValueDecimal.get(timeNanosBigDecimal.divide(nanosSeconds).add(numberOfDays.multiply(secondsPerDay))); // We just have to sum the time in nanoseconds and the total number of days in
// seconds.
result = ValueDecimal
.get(timeNanosBigDecimal.divide(nanosSeconds).add(numberOfDays.multiply(secondsPerDay)));
} }
} }
break; break;
...@@ -1866,8 +1856,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -1866,8 +1856,7 @@ public class Function extends Expression implements FunctionCall {
private static Value dateadd(String part, long count, Value v) { private static Value dateadd(String part, long count, Value v) {
int field = getDatePart(part); int field = getDatePart(part);
//v = v.convertTo(Value.TIMESTAMP); if (field != MILLISECOND && field != MICROSECOND && field != NANOSECOND &&
if (field != MILLISECOND &&
(count > Integer.MAX_VALUE || count < Integer.MIN_VALUE)) { (count > Integer.MAX_VALUE || count < Integer.MIN_VALUE)) {
throw DbException.getInvalidValueException("DATEADD count", count); throw DbException.getInvalidValueException("DATEADD count", count);
} }
...@@ -1917,11 +1906,17 @@ public class Function extends Expression implements FunctionCall { ...@@ -1917,11 +1906,17 @@ public class Function extends Expression implements FunctionCall {
count *= 60_000_000_000L; count *= 60_000_000_000L;
break; break;
case SECOND: case SECOND:
case EPOCH:
count *= 1_000_000_000; count *= 1_000_000_000;
break; break;
case MILLISECOND: case MILLISECOND:
count *= 1_000_000; count *= 1_000_000;
break; break;
case MICROSECOND:
count *= 1_000;
break;
case NANOSECOND:
break;
default: default:
throw DbException.getUnsupportedException("DATEADD " + part); throw DbException.getUnsupportedException("DATEADD " + part);
} }
...@@ -1966,17 +1961,27 @@ public class Function extends Expression implements FunctionCall { ...@@ -1966,17 +1961,27 @@ public class Function extends Expression implements FunctionCall {
long dateValue2 = a2[0]; long dateValue2 = a2[0];
long absolute2 = DateTimeUtils.absoluteDayFromDateValue(dateValue2); long absolute2 = DateTimeUtils.absoluteDayFromDateValue(dateValue2);
switch (field) { switch (field) {
case NANOSECOND:
case MICROSECOND:
case MILLISECOND: case MILLISECOND:
case SECOND: case SECOND:
case EPOCH:
case MINUTE: case MINUTE:
case HOUR: case HOUR:
long timeNanos1 = a1[1]; long timeNanos1 = a1[1];
long timeNanos2 = a2[1]; long timeNanos2 = a2[1];
switch (field) { switch (field) {
case NANOSECOND:
return (absolute2 - absolute1) * DateTimeUtils.NANOS_PER_DAY
+ (timeNanos2 - timeNanos1);
case MICROSECOND:
return (absolute2 - absolute1) * (DateTimeUtils.MILLIS_PER_DAY * 1_000)
+ (timeNanos2 / 1_000 - timeNanos1 / 1_000);
case MILLISECOND: case MILLISECOND:
return (absolute2 - absolute1) * DateTimeUtils.MILLIS_PER_DAY return (absolute2 - absolute1) * DateTimeUtils.MILLIS_PER_DAY
+ (timeNanos2 / 1_000_000 - timeNanos1 / 1_000_000); + (timeNanos2 / 1_000_000 - timeNanos1 / 1_000_000);
case SECOND: case SECOND:
case EPOCH:
return (absolute2 - absolute1) * 86_400 return (absolute2 - absolute1) * 86_400
+ (timeNanos2 / 1_000_000_000 - timeNanos1 / 1_000_000_000); + (timeNanos2 / 1_000_000_000 - timeNanos1 / 1_000_000_000);
case MINUTE: case MINUTE:
...@@ -2023,6 +2028,18 @@ public class Function extends Expression implements FunctionCall { ...@@ -2023,6 +2028,18 @@ public class Function extends Expression implements FunctionCall {
return r2 - r1; return r2 - r1;
} }
private static String[] getMonthsAndWeeks(int field) {
String[][] result = MONTHS_AND_WEEKS;
if (result == null) {
result = new String[2][];
DateFormatSymbols dfs = DateFormatSymbols.getInstance(Locale.ENGLISH);
result[0] = dfs.getMonths();
result[1] = dfs.getWeekdays();
MONTHS_AND_WEEKS = result;
}
return result[field];
}
private static String substring(String s, int start, int length) { private static String substring(String s, int start, int length) {
int len = s.length(); int len = s.length();
start--; start--;
...@@ -2310,6 +2327,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -2310,6 +2327,7 @@ public class Function extends Expression implements FunctionCall {
case XMLTEXT: case XMLTEXT:
case TRUNCATE: case TRUNCATE:
case TO_TIMESTAMP: case TO_TIMESTAMP:
case TO_TIMESTAMP_TZ:
min = 1; min = 1;
max = 2; max = 2;
break; break;
...@@ -2859,7 +2877,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -2859,7 +2877,7 @@ public class Function extends Expression implements FunctionCall {
* @param field the field type, see {@link Function} for constants * @param field the field type, see {@link Function} for constants
* @return the value * @return the value
*/ */
public static int getDatePart(Value date, int field) { public static int getIntDatePart(Value date, int field) {
long[] a = DateTimeUtils.dateAndTimeFromValue(date); long[] a = DateTimeUtils.dateAndTimeFromValue(date);
long dateValue = a[0]; long dateValue = a[0];
long timeNanos = a[1]; long timeNanos = a[1];
...@@ -2878,6 +2896,10 @@ public class Function extends Expression implements FunctionCall { ...@@ -2878,6 +2896,10 @@ public class Function extends Expression implements FunctionCall {
return (int) (timeNanos / 1_000_000_000 % 60); return (int) (timeNanos / 1_000_000_000 % 60);
case Function.MILLISECOND: case Function.MILLISECOND:
return (int) (timeNanos / 1_000_000 % 1_000); return (int) (timeNanos / 1_000_000 % 1_000);
case Function.MICROSECOND:
return (int) (timeNanos / 1_000 % 1_000_000);
case Function.NANOSECOND:
return (int) (timeNanos % 1_000_000_000);
case Function.DAY_OF_YEAR: case Function.DAY_OF_YEAR:
return DateTimeUtils.getDayOfYear(dateValue); return DateTimeUtils.getDayOfYear(dateValue);
case Function.DAY_OF_WEEK: case Function.DAY_OF_WEEK:
......
...@@ -148,6 +148,9 @@ public class IndexCursor implements Cursor { ...@@ -148,6 +148,9 @@ public class IndexCursor implements Cursor {
} }
} }
} }
if (inColumn != null) {
start = table.getTemplateRow();
}
} }
/** /**
...@@ -326,7 +329,6 @@ public class IndexCursor implements Cursor { ...@@ -326,7 +329,6 @@ public class IndexCursor implements Cursor {
while (inResult.next()) { while (inResult.next()) {
Value v = inResult.currentRow()[0]; Value v = inResult.currentRow()[0];
if (v != ValueNull.INSTANCE) { if (v != ValueNull.INSTANCE) {
v = inColumn.convert(v);
if (inResultTested == null) { if (inResultTested == null) {
inResultTested = new HashSet<>(); inResultTested = new HashSet<>();
} }
...@@ -342,9 +344,6 @@ public class IndexCursor implements Cursor { ...@@ -342,9 +344,6 @@ public class IndexCursor implements Cursor {
private void find(Value v) { private void find(Value v) {
v = inColumn.convert(v); v = inColumn.convert(v);
int id = inColumn.getColumnId(); int id = inColumn.getColumnId();
if (start == null) {
start = table.getTemplateRow();
}
start.setValue(id, v); start.setValue(id, v);
cursor = index.find(tableFilter, start, start); cursor = index.find(tableFilter, start, start);
} }
......
...@@ -2450,6 +2450,7 @@ public final class MVStore { ...@@ -2450,6 +2450,7 @@ public final class MVStore {
* needed. * needed.
*/ */
void writeInBackground() { void writeInBackground() {
try {
if (closed) { if (closed) {
return; return;
} }
...@@ -2470,7 +2471,6 @@ public final class MVStore { ...@@ -2470,7 +2471,6 @@ public final class MVStore {
} }
} }
if (autoCompactFillRate > 0) { if (autoCompactFillRate > 0) {
try {
// whether there were file read or write operations since // whether there were file read or write operations since
// the last time // the last time
boolean fileOps; boolean fileOps;
...@@ -2486,21 +2486,23 @@ public final class MVStore { ...@@ -2486,21 +2486,23 @@ public final class MVStore {
// in the bookkeeping? // in the bookkeeping?
compact(fillRate, autoCommitMemory); compact(fillRate, autoCommitMemory);
autoCompactLastFileOpCount = fileStore.getWriteCount() + fileStore.getReadCount(); autoCompactLastFileOpCount = fileStore.getWriteCount() + fileStore.getReadCount();
}
} catch (Throwable e) { } catch (Throwable e) {
handleException(e); handleException(e);
} }
} }
}
private void handleException(Throwable ex) { private void handleException(Throwable ex) {
if (backgroundExceptionHandler != null) { if (backgroundExceptionHandler != null) {
try { try {
backgroundExceptionHandler.uncaughtException(null, ex); backgroundExceptionHandler.uncaughtException(null, ex);
} catch(Throwable ignore) { } catch(Throwable ignore) {
if (ex != ignore) { // OOME may be the same
ex.addSuppressed(ignore); ex.addSuppressed(ignore);
} }
} }
} }
}
/** /**
* Set the read cache size in MB. * Set the read cache size in MB.
......
...@@ -171,7 +171,7 @@ public class DataReader extends Reader { ...@@ -171,7 +171,7 @@ public class DataReader extends Reader {
int i = 0; int i = 0;
try { try {
for (; i < len; i++) { for (; i < len; i++) {
buff[i] = readChar(); buff[off + i] = readChar();
} }
return len; return len;
} catch (EOFException e) { } catch (EOFException e) {
......
...@@ -224,7 +224,8 @@ public class ChangeFileEncryption extends Tool { ...@@ -224,7 +224,8 @@ public class ChangeFileEncryption extends Tool {
try (FileChannel fileIn = getFileChannel(fileName, "r", decryptKey)){ try (FileChannel fileIn = getFileChannel(fileName, "r", decryptKey)){
try(InputStream inStream = new FileChannelInputStream(fileIn, true)) { try(InputStream inStream = new FileChannelInputStream(fileIn, true)) {
FileUtils.delete(temp); FileUtils.delete(temp);
try (OutputStream outStream = new FileChannelOutputStream(getFileChannel(temp, "rw", encryptKey), true)) { try (OutputStream outStream = new FileChannelOutputStream(getFileChannel(temp, "rw", encryptKey),
true)) {
byte[] buffer = new byte[4 * 1024]; byte[] buffer = new byte[4 * 1024];
long remaining = fileIn.size(); long remaining = fileIn.size();
long total = remaining; long total = remaining;
......
...@@ -85,6 +85,11 @@ public class DateTimeUtils { ...@@ -85,6 +85,11 @@ public class DateTimeUtils {
private static final ThreadLocal<GregorianCalendar> CACHED_CALENDAR_NON_DEFAULT_TIMEZONE = private static final ThreadLocal<GregorianCalendar> CACHED_CALENDAR_NON_DEFAULT_TIMEZONE =
new ThreadLocal<>(); new ThreadLocal<>();
/**
* Cached local time zone.
*/
private static volatile TimeZone timeZone;
/** /**
* Observed JVM behaviour is that if the timezone of the host computer is * Observed JVM behaviour is that if the timezone of the host computer is
* changed while the JVM is running, the zone offset does not change but * changed while the JVM is running, the zone offset does not change but
...@@ -100,12 +105,26 @@ public class DateTimeUtils { ...@@ -100,12 +105,26 @@ public class DateTimeUtils {
// utility class // utility class
} }
/**
* Returns local time zone.
*
* @return local time zone
*/
private static TimeZone getTimeZone() {
TimeZone tz = timeZone;
if (tz == null) {
timeZone = tz = TimeZone.getDefault();
}
return tz;
}
/** /**
* Reset the cached calendar for default timezone, for example after * Reset the cached calendar for default timezone, for example after
* changing the default timezone. * changing the default timezone.
*/ */
public static void resetCalendar() { public static void resetCalendar() {
CACHED_CALENDAR.remove(); CACHED_CALENDAR.remove();
timeZone = null;
zoneOffsetMillis = DateTimeUtils.createGregorianCalendar().get(Calendar.ZONE_OFFSET); zoneOffsetMillis = DateTimeUtils.createGregorianCalendar().get(Calendar.ZONE_OFFSET);
} }
...@@ -628,20 +647,6 @@ public class DateTimeUtils { ...@@ -628,20 +647,6 @@ public class DateTimeUtils {
return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos); return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos);
} }
/**
* Get the year (positive or negative) from a calendar.
*
* @param calendar the calendar
* @return the year
*/
private static int getYear(Calendar calendar) {
int year = calendar.get(Calendar.YEAR);
if (calendar.get(Calendar.ERA) == GregorianCalendar.BC) {
year = 1 - year;
}
return year;
}
/** /**
* Get the number of milliseconds since 1970-01-01 in the local timezone, * Get the number of milliseconds since 1970-01-01 in the local timezone,
* but without daylight saving time into account. * but without daylight saving time into account.
...@@ -921,19 +926,6 @@ public class DateTimeUtils { ...@@ -921,19 +926,6 @@ public class DateTimeUtils {
return new Date(millis); return new Date(millis);
} }
/**
* Convert an encoded date value to millis, using the supplied timezone.
*
* @param tz the timezone
* @param dateValue the date value
* @return the date
*/
public static long convertDateValueToMillis(TimeZone tz, long dateValue) {
return getMillis(tz, yearFromDateValue(dateValue),
monthFromDateValue(dateValue), dayFromDateValue(dateValue), 0,
0, 0, 0);
}
/** /**
* Convert an encoded date-time value to millis, using the supplied timezone. * Convert an encoded date-time value to millis, using the supplied timezone.
* *
...@@ -1070,9 +1062,13 @@ public class DateTimeUtils { ...@@ -1070,9 +1062,13 @@ public class DateTimeUtils {
* @return the date value * @return the date value
*/ */
public static long dateValueFromDate(long ms) { public static long dateValueFromDate(long ms) {
Calendar cal = getCalendar(); ms += getTimeZone().getOffset(ms);
cal.setTimeInMillis(ms); long absoluteDay = ms / MILLIS_PER_DAY;
return dateValueFromCalendar(cal); // Round toward negative infinity
if (ms < 0 && (absoluteDay * MILLIS_PER_DAY != ms)) {
absoluteDay--;
}
return dateValueFromAbsoluteDay(absoluteDay);
} }
/** /**
...@@ -1082,10 +1078,12 @@ public class DateTimeUtils { ...@@ -1082,10 +1078,12 @@ public class DateTimeUtils {
* @return the date value * @return the date value
*/ */
private static long dateValueFromCalendar(Calendar cal) { private static long dateValueFromCalendar(Calendar cal) {
int year, month, day; int year = cal.get(Calendar.YEAR);
year = getYear(cal); if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
month = cal.get(Calendar.MONTH) + 1; year = 1 - year;
day = cal.get(Calendar.DAY_OF_MONTH); }
int month = cal.get(Calendar.MONTH) + 1;
int day = cal.get(Calendar.DAY_OF_MONTH);
return ((long) year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day; return ((long) year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day;
} }
...@@ -1097,9 +1095,13 @@ public class DateTimeUtils { ...@@ -1097,9 +1095,13 @@ public class DateTimeUtils {
* @return the nanoseconds * @return the nanoseconds
*/ */
public static long nanosFromDate(long ms) { public static long nanosFromDate(long ms) {
Calendar cal = getCalendar(); ms += getTimeZone().getOffset(ms);
cal.setTimeInMillis(ms); long absoluteDay = ms / MILLIS_PER_DAY;
return nanosFromCalendar(cal); // Round toward negative infinity
if (ms < 0 && (absoluteDay * MILLIS_PER_DAY != ms)) {
absoluteDay--;
}
return (ms - absoluteDay * MILLIS_PER_DAY) * 1_000_000;
} }
/** /**
...@@ -1154,7 +1156,7 @@ public class DateTimeUtils { ...@@ -1154,7 +1156,7 @@ public class DateTimeUtils {
m += 12; m += 12;
} }
long a = ((y * 2922L) >> 3) + DAYS_OFFSET[m - 3] + d - 719484; long a = ((y * 2922L) >> 3) + DAYS_OFFSET[m - 3] + d - 719484;
if (y <= 1582 && ((y < 1582) || (m * 100 + d < 1005))) { if (y <= 1582 && ((y < 1582) || (m * 100 + d < 1015))) {
// Julian calendar (cutover at 1582-10-04 / 1582-10-15) // Julian calendar (cutover at 1582-10-04 / 1582-10-15)
a += 13; a += 13;
} else if (y < 1901 || y > 2099) { } else if (y < 1901 || y > 2099) {
......
...@@ -6,20 +6,19 @@ ...@@ -6,20 +6,19 @@
package org.h2.util; package org.h2.util;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.Date; import java.text.DateFormatSymbols;
import java.sql.Timestamp;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols; import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Currency; import java.util.Currency;
import java.util.GregorianCalendar;
import java.util.Locale; import java.util.Locale;
import java.util.TimeZone; import java.util.TimeZone;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.value.Value;
import org.h2.value.ValueTimestampTimeZone;
/** /**
* Emulates Oracle's TO_CHAR function. * Emulates Oracle's TO_CHAR function.
...@@ -29,7 +28,7 @@ public class ToChar { ...@@ -29,7 +28,7 @@ public class ToChar {
/** /**
* The beginning of the Julian calendar. * The beginning of the Julian calendar.
*/ */
private static final long JULIAN_EPOCH; static final int JULIAN_EPOCH = -2_440_588;
private static final int[] ROMAN_VALUES = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, private static final int[] ROMAN_VALUES = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9,
5, 4, 1 }; 5, 4, 1 };
...@@ -37,14 +36,9 @@ public class ToChar { ...@@ -37,14 +36,9 @@ public class ToChar {
private static final String[] ROMAN_NUMERALS = { "M", "CM", "D", "CD", "C", "XC", private static final String[] ROMAN_NUMERALS = { "M", "CM", "D", "CD", "C", "XC",
"L", "XL", "X", "IX", "V", "IV", "I" }; "L", "XL", "X", "IX", "V", "IV", "I" };
static { static final int MONTHS = 0, SHORT_MONTHS = 1, WEEKDAYS = 2, SHORT_WEEKDAYS = 3, AM_PM = 4;
GregorianCalendar epoch = new GregorianCalendar(Locale.ENGLISH);
epoch.setGregorianChange(new Date(Long.MAX_VALUE)); private static volatile String[][] NAMES;
epoch.clear();
epoch.set(4713, Calendar.JANUARY, 1, 0, 0, 0);
epoch.set(Calendar.ERA, GregorianCalendar.BC);
JULIAN_EPOCH = epoch.getTimeInMillis();
}
private ToChar() { private ToChar() {
// utility class // utility class
...@@ -460,6 +454,66 @@ public class ToChar { ...@@ -460,6 +454,66 @@ public class ToChar {
return hex; return hex;
} }
static String[] getNames(int names) {
String[][] result = NAMES;
if (result == null) {
result = new String[5][];
DateFormatSymbols dfs = DateFormatSymbols.getInstance();
result[MONTHS] = dfs.getMonths();
String[] months = dfs.getShortMonths();
for (int i = 0; i < 12; i++) {
String month = months[i];
if (month.endsWith(".")) {
months[i] = month.substring(0, month.length() - 1);
}
}
result[SHORT_MONTHS] = months;
result[WEEKDAYS] = dfs.getWeekdays();
result[SHORT_WEEKDAYS] = dfs.getShortWeekdays();
result[AM_PM] = dfs.getAmPmStrings();
NAMES = result;
}
return result[names];
}
/**
* Returns time zone display name or ID for the specified date-time value.
*
* @param value
* value
* @param tzd
* if {@code true} return TZD (time zone region with Daylight Saving
* Time information included), if {@code false} return TZR (time zone
* region)
* @return time zone display name or ID
*/
private static String getTimeZone(Value value, boolean tzd) {
if (!(value instanceof ValueTimestampTimeZone)) {
TimeZone tz = TimeZone.getDefault();
if (tzd) {
boolean daylight = tz.inDaylightTime(value.getTimestamp());
return tz.getDisplayName(daylight, TimeZone.SHORT);
}
return tz.getID();
}
int offset = ((ValueTimestampTimeZone) value).getTimeZoneOffsetMins();
if (offset == 0) {
return "UTC";
}
StringBuilder b = new StringBuilder(9);
b.append("GMT");
if (offset < 0) {
b.append('-');
offset = - offset;
} else {
b.append('+');
}
StringUtils.appendZeroPadded(b, 2, offset / 60);
b.append(':');
StringUtils.appendZeroPadded(b, 2, offset % 60);
return b.toString();
}
/** /**
* Emulates Oracle's TO_CHAR(datetime) function. * Emulates Oracle's TO_CHAR(datetime) function.
* *
...@@ -592,19 +646,31 @@ public class ToChar { ...@@ -592,19 +646,31 @@ public class ToChar {
* See also TO_CHAR(datetime) and datetime format models * See also TO_CHAR(datetime) and datetime format models
* in the Oracle documentation. * in the Oracle documentation.
* *
* @param ts the timestamp to format * @param value the date-time value to format
* @param format the format pattern to use (if any) * @param format the format pattern to use (if any)
* @param nlsParam the NLS parameter (if any) * @param nlsParam the NLS parameter (if any)
* @return the formatted timestamp * @return the formatted timestamp
*/ */
public static String toChar(Timestamp ts, String format, @SuppressWarnings("unused") String nlsParam) { public static String toCharDateTime(Value value, String format, @SuppressWarnings("unused") String nlsParam) {
long[] a = DateTimeUtils.dateAndTimeFromValue(value);
long dateValue = a[0];
long timeNanos = a[1];
int year = DateTimeUtils.yearFromDateValue(dateValue);
int monthOfYear = DateTimeUtils.monthFromDateValue(dateValue);
int dayOfMonth = DateTimeUtils.dayFromDateValue(dateValue);
int posYear = Math.abs(year);
long second = timeNanos / 1_000_000_000;
int nanos = (int) (timeNanos - second * 1_000_000_000);
int minute = (int) (second / 60);
second -= minute * 60;
int hour = minute / 60;
minute -= hour * 60;
int h12 = (hour + 11) % 12 + 1;
boolean isAM = hour < 12;
if (format == null) { if (format == null) {
format = "DD-MON-YY HH.MI.SS.FF PM"; format = "DD-MON-YY HH.MI.SS.FF PM";
} }
GregorianCalendar cal = new GregorianCalendar(Locale.ENGLISH);
cal.setTimeInMillis(ts.getTime());
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
boolean fillMode = true; boolean fillMode = true;
...@@ -615,197 +681,213 @@ public class ToChar { ...@@ -615,197 +681,213 @@ public class ToChar {
// AD / BC // AD / BC
if ((cap = containsAt(format, i, "A.D.", "B.C.")) != null) { if ((cap = containsAt(format, i, "A.D.", "B.C.")) != null) {
String era = cal.get(Calendar.ERA) == GregorianCalendar.AD ? "A.D." : "B.C."; String era = year > 0 ? "A.D." : "B.C.";
output.append(cap.apply(era)); output.append(cap.apply(era));
i += 4; i += 4;
} else if ((cap = containsAt(format, i, "AD", "BC")) != null) { } else if ((cap = containsAt(format, i, "AD", "BC")) != null) {
String era = cal.get(Calendar.ERA) == GregorianCalendar.AD ? "AD" : "BC"; String era = year > 0 ? "AD" : "BC";
output.append(cap.apply(era)); output.append(cap.apply(era));
i += 2; i += 2;
// AM / PM // AM / PM
} else if ((cap = containsAt(format, i, "A.M.", "P.M.")) != null) { } else if ((cap = containsAt(format, i, "A.M.", "P.M.")) != null) {
String am = cal.get(Calendar.AM_PM) == Calendar.AM ? "A.M." : "P.M."; String am = isAM ? "A.M." : "P.M.";
output.append(cap.apply(am)); output.append(cap.apply(am));
i += 4; i += 4;
} else if ((cap = containsAt(format, i, "AM", "PM")) != null) { } else if ((cap = containsAt(format, i, "AM", "PM")) != null) {
String am = cal.get(Calendar.AM_PM) == Calendar.AM ? "AM" : "PM"; String am = isAM ? "AM" : "PM";
output.append(cap.apply(am)); output.append(cap.apply(am));
i += 2; i += 2;
// Long/short date/time format // Long/short date/time format
} else if ((cap = containsAt(format, i, "DL")) != null) { } else if (containsAt(format, i, "DL") != null) {
output.append(new SimpleDateFormat("EEEE, MMMM d, yyyy").format(ts)); String day = getNames(WEEKDAYS)[DateTimeUtils.getSundayDayOfWeek(dateValue)];
String month = getNames(MONTHS)[monthOfYear - 1];
output.append(day).append(", ").append(month).append(' ').append(dayOfMonth).append(", ");
StringUtils.appendZeroPadded(output, 4, posYear);
i += 2; i += 2;
} else if ((cap = containsAt(format, i, "DS")) != null) { } else if (containsAt(format, i, "DS") != null) {
output.append(new SimpleDateFormat("MM/dd/yyyy").format(ts)); StringUtils.appendZeroPadded(output, 2, monthOfYear);
output.append('/');
StringUtils.appendZeroPadded(output, 2, dayOfMonth);
output.append('/');
StringUtils.appendZeroPadded(output, 4, posYear);
i += 2; i += 2;
} else if ((cap = containsAt(format, i, "TS")) != null) { } else if (containsAt(format, i, "TS") != null) {
output.append(new SimpleDateFormat("h:mm:ss aa").format(ts)); output.append(h12).append(':');
StringUtils.appendZeroPadded(output, 2, minute);
output.append(':');
StringUtils.appendZeroPadded(output, 2, second);
output.append(' ');
output.append(getNames(AM_PM)[isAM ? 0 : 1]);
i += 2; i += 2;
// Day // Day
} else if ((cap = containsAt(format, i, "DDD")) != null) { } else if (containsAt(format, i, "DDD") != null) {
output.append(cal.get(Calendar.DAY_OF_YEAR)); output.append(DateTimeUtils.getDayOfYear(dateValue));
i += 3; i += 3;
} else if ((cap = containsAt(format, i, "DD")) != null) { } else if (containsAt(format, i, "DD") != null) {
output.append(String.format("%02d", StringUtils.appendZeroPadded(output, 2, dayOfMonth);
cal.get(Calendar.DAY_OF_MONTH)));
i += 2; i += 2;
} else if ((cap = containsAt(format, i, "DY")) != null) { } else if ((cap = containsAt(format, i, "DY")) != null) {
String day = new SimpleDateFormat("EEE").format(ts); String day = getNames(SHORT_WEEKDAYS)[DateTimeUtils.getSundayDayOfWeek(dateValue)];
output.append(cap.apply(day)); output.append(cap.apply(day));
i += 2; i += 2;
} else if ((cap = containsAt(format, i, "DAY")) != null) { } else if ((cap = containsAt(format, i, "DAY")) != null) {
String day = new SimpleDateFormat("EEEE").format(ts); String day = getNames(WEEKDAYS)[DateTimeUtils.getSundayDayOfWeek(dateValue)];
if (fillMode) { if (fillMode) {
day = StringUtils.pad(day, "Wednesday".length(), " ", true); day = StringUtils.pad(day, "Wednesday".length(), " ", true);
} }
output.append(cap.apply(day)); output.append(cap.apply(day));
i += 3; i += 3;
} else if ((cap = containsAt(format, i, "D")) != null) { } else if (containsAt(format, i, "D") != null) {
output.append(cal.get(Calendar.DAY_OF_WEEK)); output.append(DateTimeUtils.getSundayDayOfWeek(dateValue));
i += 1; i += 1;
} else if ((cap = containsAt(format, i, "J")) != null) { } else if (containsAt(format, i, "J") != null) {
long millis = ts.getTime() - JULIAN_EPOCH; output.append(DateTimeUtils.absoluteDayFromDateValue(dateValue) - JULIAN_EPOCH);
long days = (long) Math.floor(millis / (1000 * 60 * 60 * 24));
output.append(days);
i += 1; i += 1;
// Hours // Hours
} else if ((cap = containsAt(format, i, "HH24")) != null) { } else if (containsAt(format, i, "HH24") != null) {
output.append(new DecimalFormat("00").format(cal.get(Calendar.HOUR_OF_DAY))); StringUtils.appendZeroPadded(output, 2, hour);
i += 4; i += 4;
} else if ((cap = containsAt(format, i, "HH12")) != null) { } else if (containsAt(format, i, "HH12") != null) {
output.append(new DecimalFormat("00").format(cal.get(Calendar.HOUR))); StringUtils.appendZeroPadded(output, 2, h12);
i += 4; i += 4;
} else if ((cap = containsAt(format, i, "HH")) != null) { } else if (containsAt(format, i, "HH") != null) {
output.append(new DecimalFormat("00").format(cal.get(Calendar.HOUR))); StringUtils.appendZeroPadded(output, 2, h12);
i += 2; i += 2;
// Minutes // Minutes
} else if ((cap = containsAt(format, i, "MI")) != null) { } else if (containsAt(format, i, "MI") != null) {
output.append(new DecimalFormat("00").format(cal.get(Calendar.MINUTE))); StringUtils.appendZeroPadded(output, 2, minute);
i += 2; i += 2;
// Seconds // Seconds
} else if ((cap = containsAt(format, i, "SSSSS")) != null) { } else if (containsAt(format, i, "SSSSS") != null) {
int seconds = cal.get(Calendar.HOUR_OF_DAY) * 60 * 60; int seconds = (int) (timeNanos / 1_000_000_000);
seconds += cal.get(Calendar.MINUTE) * 60;
seconds += cal.get(Calendar.SECOND);
output.append(seconds); output.append(seconds);
i += 5; i += 5;
} else if ((cap = containsAt(format, i, "SS")) != null) { } else if (containsAt(format, i, "SS") != null) {
output.append(new DecimalFormat("00").format(cal.get(Calendar.SECOND))); StringUtils.appendZeroPadded(output, 2, second);
i += 2; i += 2;
// Fractional seconds // Fractional seconds
} else if ((cap = containsAt(format, i, "FF1", "FF2", } else if (containsAt(format, i, "FF1", "FF2",
"FF3", "FF4", "FF5", "FF6", "FF7", "FF8", "FF9")) != null) { "FF3", "FF4", "FF5", "FF6", "FF7", "FF8", "FF9") != null) {
int x = Integer.parseInt(format.substring(i + 2, i + 3)); int x = format.charAt(i + 2) - '0';
int ff = (int) (cal.get(Calendar.MILLISECOND) * Math.pow(10, x - 3)); int ff = (int) (nanos * Math.pow(10, x - 9));
output.append(ff); StringUtils.appendZeroPadded(output, x, ff);
i += 3; i += 3;
} else if ((cap = containsAt(format, i, "FF")) != null) { } else if (containsAt(format, i, "FF") != null) {
output.append(cal.get(Calendar.MILLISECOND) * 1000); StringUtils.appendZeroPadded(output, 9, nanos);
i += 2; i += 2;
// Time zone // Time zone
} else if ((cap = containsAt(format, i, "TZR")) != null) { } else if (containsAt(format, i, "TZR") != null) {
TimeZone tz = TimeZone.getDefault(); output.append(getTimeZone(value, false));
output.append(tz.getID());
i += 3; i += 3;
} else if ((cap = containsAt(format, i, "TZD")) != null) { } else if (containsAt(format, i, "TZD") != null) {
TimeZone tz = TimeZone.getDefault(); output.append(getTimeZone(value, true));
boolean daylight = tz.inDaylightTime(new java.util.Date());
output.append(tz.getDisplayName(daylight, TimeZone.SHORT));
i += 3; i += 3;
// Week // Week
} else if ((cap = containsAt(format, i, "IW", "WW")) != null) { } else if (containsAt(format, i, "IW", "WW") != null) {
output.append(cal.get(Calendar.WEEK_OF_YEAR)); output.append(DateTimeUtils.getWeekOfYear(dateValue, 0, 1));
i += 2; i += 2;
} else if ((cap = containsAt(format, i, "W")) != null) { } else if (containsAt(format, i, "W") != null) {
int w = (int) (1 + Math.floor(cal.get(Calendar.DAY_OF_MONTH) / 7)); int w = 1 + dayOfMonth / 7;
output.append(w); output.append(w);
i += 1; i += 1;
// Year // Year
} else if ((cap = containsAt(format, i, "Y,YYY")) != null) { } else if (containsAt(format, i, "Y,YYY") != null) {
output.append(new DecimalFormat("#,###").format(getYear(cal))); output.append(new DecimalFormat("#,###").format(posYear));
i += 5; i += 5;
} else if ((cap = containsAt(format, i, "SYYYY")) != null) { } else if (containsAt(format, i, "SYYYY") != null) {
if (cal.get(Calendar.ERA) == GregorianCalendar.BC) { // Should be <= 0, but Oracle prints negative years with off-by-one difference
if (year < 0) {
output.append('-'); output.append('-');
} }
output.append(new DecimalFormat("0000").format(getYear(cal))); StringUtils.appendZeroPadded(output, 4, posYear);
i += 5; i += 5;
} else if ((cap = containsAt(format, i, "YYYY", "IYYY", "RRRR")) != null) { } else if (containsAt(format, i, "YYYY", "RRRR") != null) {
output.append(new DecimalFormat("0000").format(getYear(cal))); StringUtils.appendZeroPadded(output, 4, posYear);
i += 4; i += 4;
} else if ((cap = containsAt(format, i, "YYY", "IYY")) != null) { } else if (containsAt(format, i, "IYYY") != null) {
output.append(new DecimalFormat("000").format(getYear(cal) % 1000)); StringUtils.appendZeroPadded(output, 4, Math.abs(DateTimeUtils.getIsoWeekYear(dateValue)));
i += 4;
} else if (containsAt(format, i, "YYY") != null) {
StringUtils.appendZeroPadded(output, 3, posYear % 1000);
i += 3;
} else if (containsAt(format, i, "IYY") != null) {
StringUtils.appendZeroPadded(output, 3, Math.abs(DateTimeUtils.getIsoWeekYear(dateValue)) % 1000);
i += 3; i += 3;
} else if ((cap = containsAt(format, i, "YY", "IY", "RR")) != null) { } else if (containsAt(format, i, "YY", "RR") != null) {
output.append(new DecimalFormat("00").format(getYear(cal) % 100)); StringUtils.appendZeroPadded(output, 2, posYear % 100);
i += 2; i += 2;
} else if ((cap = containsAt(format, i, "I", "Y")) != null) { } else if (containsAt(format, i, "IY") != null) {
output.append(getYear(cal) % 10); StringUtils.appendZeroPadded(output, 2, Math.abs(DateTimeUtils.getIsoWeekYear(dateValue)) % 100);
i += 2;
} else if (containsAt(format, i, "Y") != null) {
output.append(posYear % 10);
i += 1;
} else if (containsAt(format, i, "I") != null) {
output.append(Math.abs(DateTimeUtils.getIsoWeekYear(dateValue)) % 10);
i += 1; i += 1;
// Month / quarter // Month / quarter
} else if ((cap = containsAt(format, i, "MONTH")) != null) { } else if ((cap = containsAt(format, i, "MONTH")) != null) {
String month = new SimpleDateFormat("MMMM").format(ts); String month = getNames(MONTHS)[monthOfYear - 1];
if (fillMode) { if (fillMode) {
month = StringUtils.pad(month, "September".length(), " ", true); month = StringUtils.pad(month, "September".length(), " ", true);
} }
output.append(cap.apply(month)); output.append(cap.apply(month));
i += 5; i += 5;
} else if ((cap = containsAt(format, i, "MON")) != null) { } else if ((cap = containsAt(format, i, "MON")) != null) {
String month = new SimpleDateFormat("MMM").format(ts); String month = getNames(SHORT_MONTHS)[monthOfYear - 1];
output.append(cap.apply(month)); output.append(cap.apply(month));
i += 3; i += 3;
} else if ((cap = containsAt(format, i, "MM")) != null) { } else if (containsAt(format, i, "MM") != null) {
output.append(String.format("%02d", cal.get(Calendar.MONTH) + 1)); StringUtils.appendZeroPadded(output, 2, monthOfYear);
i += 2; i += 2;
} else if ((cap = containsAt(format, i, "RM")) != null) { } else if ((cap = containsAt(format, i, "RM")) != null) {
int month = cal.get(Calendar.MONTH) + 1; output.append(cap.apply(toRomanNumeral(monthOfYear)));
output.append(cap.apply(toRomanNumeral(month)));
i += 2; i += 2;
} else if ((cap = containsAt(format, i, "Q")) != null) { } else if (containsAt(format, i, "Q") != null) {
int q = (int) (1 + Math.floor(cal.get(Calendar.MONTH) / 3)); int q = 1 + ((monthOfYear - 1) / 3);
output.append(q); output.append(q);
i += 1; i += 1;
// Local radix character // Local radix character
} else if ((cap = containsAt(format, i, "X")) != null) { } else if (containsAt(format, i, "X") != null) {
char c = DecimalFormatSymbols.getInstance().getDecimalSeparator(); char c = DecimalFormatSymbols.getInstance().getDecimalSeparator();
output.append(c); output.append(c);
i += 1; i += 1;
// Format modifiers // Format modifiers
} else if ((cap = containsAt(format, i, "FM")) != null) { } else if (containsAt(format, i, "FM") != null) {
fillMode = !fillMode; fillMode = !fillMode;
i += 2; i += 2;
} else if ((cap = containsAt(format, i, "FX")) != null) { } else if (containsAt(format, i, "FX") != null) {
i += 2; i += 2;
// Literal text // Literal text
} else if ((cap = containsAt(format, i, "\"")) != null) { } else if (containsAt(format, i, "\"") != null) {
for (i = i + 1; i < format.length(); i++) { for (i = i + 1; i < format.length(); i++) {
char c = format.charAt(i); char c = format.charAt(i);
if (c != '"') { if (c != '"') {
...@@ -835,14 +917,6 @@ public class ToChar { ...@@ -835,14 +917,6 @@ public class ToChar {
return output.toString(); return output.toString();
} }
private static int getYear(Calendar cal) {
int year = cal.get(Calendar.YEAR);
if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
year--;
}
return year;
}
/** /**
* Returns a capitalization strategy if the specified string contains any of * Returns a capitalization strategy if the specified string contains any of
* the specified substrings at the specified index. The capitalization * the specified substrings at the specified index. The capitalization
......
...@@ -7,9 +7,13 @@ package org.h2.util; ...@@ -7,9 +7,13 @@ package org.h2.util;
import static java.lang.String.format; import static java.lang.String.format;
import java.sql.Timestamp;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List; import java.util.List;
import java.util.TimeZone;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;
/** /**
* Emulates Oracle's TO_DATE function.<br> * Emulates Oracle's TO_DATE function.<br>
...@@ -21,8 +25,29 @@ public class ToDateParser { ...@@ -21,8 +25,29 @@ public class ToDateParser {
private final ConfigParam functionName; private final ConfigParam functionName;
private String inputStr; private String inputStr;
private String formatStr; private String formatStr;
private final Calendar resultCalendar = DateTimeUtils.createGregorianCalendar();
private Integer nanos; private boolean doyValid = false, absoluteDayValid = false,
hour12Valid = false,
timeZoneHMValid = false;
private boolean bc;
private long absoluteDay;
private int year, month, day = 1;
private int dayOfYear;
private int hour, minute, second, nanos;
private int hour12;
boolean isAM = true;
private TimeZone timeZone;
private int timeZoneHour, timeZoneMinute;
private int currentYear, currentMonth;
/** /**
* @param input the input date with the date-time info * @param input the input date with the date-time info
...@@ -31,20 +56,6 @@ public class ToDateParser { ...@@ -31,20 +56,6 @@ public class ToDateParser {
* code) * code)
*/ */
private ToDateParser(ConfigParam functionName, String input, String format) { private ToDateParser(ConfigParam functionName, String input, String format) {
// reset calendar - default oracle behaviour
resultCalendar.set(Calendar.YEAR, 1970);
resultCalendar.set(Calendar.MONTH, DateTimeUtils.createGregorianCalendar().get(Calendar.MONTH));
resultCalendar.clear(Calendar.DAY_OF_YEAR);
resultCalendar.clear(Calendar.DAY_OF_WEEK);
resultCalendar.clear(Calendar.DAY_OF_WEEK_IN_MONTH);
resultCalendar.set(Calendar.DAY_OF_MONTH, 1);
resultCalendar.set(Calendar.HOUR, 0);
resultCalendar.set(Calendar.HOUR_OF_DAY, 0);
resultCalendar.set(Calendar.MINUTE, 0);
resultCalendar.set(Calendar.SECOND, 0);
resultCalendar.set(Calendar.MILLISECOND, 0);
resultCalendar.set(Calendar.AM_PM, Calendar.AM);
this.functionName = functionName; this.functionName = functionName;
inputStr = input.trim(); inputStr = input.trim();
// Keep a copy // Keep a copy
...@@ -59,30 +70,65 @@ public class ToDateParser { ...@@ -59,30 +70,65 @@ public class ToDateParser {
unmodifiedFormatStr = formatStr; unmodifiedFormatStr = formatStr;
} }
private static ToDateParser getDateParser(String input, String format) { private static ToDateParser getTimestampParser(ConfigParam param, String input, String format) {
ToDateParser result = new ToDateParser(ConfigParam.TO_DATE, input, format); ToDateParser result = new ToDateParser(param, input, format);
parse(result); parse(result);
return result; return result;
} }
private static ToDateParser getTimestampParser(String input, String format) { private ValueTimestamp getResultingValue() {
ToDateParser result = new ToDateParser(ConfigParam.TO_TIMESTAMP, input, format); long dateValue;
parse(result); if (absoluteDayValid) {
return result; dateValue = DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay);
} else {
int year = this.year;
if (year == 0) {
year = getCurrentYear();
} }
if (bc) {
private Timestamp getResultingTimestamp() { year = 1 - year;
Calendar cal = (Calendar) getResultCalendar().clone(); }
int nanosToSet = nanos == null ? if (doyValid) {
cal.get(Calendar.MILLISECOND) * 1000000 : nanos.intValue(); dateValue = DateTimeUtils.dateValueFromAbsoluteDay(
cal.set(Calendar.MILLISECOND, 0); DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.dateValue(year, 1, 1))
Timestamp ts = new Timestamp(cal.getTimeInMillis()); + dayOfYear - 1);
ts.setNanos(nanosToSet); } else {
return ts; int month = this.month;
if (month == 0) {
// Oracle uses current month as default
month = getCurrentMonth();
}
dateValue = DateTimeUtils.dateValue(year, month, day);
}
}
int hour;
if (hour12Valid) {
hour = hour12 % 12;
if (!isAM) {
hour += 12;
}
} else {
hour = this.hour;
}
long timeNanos = ((((hour * 60) + minute) * 60) + second) * 1_000_000_000L + nanos;
return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos);
} }
Calendar getResultCalendar() { private ValueTimestampTimeZone getResultingValueWithTimeZone() {
return resultCalendar; ValueTimestamp ts = getResultingValue();
long dateValue = ts.getDateValue();
short offset;
if (timeZoneHMValid) {
offset = (short) (timeZoneHour * 60 + ((timeZoneHour >= 0) ? timeZoneMinute : -timeZoneMinute));
} else {
TimeZone timeZone = this.timeZone;
if (timeZone == null) {
timeZone = TimeZone.getDefault();
}
long millis = DateTimeUtils.convertDateTimeValueToMillis(timeZone, dateValue, nanos / 1000000);
offset = (short) (timeZone.getOffset(millis) / 1000 / 60);
}
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, ts.getTimeNanos(), offset);
} }
String getInputStr() { String getInputStr() {
...@@ -97,10 +143,111 @@ public class ToDateParser { ...@@ -97,10 +143,111 @@ public class ToDateParser {
return functionName.name(); return functionName.name();
} }
private void queryCurrentYearAndMonth() {
GregorianCalendar gc = DateTimeUtils.getCalendar();
gc.setTimeInMillis(System.currentTimeMillis());
currentYear = gc.get(Calendar.YEAR);
currentMonth = gc.get(Calendar.MONTH) + 1;
}
int getCurrentYear() {
if (currentYear == 0) {
queryCurrentYearAndMonth();
}
return currentYear;
}
int getCurrentMonth() {
if (currentMonth == 0) {
queryCurrentYearAndMonth();
}
return currentMonth;
}
void setAbsoluteDay(int absoluteDay) {
doyValid = false;
absoluteDayValid = true;
this.absoluteDay = absoluteDay;
}
void setBC(boolean bc) {
doyValid = false;
absoluteDayValid = false;
this.bc = bc;
}
void setYear(int year) {
doyValid = false;
absoluteDayValid = false;
this.year = year;
}
void setMonth(int month) {
doyValid = false;
absoluteDayValid = false;
this.month = month;
if (year == 0) {
year = 1970;
}
}
void setDay(int day) {
doyValid = false;
absoluteDayValid = false;
this.day = day;
if (year == 0) {
year = 1970;
}
}
void setDayOfYear(int dayOfYear) {
doyValid = true;
absoluteDayValid = false;
this.dayOfYear = dayOfYear;
}
void setHour(int hour) {
hour12Valid = false;
this.hour = hour;
}
void setMinute(int minute) {
this.minute = minute;
}
void setSecord(int second) {
this.second = second;
}
void setNanos(int nanos) { void setNanos(int nanos) {
this.nanos = nanos; this.nanos = nanos;
} }
void setAmPm(boolean isAM) {
hour12Valid = true;
this.isAM = isAM;
}
void setHour12(int hour12) {
hour12Valid = true;
this.hour12 = hour12;
}
void setTimeZone(TimeZone timeZone) {
timeZoneHMValid = false;
this.timeZone = timeZone;
}
void setTimeZoneHour(int timeZoneHour) {
timeZoneHMValid = true;
this.timeZoneHour = timeZoneHour;
}
void setTimeZoneMinute(int timeZoneMinute) {
timeZoneHMValid = true;
this.timeZoneMinute = timeZoneMinute;
}
private boolean hasToParseData() { private boolean hasToParseData() {
return formatStr.length() > 0; return formatStr.length() > 0;
} }
...@@ -180,9 +327,21 @@ public class ToDateParser { ...@@ -180,9 +327,21 @@ public class ToDateParser {
* @param format the format * @param format the format
* @return the timestamp * @return the timestamp
*/ */
public static Timestamp toTimestamp(String input, String format) { public static ValueTimestamp toTimestamp(String input, String format) {
ToDateParser parser = getTimestampParser(input, format); ToDateParser parser = getTimestampParser(ConfigParam.TO_TIMESTAMP, input, format);
return parser.getResultingTimestamp(); return parser.getResultingValue();
}
/**
* Parse a string as a timestamp with the given format.
*
* @param input the input
* @param format the format
* @return the timestamp
*/
public static ValueTimestampTimeZone toTimestampTz(String input, String format) {
ToDateParser parser = getTimestampParser(ConfigParam.TO_TIMESTAMP_TZ, input, format);
return parser.getResultingValueWithTimeZone();
} }
/** /**
...@@ -192,9 +351,9 @@ public class ToDateParser { ...@@ -192,9 +351,9 @@ public class ToDateParser {
* @param format the format * @param format the format
* @return the date as a timestamp * @return the date as a timestamp
*/ */
public static Timestamp toDate(String input, String format) { public static ValueTimestamp toDate(String input, String format) {
ToDateParser parser = getDateParser(input, format); ToDateParser parser = getTimestampParser(ConfigParam.TO_DATE, input, format);
return parser.getResultingTimestamp(); return parser.getResultingValue();
} }
/** /**
...@@ -202,7 +361,8 @@ public class ToDateParser { ...@@ -202,7 +361,8 @@ public class ToDateParser {
*/ */
private enum ConfigParam { private enum ConfigParam {
TO_DATE("DD MON YYYY"), TO_DATE("DD MON YYYY"),
TO_TIMESTAMP("DD MON YYYY HH:MI:SS"); TO_TIMESTAMP("DD MON YYYY HH:MI:SS"),
TO_TIMESTAMP_TZ("DD MON YYYY HH:MI:SS TZR");
private final String defaultFormatStr; private final String defaultFormatStr;
ConfigParam(String defaultFormatStr) { ConfigParam(String defaultFormatStr) {
......
...@@ -6,17 +6,12 @@ ...@@ -6,17 +6,12 @@
package org.h2.util; package org.h2.util;
import static java.lang.String.format; import static java.lang.String.format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.regex.Matcher; import java.util.regex.Matcher;
...@@ -144,38 +139,36 @@ class ToDateTokenizer { ...@@ -144,38 +139,36 @@ class ToDateTokenizer {
* Parslet responsible for parsing year parameter * Parslet responsible for parsing year parameter
*/ */
static class YearParslet implements ToDateParslet { static class YearParslet implements ToDateParslet {
@Override @Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) { String formatTokenStr) {
Calendar result = params.getResultCalendar();
String inputFragmentStr = null; String inputFragmentStr = null;
int dateNr = 0; int dateNr = 0;
switch (formatTokenEnum) { switch (formatTokenEnum) {
case SYYYY: case SYYYY:
case YYYY: case YYYY:
case IYYY:
inputFragmentStr = matchStringOrThrow(PATTERN_FOUR_DIGITS, inputFragmentStr = matchStringOrThrow(PATTERN_FOUR_DIGITS,
params, formatTokenEnum); params, formatTokenEnum);
// only necessary for Java1.6
if (inputFragmentStr.startsWith("+")) {
inputFragmentStr = inputFragmentStr.substring(1);
}
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
// Gregorian calendar does not have a year 0. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
if (dateNr == 0) { if (dateNr == 0) {
throwException(params, "Year may not be zero"); throwException(params, "Year may not be zero");
} }
result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1); params.setYear(dateNr >= 0 ? dateNr : dateNr + 1);
break; break;
case YYY: case YYY:
case IYY:
inputFragmentStr = matchStringOrThrow(PATTERN_THREE_DIGITS, inputFragmentStr = matchStringOrThrow(PATTERN_THREE_DIGITS,
params, formatTokenEnum); params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
if (dateNr > 999) {
throwException(params, "Year may have only three digits with specified format");
}
dateNr += (params.getCurrentYear() / 1_000) * 1_000;
// Gregorian calendar does not have a year 0. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1); params.setYear(dateNr >= 0 ? dateNr : dateNr + 1);
break; break;
case RRRR: case RRRR:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
...@@ -191,15 +184,14 @@ class ToDateTokenizer { ...@@ -191,15 +184,14 @@ class ToDateTokenizer {
if (dateNr == 0) { if (dateNr == 0) {
throwException(params, "Year may not be zero"); throwException(params, "Year may not be zero");
} }
result.set(Calendar.YEAR, dateNr); params.setYear(dateNr);
break; break;
case RR: case RR:
Calendar calendar = DateTimeUtils.createGregorianCalendar(); int cc = params.getCurrentYear() / 100;
int cc = calendar.get(Calendar.YEAR) / 100;
inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS,
params, formatTokenEnum); params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr) + cc * 100; dateNr = Integer.parseInt(inputFragmentStr) + cc * 100;
result.set(Calendar.YEAR, dateNr); params.setYear(dateNr);
break; break;
case EE /* NOT supported yet */: case EE /* NOT supported yet */:
throwException(params, format("token '%s' not supported yet.", throwException(params, format("token '%s' not supported yet.",
...@@ -210,38 +202,40 @@ class ToDateTokenizer { ...@@ -210,38 +202,40 @@ class ToDateTokenizer {
formatTokenEnum.name())); formatTokenEnum.name()));
break; break;
case YY: case YY:
case IY:
inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS,
params, formatTokenEnum); params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
if (dateNr > 99) {
throwException(params, "Year may have only two digits with specified format");
}
dateNr += (params.getCurrentYear() / 100) * 100;
// Gregorian calendar does not have a year 0. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1); params.setYear(dateNr >= 0 ? dateNr : dateNr + 1);
break; break;
case SCC: case SCC:
case CC: case CC:
inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS,
params, formatTokenEnum); params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr) * 100; dateNr = Integer.parseInt(inputFragmentStr) * 100;
result.set(Calendar.YEAR, dateNr); params.setYear(dateNr);
break; break;
case Y: case Y:
case I:
inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params, inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params,
formatTokenEnum); formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
if (dateNr > 9) {
throwException(params, "Year may have only two digits with specified format");
}
dateNr += (params.getCurrentYear() / 10) * 10;
// Gregorian calendar does not have a year 0. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1); params.setYear(dateNr >= 0 ? dateNr : dateNr + 1);
break; break;
case BC_AD: case BC_AD:
inputFragmentStr = matchStringOrThrow(PATTERN_BC_AD, params, inputFragmentStr = matchStringOrThrow(PATTERN_BC_AD, params,
formatTokenEnum); formatTokenEnum);
if (inputFragmentStr.toUpperCase().startsWith("B")) { params.setBC(inputFragmentStr.toUpperCase().startsWith("B"));
result.set(Calendar.ERA, GregorianCalendar.BC);
} else {
result.set(Calendar.ERA, GregorianCalendar.AD);
}
break; break;
default: default:
throw new IllegalArgumentException(format( throw new IllegalArgumentException(format(
...@@ -262,29 +256,26 @@ class ToDateTokenizer { ...@@ -262,29 +256,26 @@ class ToDateTokenizer {
@Override @Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) { String formatTokenStr) {
Calendar result = params.getResultCalendar();
String s = params.getInputStr(); String s = params.getInputStr();
String inputFragmentStr = null; String inputFragmentStr = null;
int dateNr = 0; int dateNr = 0;
switch (formatTokenEnum) { switch (formatTokenEnum) {
case MONTH: case MONTH:
inputFragmentStr = setByName(result, params, Calendar.MONTH, inputFragmentStr = setByName(params, ToChar.MONTHS);
Calendar.LONG);
break; break;
case Q /* NOT supported yet */: case Q /* NOT supported yet */:
throwException(params, format("token '%s' not supported yet.", throwException(params, format("token '%s' not supported yet.",
formatTokenEnum.name())); formatTokenEnum.name()));
break; break;
case MON: case MON:
inputFragmentStr = setByName(result, params, Calendar.MONTH, inputFragmentStr = setByName(params, ToChar.SHORT_MONTHS);
Calendar.SHORT);
break; break;
case MM: case MM:
// Note: In Calendar Month go from 0 - 11 // Note: In Calendar Month go from 0 - 11
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.MONTH, dateNr - 1); params.setMonth(dateNr);
break; break;
case RM: case RM:
dateNr = 0; dateNr = 0;
...@@ -293,7 +284,7 @@ class ToDateTokenizer { ...@@ -293,7 +284,7 @@ class ToDateTokenizer {
int len = monthName.length(); int len = monthName.length();
if (s.length() >= len && monthName if (s.length() >= len && monthName
.equalsIgnoreCase(s.substring(0, len))) { .equalsIgnoreCase(s.substring(0, len))) {
result.set(Calendar.MONTH, dateNr); params.setMonth(dateNr + 1);
inputFragmentStr = monthName; inputFragmentStr = monthName;
break; break;
} }
...@@ -322,7 +313,6 @@ class ToDateTokenizer { ...@@ -322,7 +313,6 @@ class ToDateTokenizer {
@Override @Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) { String formatTokenStr) {
Calendar result = params.getResultCalendar();
String inputFragmentStr = null; String inputFragmentStr = null;
int dateNr = 0; int dateNr = 0;
switch (formatTokenEnum) { switch (formatTokenEnum) {
...@@ -330,40 +320,31 @@ class ToDateTokenizer { ...@@ -330,40 +320,31 @@ class ToDateTokenizer {
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum); formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_YEAR, dateNr); params.setDayOfYear(dateNr);
break; break;
case DD: case DD:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_MONTH, dateNr); params.setDay(dateNr);
break; break;
case D: case D:
inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params, inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params,
formatTokenEnum); formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.DAY_OF_MONTH, dateNr); params.setDay(dateNr);
break; break;
case DAY: case DAY:
inputFragmentStr = setByName(result, params, inputFragmentStr = setByName(params, ToChar.WEEKDAYS);
Calendar.DAY_OF_WEEK, Calendar.LONG);
break; break;
case DY: case DY:
inputFragmentStr = setByName(result, params, inputFragmentStr = setByName(params, ToChar.SHORT_WEEKDAYS);
Calendar.DAY_OF_WEEK, Calendar.SHORT);
break; break;
case J: case J:
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum); formatTokenEnum);
try { dateNr = Integer.parseInt(inputFragmentStr);
Date date = new SimpleDateFormat("Myydd") params.setAbsoluteDay(dateNr + ToChar.JULIAN_EPOCH);
.parse(inputFragmentStr);
result.setTime(date);
} catch (ParseException e) {
throwException(params,
format("Failed to parse Julian date: %s",
inputFragmentStr));
}
break; break;
default: default:
throw new IllegalArgumentException(format( throw new IllegalArgumentException(format(
...@@ -382,7 +363,6 @@ class ToDateTokenizer { ...@@ -382,7 +363,6 @@ class ToDateTokenizer {
@Override @Override
public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum,
String formatTokenStr) { String formatTokenStr) {
Calendar result = params.getResultCalendar();
String inputFragmentStr = null; String inputFragmentStr = null;
int dateNr = 0; int dateNr = 0;
switch (formatTokenEnum) { switch (formatTokenEnum) {
...@@ -390,97 +370,76 @@ class ToDateTokenizer { ...@@ -390,97 +370,76 @@ class ToDateTokenizer {
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.HOUR_OF_DAY, dateNr); params.setHour(dateNr);
break; break;
case HH12: case HH12:
case HH: case HH:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.HOUR, dateNr); params.setHour12(dateNr);
break; break;
case MI: case MI:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.MINUTE, dateNr); params.setMinute(dateNr);
break; break;
case SS: case SS:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.SECOND, dateNr); params.setSecord(dateNr);
break; break;
case SSSSS: case SSSSS: {
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum); formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
result.set(Calendar.HOUR_OF_DAY, 0); int second = dateNr % 60;
result.set(Calendar.MINUTE, 0); dateNr /= 60;
result.set(Calendar.SECOND, dateNr); int minute = dateNr % 60;
dateNr /= 60;
int hour = dateNr % 24;
params.setHour(hour);
params.setMinute(minute);
params.setSecord(second);
break; break;
}
case FF: case FF:
inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params,
formatTokenEnum); formatTokenEnum);
String paddedRightNrStr = format("%-9s", inputFragmentStr) String paddedRightNrStr = format("%-9s", inputFragmentStr)
.replace(' ', '0'); .replace(' ', '0');
paddedRightNrStr = paddedRightNrStr.substring(0, 9); paddedRightNrStr = paddedRightNrStr.substring(0, 9);
Double nineDigits = Double.parseDouble(paddedRightNrStr); double nineDigits = Double.parseDouble(paddedRightNrStr);
params.setNanos(nineDigits.intValue()); params.setNanos((int) nineDigits);
dateNr = (int) Math.round(nineDigits / 1000000.0);
result.set(Calendar.MILLISECOND, dateNr);
break; break;
case AM_PM: case AM_PM:
inputFragmentStr = matchStringOrThrow(PATTERN_AM_PM, params, inputFragmentStr = matchStringOrThrow(PATTERN_AM_PM, params,
formatTokenEnum); formatTokenEnum);
if (inputFragmentStr.toUpperCase().startsWith("A")) { if (inputFragmentStr.toUpperCase().startsWith("A")) {
result.set(Calendar.AM_PM, Calendar.AM); params.setAmPm(true);
} else { } else {
result.set(Calendar.AM_PM, Calendar.PM); params.setAmPm(false);
} }
break; break;
case TZH: case TZH:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
TimeZone tz = result.getTimeZone(); params.setTimeZoneHour(dateNr);
int offsetMillis = tz.getRawOffset();
// purge min and sec
offsetMillis = (offsetMillis / MILLIS_IN_HOUR) * MILLIS_IN_HOUR;
tz.setRawOffset(offsetMillis + dateNr);
result.setTimeZone(tz);
break; break;
case TZM: case TZM:
inputFragmentStr = matchStringOrThrow( inputFragmentStr = matchStringOrThrow(
PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
tz = result.getTimeZone(); params.setTimeZoneMinute(dateNr);
offsetMillis = tz.getRawOffset();
// purge hour
offsetMillis = offsetMillis % MILLIS_IN_HOUR;
tz.setRawOffset(dateNr * MILLIS_IN_HOUR + offsetMillis);
result.setTimeZone(tz);
break; break;
case TZR: case TZR:
// Example: US/Pacific
String s = params.getInputStr();
tz = result.getTimeZone();
for (String tzName : TimeZone.getAvailableIDs()) {
int length = tzName.length();
if (s.length() >= length && tzName
.equalsIgnoreCase(s.substring(0, length))) {
tz.setID(tzName);
result.setTimeZone(tz);
inputFragmentStr = tzName;
break;
}
}
break;
case TZD: case TZD:
// Must correspond with TZR region. Example: PST (for US/Pacific String tzName = params.getInputStr();
// standard time) params.setTimeZone(TimeZone.getTimeZone(tzName));
throwException(params, format("token '%s' not supported yet.", inputFragmentStr = tzName;
formatTokenEnum.name()));
break; break;
default: default:
throw new IllegalArgumentException(format( throw new IllegalArgumentException(format(
...@@ -535,22 +494,33 @@ class ToDateTokenizer { ...@@ -535,22 +494,33 @@ class ToDateTokenizer {
/** /**
* Set the given field in the calendar. * Set the given field in the calendar.
* *
* @param c the calendar
* @param params the parameters with the input string * @param params the parameters with the input string
* @param field the field to set * @param field the field to set
* @param style the data type
* @return the matched value * @return the matched value
*/ */
static String setByName(Calendar c, ToDateParser params, int field, static String setByName(ToDateParser params, int field) {
int style) {
String inputFragmentStr = null; String inputFragmentStr = null;
String s = params.getInputStr(); String s = params.getInputStr();
Map<String, Integer> timeStringMap = c.getDisplayNames(field, style, String[] values = ToChar.getNames(field);
Locale.getDefault()); for (int i = 0; i < values.length; i++) {
for (String dayName : timeStringMap.keySet()) { String dayName = values[i];
if (dayName == null) {
continue;
}
int len = dayName.length(); int len = dayName.length();
if (dayName.equalsIgnoreCase(s.substring(0, len))) { if (dayName.equalsIgnoreCase(s.substring(0, len))) {
c.set(field, timeStringMap.get(dayName)); switch (field) {
case ToChar.MONTHS:
case ToChar.SHORT_MONTHS:
params.setMonth(i + 1);
break;
case ToChar.WEEKDAYS:
case ToChar.SHORT_WEEKDAYS:
// TODO
break;
default:
throw new IllegalArgumentException();
}
inputFragmentStr = dayName; inputFragmentStr = dayName;
break; break;
} }
...@@ -558,7 +528,7 @@ class ToDateTokenizer { ...@@ -558,7 +528,7 @@ class ToDateTokenizer {
if (inputFragmentStr == null || inputFragmentStr.isEmpty()) { if (inputFragmentStr == null || inputFragmentStr.isEmpty()) {
throwException(params, format( throwException(params, format(
"Tried to parse one of '%s' but failed (may be an internal error?)", "Tried to parse one of '%s' but failed (may be an internal error?)",
timeStringMap.keySet())); Arrays.toString(values)));
} }
return inputFragmentStr; return inputFragmentStr;
} }
...@@ -583,9 +553,10 @@ class ToDateTokenizer { ...@@ -583,9 +553,10 @@ class ToDateTokenizer {
YYYY(PARSLET_YEAR), YYYY(PARSLET_YEAR),
// 4-digit year with sign (- = B.C.) // 4-digit year with sign (- = B.C.)
SYYYY(PARSLET_YEAR), SYYYY(PARSLET_YEAR),
// 4-digit year based on the ISO standard (?) // 3-digit year
IYYY(PARSLET_YEAR), YYY(PARSLET_YEAR), IYY(PARSLET_YEAR), YY( YYY(PARSLET_YEAR),
PARSLET_YEAR), IY(PARSLET_YEAR), // 2-digit year
YY(PARSLET_YEAR),
// Two-digit century with with sign (- = B.C.) // Two-digit century with with sign (- = B.C.)
SCC(PARSLET_YEAR), SCC(PARSLET_YEAR),
// Two-digit century. // Two-digit century.
...@@ -638,7 +609,7 @@ class ToDateTokenizer { ...@@ -638,7 +609,7 @@ class ToDateTokenizer {
// NOT supported yet - // NOT supported yet -
// Abbreviated era name (Japanese Imperial, // Abbreviated era name (Japanese Imperial,
// ROC Official, and Thai Buddha calendars). // ROC Official, and Thai Buddha calendars).
E(PARSLET_YEAR), Y(PARSLET_YEAR), I(PARSLET_YEAR), E(PARSLET_YEAR), Y(PARSLET_YEAR),
// Quarter of year (1, 2, 3, 4; JAN-MAR = 1). // Quarter of year (1, 2, 3, 4; JAN-MAR = 1).
Q(PARSLET_MONTH), Q(PARSLET_MONTH),
// Day of week (1-7). // Day of week (1-7).
......
...@@ -14,7 +14,6 @@ import java.io.Reader; ...@@ -14,7 +14,6 @@ import java.io.Reader;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
...@@ -463,10 +462,6 @@ public class Transfer { ...@@ -463,10 +462,6 @@ public class Transfer {
throw DbException.get( throw DbException.get(
ErrorCode.CONNECTION_BROKEN_1, "length=" + length); ErrorCode.CONNECTION_BROKEN_1, "length=" + length);
} }
if (length > Integer.MAX_VALUE) {
throw DbException.get(
ErrorCode.CONNECTION_BROKEN_1, "length="+ length);
}
writeLong(length); writeLong(length);
Reader reader = v.getReader(); Reader reader = v.getReader();
Data.copyString(reader, out); Data.copyString(reader, out);
...@@ -652,21 +647,10 @@ public class Transfer { ...@@ -652,21 +647,10 @@ public class Transfer {
return ValueLobDb.create( return ValueLobDb.create(
Value.CLOB, session.getDataHandler(), tableId, id, hmac, precision); Value.CLOB, session.getDataHandler(), tableId, id, hmac, precision);
} }
if (length < 0 || length > Integer.MAX_VALUE) { if (length < 0) {
throw DbException.get( throw DbException.get(
ErrorCode.CONNECTION_BROKEN_1, "length="+ length); ErrorCode.CONNECTION_BROKEN_1, "length="+ length);
} }
DataReader reader = new DataReader(in);
int len = (int) length;
char[] buff = new char[len];
IOUtils.readFully(reader, buff, len);
int magic = readInt();
if (magic != LOB_MAGIC) {
throw DbException.get(
ErrorCode.CONNECTION_BROKEN_1, "magic=" + magic);
}
byte[] small = new String(buff).getBytes(StandardCharsets.UTF_8);
return ValueLobDb.createSmallLob(Value.CLOB, small, length);
} }
Value v = session.getDataHandler().getLobStorage(). Value v = session.getDataHandler().getLobStorage().
createClob(new DataReader(in), length); createClob(new DataReader(in), length);
......
...@@ -26,6 +26,7 @@ import org.h2.store.FileStoreInputStream; ...@@ -26,6 +26,7 @@ import org.h2.store.FileStoreInputStream;
import org.h2.store.FileStoreOutputStream; import org.h2.store.FileStoreOutputStream;
import org.h2.store.LobStorageFrontend; import org.h2.store.LobStorageFrontend;
import org.h2.store.LobStorageInterface; import org.h2.store.LobStorageInterface;
import org.h2.store.RangeReader;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
...@@ -544,6 +545,15 @@ public class ValueLobDb extends Value implements Value.ValueClob, ...@@ -544,6 +545,15 @@ public class ValueLobDb extends Value implements Value.ValueClob,
*/ */
public static ValueLobDb createTempClob(Reader in, long length, public static ValueLobDb createTempClob(Reader in, long length,
DataHandler handler) { DataHandler handler) {
if (length >= 0) {
// Otherwise BufferedReader may try to read more data than needed and that
// blocks the network level
try {
in = new RangeReader(in, 0, length);
} catch (IOException e) {
throw DbException.convert(e);
}
}
BufferedReader reader; BufferedReader reader;
if (in instanceof BufferedReader) { if (in instanceof BufferedReader) {
reader = (BufferedReader) in; reader = (BufferedReader) in;
......
...@@ -9,8 +9,6 @@ import java.math.BigDecimal; ...@@ -9,8 +9,6 @@ import java.math.BigDecimal;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.TimestampWithTimeZone; import org.h2.api.TimestampWithTimeZone;
import org.h2.message.DbException; import org.h2.message.DbException;
...@@ -148,19 +146,6 @@ public class ValueTimestampTimeZone extends Value { ...@@ -148,19 +146,6 @@ public class ValueTimestampTimeZone extends Value {
return timeZoneOffsetMins; return timeZoneOffsetMins;
} }
/**
* Returns compatible offset-based time zone with no DST schedule.
*
* @return compatible offset-based time zone
*/
public TimeZone getTimeZone() {
int offset = timeZoneOffsetMins;
if (offset == 0) {
return DateTimeUtils.UTC;
}
return new SimpleTimeZone(offset * 60000, Integer.toString(offset));
}
@Override @Override
public Timestamp getTimestamp() { public Timestamp getTimestamp() {
throw new UnsupportedOperationException("unimplemented"); throw new UnsupportedOperationException("unimplemented");
...@@ -219,34 +204,31 @@ public class ValueTimestampTimeZone extends Value { ...@@ -219,34 +204,31 @@ public class ValueTimestampTimeZone extends Value {
@Override @Override
protected int compareSecure(Value o, CompareMode mode) { protected int compareSecure(Value o, CompareMode mode) {
ValueTimestampTimeZone t = (ValueTimestampTimeZone) o; ValueTimestampTimeZone t = (ValueTimestampTimeZone) o;
// We are pretending that the dateValue is in UTC because that gives us // Maximum time zone offset is +/-18 hours so difference in days between local
// a stable sort even if the DST database changes. // and UTC cannot be more than one day
long daysA = DateTimeUtils.absoluteDayFromDateValue(dateValue);
// convert to minutes and add timezone offset long timeA = timeNanos - timeZoneOffsetMins * 60_000_000_000L;
long a = DateTimeUtils.convertDateValueToMillis( if (timeA < 0) {
DateTimeUtils.UTC, dateValue) / timeA += DateTimeUtils.NANOS_PER_DAY;
(1000L * 60L); daysA--;
long ma = timeNanos / (1000L * 1000L * 1000L * 60L); } else if (timeA >= DateTimeUtils.NANOS_PER_DAY) {
a += ma; timeA -= DateTimeUtils.NANOS_PER_DAY;
a -= timeZoneOffsetMins; daysA++;
}
// convert to minutes and add timezone offset long daysB = DateTimeUtils.absoluteDayFromDateValue(t.dateValue);
long b = DateTimeUtils.convertDateValueToMillis( long timeB = t.timeNanos - t.timeZoneOffsetMins * 60_000_000_000L;
DateTimeUtils.UTC, t.dateValue) / if (timeB < 0) {
(1000L * 60L); timeB += DateTimeUtils.NANOS_PER_DAY;
long mb = t.timeNanos / (1000L * 1000L * 1000L * 60L); daysB--;
b += mb; } else if (timeB >= DateTimeUtils.NANOS_PER_DAY) {
b -= t.timeZoneOffsetMins; timeB -= DateTimeUtils.NANOS_PER_DAY;
daysB++;
// compare date }
int c = Long.compare(a, b); int cmp = Long.compare(daysA, daysB);
if (c != 0) { if (cmp != 0) {
return c; return cmp;
} }
// compare time return Long.compare(timeA, timeB);
long na = timeNanos - (ma * 1000L * 1000L * 1000L * 60L);
long nb = t.timeNanos - (mb * 1000L * 1000L * 1000L * 60L);
return Long.compare(na, nb);
} }
@Override @Override
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
package org.h2.test; package org.h2.test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
...@@ -30,7 +31,9 @@ import java.sql.Types; ...@@ -30,7 +31,9 @@ import java.sql.Types;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.SimpleTimeZone; import java.util.SimpleTimeZone;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -40,6 +43,7 @@ import org.h2.store.fs.FilePath; ...@@ -40,6 +43,7 @@ import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.utils.ProxyCodeGenerator; import org.h2.test.utils.ProxyCodeGenerator;
import org.h2.test.utils.ResultVerifier; import org.h2.test.utils.ResultVerifier;
import org.h2.test.utils.SelfDestructor;
import org.h2.tools.DeleteDbFiles; import org.h2.tools.DeleteDbFiles;
/** /**
...@@ -1444,6 +1448,16 @@ public abstract class TestBase { ...@@ -1444,6 +1448,16 @@ public abstract class TestBase {
return System.getProperty("java.class.path"); return System.getProperty("java.class.path");
} }
/**
* Get the path to a java executable of the current process
*
* @return the path to java
*/
private String getJVM() {
return System.getProperty("java.home") + File.separatorChar + "bin" +
File.separator + "java";
}
/** /**
* Use up almost all memory. * Use up almost all memory.
* *
...@@ -1687,4 +1701,46 @@ public abstract class TestBase { ...@@ -1687,4 +1701,46 @@ public abstract class TestBase {
return getClass().getSimpleName(); return getClass().getSimpleName();
} }
public ProcessBuilder buildChild(String name, Class<? extends TestBase> childClass,
String... jvmArgs) {
List<String> args = new ArrayList<>(16);
args.add(getJVM());
Collections.addAll(args, jvmArgs);
Collections.addAll(args, "-cp", getClassPath(),
SelfDestructor.getPropertyString(1),
childClass.getName(),
"-url", getURL(name, true),
"-user", getUser(),
"-password", getPassword());
ProcessBuilder processBuilder = new ProcessBuilder()
// .redirectError(ProcessBuilder.Redirect.INHERIT)
.redirectErrorStream(true)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.command(args);
return processBuilder;
}
public abstract static class Child extends TestBase
{
private String url;
private String user;
private String password;
public Child(String... args) {
for (int i = 0; i < args.length; i++) {
if ("-url".equals(args[i])) {
url = args[++i];
} else if ("-user".equals(args[i])) {
user = args[++i];
} else if ("-password".equals(args[i])) {
password = args[++i];
}
SelfDestructor.startCountdown(60);
}
}
public Connection getConnection() throws SQLException {
return getConnection(url, user, password);
}
}
} }
...@@ -34,6 +34,7 @@ import java.util.ArrayList; ...@@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Currency; import java.util.Currency;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
...@@ -56,6 +57,8 @@ import org.h2.util.StringUtils; ...@@ -56,6 +57,8 @@ import org.h2.util.StringUtils;
import org.h2.util.ToChar.Capitalization; import org.h2.util.ToChar.Capitalization;
import org.h2.util.ToDateParser; import org.h2.util.ToDateParser;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;
/** /**
* Tests for user defined functions and aggregates. * Tests for user defined functions and aggregates.
...@@ -1296,10 +1299,13 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1296,10 +1299,13 @@ public class TestFunctions extends TestBase implements AggregateFunction {
} }
private void testToDate() throws ParseException { private void testToDate() throws ParseException {
final int month = DateTimeUtils.createGregorianCalendar().get(Calendar.MONTH); GregorianCalendar calendar = DateTimeUtils.createGregorianCalendar();
int year = calendar.get(Calendar.YEAR);
Date date = null; int month = calendar.get(Calendar.MONTH) + 1;
date = new SimpleDateFormat("yyyy-MM-dd").parse("1979-11-12"); // Default date in Oracle is the first day of the current month
String defDate = year + "-" + month + "-1 ";
ValueTimestamp date = null;
date = ValueTimestamp.parse("1979-11-12");
assertEquals(date, ToDateParser.toDate("1979-11-12T00:00:00Z", "YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"")); assertEquals(date, ToDateParser.toDate("1979-11-12T00:00:00Z", "YYYY-MM-DD\"T\"HH24:MI:SS\"Z\""));
assertEquals(date, ToDateParser.toDate("1979*foo*1112", "YYYY\"*foo*\"MM\"\"DD")); assertEquals(date, ToDateParser.toDate("1979*foo*1112", "YYYY\"*foo*\"MM\"\"DD"));
assertEquals(date, ToDateParser.toDate("1979-11-12", "YYYY-MM-DD")); assertEquals(date, ToDateParser.toDate("1979-11-12", "YYYY-MM-DD"));
...@@ -1309,126 +1315,124 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1309,126 +1315,124 @@ public class TestFunctions extends TestBase implements AggregateFunction {
assertEquals(date, ToDateParser.toDate("1979;11;12", "YYYY;MM;DD")); assertEquals(date, ToDateParser.toDate("1979;11;12", "YYYY;MM;DD"));
assertEquals(date, ToDateParser.toDate("1979:11:12", "YYYY:MM:DD")); assertEquals(date, ToDateParser.toDate("1979:11:12", "YYYY:MM:DD"));
date = new SimpleDateFormat("yyyy").parse("1979"); date = ValueTimestamp.parse("1979-" + month + "-01");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("1979", "YYYY")); assertEquals(date, ToDateParser.toDate("1979", "YYYY"));
assertEquals(date, ToDateParser.toDate("1979 AD", "YYYY AD")); assertEquals(date, ToDateParser.toDate("1979 AD", "YYYY AD"));
assertEquals(date, ToDateParser.toDate("1979 A.D.", "YYYY A.D.")); assertEquals(date, ToDateParser.toDate("1979 A.D.", "YYYY A.D."));
assertEquals(date, ToDateParser.toDate("1979 A.D.", "YYYY BC")); assertEquals(date, ToDateParser.toDate("1979 A.D.", "YYYY BC"));
assertEquals(date, ToDateParser.toDate("1979", "IYYY"));
assertEquals(date, ToDateParser.toDate("+1979", "SYYYY")); assertEquals(date, ToDateParser.toDate("+1979", "SYYYY"));
assertEquals(date, ToDateParser.toDate("79", "RRRR")); assertEquals(date, ToDateParser.toDate("79", "RRRR"));
date = new SimpleDateFormat("yyyy-mm").parse("1970-12"); date = ValueTimestamp.parse(defDate + "00:12:00");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("12", "MI")); assertEquals(date, ToDateParser.toDate("12", "MI"));
date = new SimpleDateFormat("yyyy-MM").parse("1970-11"); date = ValueTimestamp.parse("1970-11-01");
assertEquals(date, ToDateParser.toDate("11", "MM")); assertEquals(date, ToDateParser.toDate("11", "MM"));
assertEquals(date, ToDateParser.toDate("11", "Mm")); assertEquals(date, ToDateParser.toDate("11", "Mm"));
assertEquals(date, ToDateParser.toDate("11", "mM")); assertEquals(date, ToDateParser.toDate("11", "mM"));
assertEquals(date, ToDateParser.toDate("11", "mm")); assertEquals(date, ToDateParser.toDate("11", "mm"));
assertEquals(date, ToDateParser.toDate("XI", "RM")); assertEquals(date, ToDateParser.toDate("XI", "RM"));
date = new SimpleDateFormat("yyyy").parse("9"); int y = (year / 10) * 10 + 9;
setMonth(date, month); date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("9", "Y")); assertEquals(date, ToDateParser.toDate("9", "Y"));
assertEquals(date, ToDateParser.toDate("9", "I")); y = (year / 100) * 100 + 79;
date = new SimpleDateFormat("yyyy").parse("79"); date = ValueTimestamp.parse(y + "-" + month + "-01");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("79", "YY")); assertEquals(date, ToDateParser.toDate("79", "YY"));
assertEquals(date, ToDateParser.toDate("79", "IY")); y = (year / 1_000) * 1_000 + 979;
date = ValueTimestamp.parse(y + "-" + month + "-01");
date = new SimpleDateFormat("yyyy").parse("979");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("979", "YYY")); assertEquals(date, ToDateParser.toDate("979", "YYY"));
assertEquals(date, ToDateParser.toDate("979", "IYY"));
// Gregorian calendar does not have a year 0. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
date = new SimpleDateFormat("yyy").parse("-99"); date = ValueTimestamp.parse("-99-" + month + "-01");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("0100 BC", "YYYY BC")); assertEquals(date, ToDateParser.toDate("0100 BC", "YYYY BC"));
assertEquals(date, ToDateParser.toDate("0100 B.C.", "YYYY B.C.")); assertEquals(date, ToDateParser.toDate("0100 B.C.", "YYYY B.C."));
assertEquals(date, ToDateParser.toDate("100 BC", "YYY BC"));
assertEquals(date, ToDateParser.toDate("-0100", "SYYYY")); assertEquals(date, ToDateParser.toDate("-0100", "SYYYY"));
assertEquals(date, ToDateParser.toDate("-0100", "YYYY")); assertEquals(date, ToDateParser.toDate("-0100", "YYYY"));
// Gregorian calendar does not have a year 0. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
date = new SimpleDateFormat("y").parse("0"); y = -((year / 1_000) * 1_000 + 99);
setMonth(date, month); date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("100 BC", "YYY BC"));
// Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
y = -((year / 100) * 100);
date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("01 BC", "YY BC")); assertEquals(date, ToDateParser.toDate("01 BC", "YY BC"));
y = -((year / 10) * 10);
date = ValueTimestamp.parse(y + "-" + month + "-01");
assertEquals(date, ToDateParser.toDate("1 BC", "Y BC")); assertEquals(date, ToDateParser.toDate("1 BC", "Y BC"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:00"); date = ValueTimestamp.parse(defDate + "08:12:00");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("08:12 AM", "HH:MI AM")); assertEquals(date, ToDateParser.toDate("08:12 AM", "HH:MI AM"));
assertEquals(date, ToDateParser.toDate("08:12 A.M.", "HH:MI A.M.")); assertEquals(date, ToDateParser.toDate("08:12 A.M.", "HH:MI A.M."));
assertEquals(date, ToDateParser.toDate("08:12", "HH24:MI")); assertEquals(date, ToDateParser.toDate("08:12", "HH24:MI"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:00"); date = ValueTimestamp.parse(defDate + "08:12:00");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("08:12", "HH:MI")); assertEquals(date, ToDateParser.toDate("08:12", "HH:MI"));
assertEquals(date, ToDateParser.toDate("08:12", "HH12:MI")); assertEquals(date, ToDateParser.toDate("08:12", "HH12:MI"));
date = new SimpleDateFormat("hh:mm:ss").parse("08:12:34"); date = ValueTimestamp.parse(defDate + "08:12:34");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("08:12:34", "HH:MI:SS")); assertEquals(date, ToDateParser.toDate("08:12:34", "HH:MI:SS"));
date = new SimpleDateFormat("ss").parse("34"); date = ValueTimestamp.parse(defDate + "12:00:00");
setMonth(date, month); assertEquals(date, ToDateParser.toDate("12:00:00 PM", "HH12:MI:SS AM"));
date = ValueTimestamp.parse(defDate + "00:00:00");
assertEquals(date, ToDateParser.toDate("12:00:00 AM", "HH12:MI:SS AM"));
date = ValueTimestamp.parse(defDate + "00:00:34");
assertEquals(date, ToDateParser.toDate("34", "SS")); assertEquals(date, ToDateParser.toDate("34", "SS"));
date = new SimpleDateFormat("yyyy hh:mm:ss").parse("1970 08:12:34"); date = ValueTimestamp.parse(defDate + "08:12:34");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("29554", "SSSSS")); assertEquals(date, ToDateParser.toDate("29554", "SSSSS"));
date = new SimpleDateFormat("yyyy hh:mm:ss SSS").parse("1970 08:12:34 550"); date = ValueTimestamp.parse(defDate + "08:12:34.550");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("08:12:34 550", "HH:MI:SS FF")); assertEquals(date, ToDateParser.toDate("08:12:34 550", "HH:MI:SS FF"));
assertEquals(date, ToDateParser.toDate("08:12:34 55", "HH:MI:SS FF2")); assertEquals(date, ToDateParser.toDate("08:12:34 55", "HH:MI:SS FF2"));
date = new SimpleDateFormat("hh:mm:ss").parse("14:04:00"); date = ValueTimestamp.parse(defDate + "14:04:00");
setMonth(date, month);
assertEquals(date, ToDateParser.toDate("02:04 P.M.", "HH:MI p.M.")); assertEquals(date, ToDateParser.toDate("02:04 P.M.", "HH:MI p.M."));
assertEquals(date, ToDateParser.toDate("02:04 PM", "HH:MI PM")); assertEquals(date, ToDateParser.toDate("02:04 PM", "HH:MI PM"));
date = new SimpleDateFormat("yyyy-MM-dd").parse("1970-12-12"); date = ValueTimestamp.parse("1970-" + month + "-12");
// does not work in all timezones assertEquals(date, ToDateParser.toDate("12", "DD"));
// assertEquals(date, ToDateParser.toDate("12", "DD"));
date = new SimpleDateFormat("yyyy-MM-dd").parse("1970-11-12"); date = ValueTimestamp.parse(year + (calendar.isLeapYear(year) ? "11-11" : "-11-12"));
assertEquals(date, ToDateParser.toDate("316", "DDD")); assertEquals(date, ToDateParser.toDate("316", "DDD"));
assertEquals(date, ToDateParser.toDate("316", "DdD")); assertEquals(date, ToDateParser.toDate("316", "DdD"));
assertEquals(date, ToDateParser.toDate("316", "dDD")); assertEquals(date, ToDateParser.toDate("316", "dDD"));
assertEquals(date, ToDateParser.toDate("316", "ddd")); assertEquals(date, ToDateParser.toDate("316", "ddd"));
date = new SimpleDateFormat("yyyy-MM-dd").parse("2013-01-29"); date = ValueTimestamp.parse("2013-01-29");
assertEquals(date, ToDateParser.toDate("113029", "J")); assertEquals(date, ToDateParser.toDate("2456322", "J"));
if (Locale.getDefault() == Locale.ENGLISH) { if (Locale.getDefault().getLanguage().equals("en")) {
date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse("9999-12-31T23:59:59"); date = ValueTimestamp.parse("9999-12-31 23:59:59");
assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59", "DD-MON-YYYY HH24:MI:SS")); assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59", "DD-MON-YYYY HH24:MI:SS"));
assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59", "DD-MON-RRRR HH24:MI:SS")); assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59", "DD-MON-RRRR HH24:MI:SS"));
SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd"); assertEquals(ValueTimestamp.parse("0001-03-01"), ToDateParser.toDate("1-MAR-0001", "DD-MON-RRRR"));
assertEquals(ymd.parse("0001-03-01"), ToDateParser.toDate("1-MAR-0001", "DD-MON-RRRR")); assertEquals(ValueTimestamp.parse("9999-03-01"), ToDateParser.toDate("1-MAR-9999", "DD-MON-RRRR"));
assertEquals(ymd.parse("9999-03-01"), ToDateParser.toDate("1-MAR-9999", "DD-MON-RRRR")); assertEquals(ValueTimestamp.parse("2000-03-01"), ToDateParser.toDate("1-MAR-000", "DD-MON-RRRR"));
assertEquals(ymd.parse("2000-03-01"), ToDateParser.toDate("1-MAR-000", "DD-MON-RRRR")); assertEquals(ValueTimestamp.parse("1999-03-01"), ToDateParser.toDate("1-MAR-099", "DD-MON-RRRR"));
assertEquals(ymd.parse("1999-03-01"), ToDateParser.toDate("1-MAR-099", "DD-MON-RRRR")); assertEquals(ValueTimestamp.parse("0100-03-01"), ToDateParser.toDate("1-MAR-100", "DD-MON-RRRR"));
assertEquals(ymd.parse("0100-03-01"), ToDateParser.toDate("1-MAR-100", "DD-MON-RRRR")); assertEquals(ValueTimestamp.parse("2000-03-01"), ToDateParser.toDate("1-MAR-00", "DD-MON-RRRR"));
assertEquals(ymd.parse("2000-03-01"), ToDateParser.toDate("1-MAR-00", "DD-MON-RRRR")); assertEquals(ValueTimestamp.parse("2049-03-01"), ToDateParser.toDate("1-MAR-49", "DD-MON-RRRR"));
assertEquals(ymd.parse("2049-03-01"), ToDateParser.toDate("1-MAR-49", "DD-MON-RRRR")); assertEquals(ValueTimestamp.parse("1950-03-01"), ToDateParser.toDate("1-MAR-50", "DD-MON-RRRR"));
assertEquals(ymd.parse("1950-03-01"), ToDateParser.toDate("1-MAR-50", "DD-MON-RRRR")); assertEquals(ValueTimestamp.parse("1999-03-01"), ToDateParser.toDate("1-MAR-99", "DD-MON-RRRR"));
assertEquals(ymd.parse("1999-03-01"), ToDateParser.toDate("1-MAR-99", "DD-MON-RRRR")); }
}
} assertEquals(ValueTimestampTimeZone.parse("2000-05-10 10:11:12-08:15"),
ToDateParser.toTimestampTz("2000-05-10 10:11:12 -8:15", "YYYY-MM-DD HH24:MI:SS TZH:TZM"));
private static void setMonth(Date date, int month) { assertEquals(ValueTimestampTimeZone.parse("2000-05-10 10:11:12-08:15"),
Calendar c = DateTimeUtils.createGregorianCalendar(); ToDateParser.toTimestampTz("2000-05-10 10:11:12 GMT-08:15", "YYYY-MM-DD HH24:MI:SS TZR"));
c.setTime(date); assertEquals(ValueTimestampTimeZone.parse("2000-02-10 10:11:12-08"),
c.set(Calendar.MONTH, month); ToDateParser.toTimestampTz("2000-02-10 10:11:12 US/Pacific", "YYYY-MM-DD HH24:MI:SS TZR"));
date.setTime(c.getTimeInMillis()); assertEquals(ValueTimestampTimeZone.parse("2000-02-10 10:11:12-08"),
ToDateParser.toTimestampTz("2000-02-10 10:11:12 PST", "YYYY-MM-DD HH24:MI:SS TZD"));
} }
private void testToCharFromDateTime() throws SQLException { private void testToCharFromDateTime() throws SQLException {
...@@ -1453,7 +1457,7 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1453,7 +1457,7 @@ public class TestFunctions extends TestBase implements AggregateFunction {
assertResult("-100-01-15 14:04:02.12", stat, "SELECT X FROM U"); assertResult("-100-01-15 14:04:02.12", stat, "SELECT X FROM U");
String expected = String.format("%tb", timestamp1979).toUpperCase(); String expected = String.format("%tb", timestamp1979).toUpperCase();
expected = stripTrailingPeriod(expected); expected = stripTrailingPeriod(expected);
assertResult("12-" + expected + "-79 08.12.34.560000 AM", stat, assertResult("12-" + expected + "-79 08.12.34.560000000 AM", stat,
"SELECT TO_CHAR(X) FROM T"); "SELECT TO_CHAR(X) FROM T");
assertResult("- / , . ; : text - /", stat, assertResult("- / , . ; : text - /", stat,
"SELECT TO_CHAR(X, '- / , . ; : \"text\" - /') FROM T"); "SELECT TO_CHAR(X, '- / , . ; : \"text\" - /') FROM T");
...@@ -1486,12 +1490,25 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1486,12 +1490,25 @@ public class TestFunctions extends TestBase implements AggregateFunction {
assertResult("0 BC", stat, assertResult("0 BC", stat,
"SELECT TO_CHAR(X, 'Y BC') FROM U"); "SELECT TO_CHAR(X, 'Y BC') FROM U");
assertResult("1979 A.D.", stat, "SELECT TO_CHAR(X, 'YYYY B.C.') FROM T"); assertResult("1979 A.D.", stat, "SELECT TO_CHAR(X, 'YYYY B.C.') FROM T");
assertResult("2013", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'YYYY') FROM DUAL");
assertResult("013", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'YYY') FROM DUAL");
assertResult("13", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'YY') FROM DUAL");
assertResult("3", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'Y') FROM DUAL");
// ISO week year
assertResult("2014", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'IYYY') FROM DUAL");
assertResult("014", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'IYY') FROM DUAL");
assertResult("14", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'IY') FROM DUAL");
assertResult("4", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'I') FROM DUAL");
assertResult("0001", stat, "SELECT TO_CHAR(DATE '-0001-01-01', 'IYYY') FROM DUAL");
assertResult("0005", stat, "SELECT TO_CHAR(DATE '-0004-01-01', 'IYYY') FROM DUAL");
assertResult("08:12 AM", stat, "SELECT TO_CHAR(X, 'HH:MI AM') FROM T"); assertResult("08:12 AM", stat, "SELECT TO_CHAR(X, 'HH:MI AM') FROM T");
assertResult("08:12 A.M.", stat, "SELECT TO_CHAR(X, 'HH:MI A.M.') FROM T"); assertResult("08:12 A.M.", stat, "SELECT TO_CHAR(X, 'HH:MI A.M.') FROM T");
assertResult("02:04 P.M.", stat, "SELECT TO_CHAR(X, 'HH:MI A.M.') FROM U"); assertResult("02:04 P.M.", stat, "SELECT TO_CHAR(X, 'HH:MI A.M.') FROM U");
assertResult("08:12 AM", stat, "SELECT TO_CHAR(X, 'HH:MI PM') FROM T"); assertResult("08:12 AM", stat, "SELECT TO_CHAR(X, 'HH:MI PM') FROM T");
assertResult("02:04 PM", stat, "SELECT TO_CHAR(X, 'HH:MI PM') FROM U"); assertResult("02:04 PM", stat, "SELECT TO_CHAR(X, 'HH:MI PM') FROM U");
assertResult("08:12 A.M.", stat, "SELECT TO_CHAR(X, 'HH:MI P.M.') FROM T"); assertResult("08:12 A.M.", stat, "SELECT TO_CHAR(X, 'HH:MI P.M.') FROM T");
assertResult("12 PM", stat, "SELECT TO_CHAR(TIME '12:00:00', 'HH AM')");
assertResult("12 AM", stat, "SELECT TO_CHAR(TIME '00:00:00', 'HH AM')");
assertResult("A.M.", stat, "SELECT TO_CHAR(X, 'P.M.') FROM T"); assertResult("A.M.", stat, "SELECT TO_CHAR(X, 'P.M.') FROM T");
assertResult("a.m.", stat, "SELECT TO_CHAR(X, 'p.M.') FROM T"); assertResult("a.m.", stat, "SELECT TO_CHAR(X, 'p.M.') FROM T");
assertResult("a.m.", stat, "SELECT TO_CHAR(X, 'p.m.') FROM T"); assertResult("a.m.", stat, "SELECT TO_CHAR(X, 'p.m.') FROM T");
...@@ -1532,7 +1549,7 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1532,7 +1549,7 @@ public class TestFunctions extends TestBase implements AggregateFunction {
assertResult(Capitalization.CAPITALIZE.apply(expected), stat, "SELECT TO_CHAR(X, 'Dy') FROM T"); assertResult(Capitalization.CAPITALIZE.apply(expected), stat, "SELECT TO_CHAR(X, 'Dy') FROM T");
assertResult(expected.toLowerCase(), stat, "SELECT TO_CHAR(X, 'dy') FROM T"); assertResult(expected.toLowerCase(), stat, "SELECT TO_CHAR(X, 'dy') FROM T");
assertResult(expected.toLowerCase(), stat, "SELECT TO_CHAR(X, 'dY') FROM T"); assertResult(expected.toLowerCase(), stat, "SELECT TO_CHAR(X, 'dY') FROM T");
assertResult("08:12:34.560000", stat, assertResult("08:12:34.560000000", stat,
"SELECT TO_CHAR(X, 'HH:MI:SS.FF') FROM T"); "SELECT TO_CHAR(X, 'HH:MI:SS.FF') FROM T");
assertResult("08:12:34.5", stat, assertResult("08:12:34.5", stat,
"SELECT TO_CHAR(X, 'HH:MI:SS.FF1') FROM T"); "SELECT TO_CHAR(X, 'HH:MI:SS.FF1') FROM T");
...@@ -1552,10 +1569,10 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1552,10 +1569,10 @@ public class TestFunctions extends TestBase implements AggregateFunction {
"SELECT TO_CHAR(X, 'HH:MI:SS.FF8') FROM T"); "SELECT TO_CHAR(X, 'HH:MI:SS.FF8') FROM T");
assertResult("08:12:34.560000000", stat, assertResult("08:12:34.560000000", stat,
"SELECT TO_CHAR(X, 'HH:MI:SS.FF9') FROM T"); "SELECT TO_CHAR(X, 'HH:MI:SS.FF9') FROM T");
assertResult("08:12:34.560000000", stat, assertResult("012345678", stat,
"SELECT TO_CHAR(X, 'HH:MI:SS.ff9') FROM T"); "SELECT TO_CHAR(TIME '0:00:00.012345678', 'FF') FROM T");
assertResult("08:12:34.560000000", stat, assertResult("00", stat,
"SELECT TO_CHAR(X, 'HH:MI:SS.fF9') FROM T"); "SELECT TO_CHAR(TIME '0:00:00.000', 'FF2') FROM T");
assertResult("08:12", stat, "SELECT TO_CHAR(X, 'HH:MI') FROM T"); assertResult("08:12", stat, "SELECT TO_CHAR(X, 'HH:MI') FROM T");
assertResult("08:12", stat, "SELECT TO_CHAR(X, 'HH12:MI') FROM T"); assertResult("08:12", stat, "SELECT TO_CHAR(X, 'HH12:MI') FROM T");
assertResult("08:12", stat, "SELECT TO_CHAR(X, 'HH24:MI') FROM T"); assertResult("08:12", stat, "SELECT TO_CHAR(X, 'HH24:MI') FROM T");
...@@ -1606,6 +1623,10 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1606,6 +1623,10 @@ public class TestFunctions extends TestBase implements AggregateFunction {
assertResult(expected, stat, "SELECT TO_CHAR(X, 'TS') FROM T"); assertResult(expected, stat, "SELECT TO_CHAR(X, 'TS') FROM T");
assertResult(tzLongName, stat, "SELECT TO_CHAR(X, 'TZR') FROM T"); assertResult(tzLongName, stat, "SELECT TO_CHAR(X, 'TZR') FROM T");
assertResult(tzShortName, stat, "SELECT TO_CHAR(X, 'TZD') FROM T"); assertResult(tzShortName, stat, "SELECT TO_CHAR(X, 'TZD') FROM T");
assertResult("GMT+10:30", stat,
"SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00+10:30', 'TZR')");
assertResult("GMT+10:30", stat,
"SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00+10:30', 'TZD')");
expected = String.format("%f", 1.1).substring(1, 2); expected = String.format("%f", 1.1).substring(1, 2);
assertResult(expected, stat, "SELECT TO_CHAR(X, 'X') FROM T"); assertResult(expected, stat, "SELECT TO_CHAR(X, 'X') FROM T");
expected = String.format("%,d", 1979); expected = String.format("%,d", 1979);
...@@ -1903,7 +1924,8 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1903,7 +1924,8 @@ public class TestFunctions extends TestBase implements AggregateFunction {
final String twoDecimals = "0" + decimalSeparator + "00"; final String twoDecimals = "0" + decimalSeparator + "00";
assertResult(oneDecimal, stat, "select to_char(0, 'FM0D099') from dual;"); assertResult(oneDecimal, stat, "select to_char(0, 'FM0D099') from dual;");
assertResult(twoDecimals, stat, "select to_char(0., 'FM0D009') from dual;"); assertResult(twoDecimals, stat, "select to_char(0., 'FM0D009') from dual;");
assertResult("0.000000000", stat, "select to_char(0.000000000, 'FM0D999999999') from dual;"); assertResult("0" + decimalSeparator + "000000000",
stat, "select to_char(0.000000000, 'FM0D999999999') from dual;");
assertResult("0" + decimalSeparator, stat, "select to_char(0, 'FM0D9') from dual;"); assertResult("0" + decimalSeparator, stat, "select to_char(0, 'FM0D9') from dual;");
assertResult(oneDecimal, stat, "select to_char(0.0, 'FM0D099') from dual;"); assertResult(oneDecimal, stat, "select to_char(0.0, 'FM0D099') from dual;");
assertResult(twoDecimals, stat, "select to_char(0.00, 'FM0D009') from dual;"); assertResult(twoDecimals, stat, "select to_char(0.00, 'FM0D009') from dual;");
......
...@@ -15,7 +15,6 @@ import java.util.Map; ...@@ -15,7 +15,6 @@ import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.message.DbException;
import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore;
import org.h2.store.fs.FilePath; import org.h2.store.fs.FilePath;
import org.h2.store.fs.FilePathMem; import org.h2.store.fs.FilePathMem;
...@@ -28,6 +27,8 @@ import org.h2.test.TestBase; ...@@ -28,6 +27,8 @@ import org.h2.test.TestBase;
*/ */
public class TestOutOfMemory extends TestBase { public class TestOutOfMemory extends TestBase {
private static final String DB_NAME = "outOfMemory";
/** /**
* Run just this test. * Run just this test.
* *
...@@ -38,16 +39,18 @@ public class TestOutOfMemory extends TestBase { ...@@ -38,16 +39,18 @@ public class TestOutOfMemory extends TestBase {
} }
@Override @Override
public void test() throws SQLException, InterruptedException { public void test() throws Exception {
if (config.vmlens) { if (config.vmlens) {
// running out of memory will cause the vmlens agent to stop working // running out of memory will cause the vmlens agent to stop working
return; return;
} }
try { try {
if (!config.travis) {
System.gc(); System.gc();
testMVStoreUsingInMemoryFileSystem(); testMVStoreUsingInMemoryFileSystem();
System.gc(); System.gc();
testDatabaseUsingInMemoryFileSystem(); testDatabaseUsingInMemoryFileSystem();
}
System.gc(); System.gc();
testUpdateWhenNearlyOutOfMemory(); testUpdateWhenNearlyOutOfMemory();
} finally { } finally {
...@@ -147,67 +150,92 @@ public class TestOutOfMemory extends TestBase { ...@@ -147,67 +150,92 @@ public class TestOutOfMemory extends TestBase {
} }
} }
private void testUpdateWhenNearlyOutOfMemory() throws SQLException, InterruptedException { private void testUpdateWhenNearlyOutOfMemory() throws Exception {
if (config.memory) { if (config.memory) {
return; return;
} }
recoverAfterOOM(); deleteDb(DB_NAME);
deleteDb("outOfMemory");
Connection conn = getConnection("outOfMemory;MAX_OPERATION_MEMORY=1000000"); ProcessBuilder processBuilder = buildChild(
DB_NAME + ";MAX_OPERATION_MEMORY=1000000",
MyChild.class,
"-XX:+UseParallelGC",
// "-XX:+UseG1GC",
"-Xmx128m");
//*
processBuilder.start().waitFor();
/*/
List<String> args = processBuilder.command();
for (Iterator<String> iter = args.iterator(); iter.hasNext(); ) {
String arg = iter.next();
if(arg.equals(MyChild.class.getName())) {
iter.remove();
break;
}
iter.remove();
}
MyChild.main(args.toArray(new String[0]));
//*/
try (Connection conn = getConnection(DB_NAME)) {
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
stat.execute("drop all objects"); ResultSet rs = stat.executeQuery("SELECT count(*) FROM stuff");
stat.execute("create table stuff (id int, text varchar as space(100) || id)"); assertTrue(rs.next());
stat.execute("insert into stuff(id) select x from system_range(1, 3000)"); assertEquals(3000, rs.getInt(1));
rs = stat.executeQuery("SELECT * FROM stuff WHERE id = 3000");
assertTrue(rs.next());
String text = rs.getString(2);
assertFalse(rs.wasNull());
assertEquals(1004, text.length());
// TODO: there are intermittent failures here
// where number is about 1000 short of expected value.
// This indicates a real problem - durability failure
// and need to be looked at.
rs = stat.executeQuery("SELECT sum(length(text)) FROM stuff");
assertTrue(rs.next());
int totalSize = rs.getInt(1);
if (3010893 > totalSize) {
TestBase.logErrorMessage("Durability failure - expected: 3010893, actual: " + totalSize);
}
} finally {
deleteDb(DB_NAME);
}
}
public static final class MyChild extends TestBase.Child
{
public static void main(String... args) throws Exception {
new MyChild(args).init().test();
}
private MyChild(String... args) {
super(args);
}
@Override
public void test() {
try (Connection conn = getConnection()) {
Statement stat = conn.createStatement();
stat.execute("DROP ALL OBJECTS");
stat.execute("CREATE TABLE stuff (id INT, text VARCHAR)");
stat.execute("INSERT INTO stuff(id) SELECT x FROM system_range(1, 3000)");
PreparedStatement prep = conn.prepareStatement( PreparedStatement prep = conn.prepareStatement(
"update stuff set text = text || space(1000) || id"); "UPDATE stuff SET text = IFNULL(text,'') || space(1000) || id");
prep.execute(); prep.execute();
stat.execute("checkpoint"); stat.execute("CHECKPOINT");
ResultSet rs = stat.executeQuery("SELECT sum(length(text)) FROM stuff");
assertTrue(rs.next());
assertEquals(3010893, rs.getInt(1));
eatMemory(80); eatMemory(80);
try {
try {
prep.execute(); prep.execute();
fail(); fail();
} catch(DbException ex) { } catch (SQLException ignore) {
freeMemory();
assertTrue(ErrorCode.OUT_OF_MEMORY == ex.getErrorCode() || ErrorCode.GENERAL_ERROR_1 == ex.getErrorCode());
} catch (SQLException ex) {
freeMemory();
assertTrue(ErrorCode.OUT_OF_MEMORY == ex.getErrorCode() || ErrorCode.GENERAL_ERROR_1 == ex.getErrorCode());
}
recoverAfterOOM();
try {
conn.close();
fail();
} catch(DbException ex) {
freeMemory();
assertEquals(ErrorCode.DATABASE_IS_CLOSED, ex.getErrorCode());
} catch (SQLException ex) {
freeMemory();
assertEquals(ErrorCode.DATABASE_IS_CLOSED, ex.getErrorCode());
}
freeMemory();
conn = null;
conn = getConnection("outOfMemory");
stat = conn.createStatement();
ResultSet rs = stat.executeQuery("select count(*) from stuff");
rs.next();
assertEquals(3000, rs.getInt(1));
} catch (OutOfMemoryError e) {
freeMemory();
// out of memory not detected
throw new AssertionError("Out of memory not detected", e);
} finally { } finally {
freeMemory(); freeMemory();
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// out of memory will / may close the database
assertKnownException(e);
} }
} }
} }
deleteDb("outOfMemory");
}
} }
...@@ -119,7 +119,20 @@ public class TestLobApi extends TestBase { ...@@ -119,7 +119,20 @@ public class TestLobApi extends TestBase {
prep.setString(1, ""); prep.setString(1, "");
prep.setBytes(2, new byte[0]); prep.setBytes(2, new byte[0]);
prep.execute(); prep.execute();
Random r = new Random(1); Random r = new Random(1);
char[] charsSmall = new char[20];
for (int i = 0; i < charsSmall.length; i++) {
charsSmall[i] = (char) r.nextInt(10000);
}
String dSmall = new String(charsSmall);
prep.setCharacterStream(1, new StringReader(dSmall), -1);
byte[] bytesSmall = new byte[20];
r.nextBytes(bytesSmall);
prep.setBinaryStream(2, new ByteArrayInputStream(bytesSmall), -1);
prep.execute();
char[] chars = new char[100000]; char[] chars = new char[100000];
for (int i = 0; i < chars.length; i++) { for (int i = 0; i < chars.length; i++) {
chars[i] = (char) r.nextInt(10000); chars[i] = (char) r.nextInt(10000);
...@@ -130,6 +143,7 @@ public class TestLobApi extends TestBase { ...@@ -130,6 +143,7 @@ public class TestLobApi extends TestBase {
r.nextBytes(bytes); r.nextBytes(bytes);
prep.setBinaryStream(2, new ByteArrayInputStream(bytes), -1); prep.setBinaryStream(2, new ByteArrayInputStream(bytes), -1);
prep.execute(); prep.execute();
conn.setAutoCommit(false); conn.setAutoCommit(false);
ResultSet rs = stat.executeQuery("select * from test order by id"); ResultSet rs = stat.executeQuery("select * from test order by id");
rs.next(); rs.next();
...@@ -138,18 +152,27 @@ public class TestLobApi extends TestBase { ...@@ -138,18 +152,27 @@ public class TestLobApi extends TestBase {
rs.next(); rs.next();
Clob c2 = rs.getClob(2); Clob c2 = rs.getClob(2);
Blob b2 = rs.getBlob(3); Blob b2 = rs.getBlob(3);
rs.next();
Clob c3 = rs.getClob(2);
Blob b3 = rs.getBlob(3);
assertFalse(rs.next()); assertFalse(rs.next());
// now close // now close
rs.close(); rs.close();
// but the LOBs must stay open // but the LOBs must stay open
assertEquals(0, c1.length()); assertEquals(0, c1.length());
assertEquals(0, b1.length()); assertEquals(0, b1.length());
assertEquals(chars.length, c2.length());
assertEquals(bytes.length, b2.length());
assertEquals("", c1.getSubString(1, 0)); assertEquals("", c1.getSubString(1, 0));
assertEquals(new byte[0], b1.getBytes(1, 0)); assertEquals(new byte[0], b1.getBytes(1, 0));
assertEquals(d, c2.getSubString(1, (int) c2.length()));
assertEquals(bytes, b2.getBytes(1, (int) b2.length())); assertEquals(charsSmall.length, c2.length());
assertEquals(bytesSmall.length, b2.length());
assertEquals(dSmall, c2.getSubString(1, (int) c2.length()));
assertEquals(bytesSmall, b2.getBytes(1, (int) b2.length()));
assertEquals(chars.length, c3.length());
assertEquals(bytes.length, b3.length());
assertEquals(d, c3.getSubString(1, (int) c3.length()));
assertEquals(bytes, b3.getBytes(1, (int) b3.length()));
stat.execute("drop table test"); stat.execute("drop table test");
conn.close(); conn.close();
} }
......
...@@ -105,6 +105,18 @@ call dateadd('MS', 1, TIMESTAMP '2001-02-03 04:05:06.789001'); ...@@ -105,6 +105,18 @@ call dateadd('MS', 1, TIMESTAMP '2001-02-03 04:05:06.789001');
> 2001-02-03 04:05:06.790001 > 2001-02-03 04:05:06.790001
> rows: 1 > rows: 1
SELECT DATEADD('MICROSECOND', 1, TIME '10:00:01'), DATEADD('MCS', 1, TIMESTAMP '2010-10-20 10:00:01.1');
> TIME '10:00:01.000001' TIMESTAMP '2010-10-20 10:00:01.100001'
> ---------------------- --------------------------------------
> 10:00:01.000001 2010-10-20 10:00:01.100001
> rows: 1
SELECT DATEADD('NANOSECOND', 1, TIME '10:00:01'), DATEADD('NS', 1, TIMESTAMP '2010-10-20 10:00:01.1');
> TIME '10:00:01.000000001' TIMESTAMP '2010-10-20 10:00:01.100000001'
> ------------------------- -----------------------------------------
> 10:00:01.000000001 2010-10-20 10:00:01.100000001
> rows: 1
SELECT DATEADD('HOUR', 1, DATE '2010-01-20'); SELECT DATEADD('HOUR', 1, DATE '2010-01-20');
> TIMESTAMP '2010-01-20 01:00:00.0' > TIMESTAMP '2010-01-20 01:00:00.0'
> --------------------------------- > ---------------------------------
......
...@@ -159,6 +159,22 @@ call datediff('MS', TIMESTAMP '1900-01-01 00:00:01.000', TIMESTAMP '2008-01-01 0 ...@@ -159,6 +159,22 @@ call datediff('MS', TIMESTAMP '1900-01-01 00:00:01.000', TIMESTAMP '2008-01-01 0
> 3408134399000 > 3408134399000
> rows: 1 > rows: 1
SELECT DATEDIFF('MICROSECOND', '2006-01-01 00:00:00.0000000', '2006-01-01 00:00:00.123456789'),
DATEDIFF('MCS', '2006-01-01 00:00:00.0000000', '2006-01-01 00:00:00.123456789'),
DATEDIFF('MCS', '2006-01-01 00:00:00.0000000', '2006-01-02 00:00:00.123456789');
> 123456 123456 86400123456
> ------ ------ -----------
> 123456 123456 86400123456
> rows: 1
SELECT DATEDIFF('NANOSECOND', '2006-01-01 00:00:00.0000000', '2006-01-01 00:00:00.123456789'),
DATEDIFF('NS', '2006-01-01 00:00:00.0000000', '2006-01-01 00:00:00.123456789'),
DATEDIFF('NS', '2006-01-01 00:00:00.0000000', '2006-01-02 00:00:00.123456789');
> 123456789 123456789 86400123456789
> --------- --------- --------------
> 123456789 123456789 86400123456789
> rows: 1
SELECT DATEDIFF('WEEK', DATE '2018-02-02', DATE '2018-02-03'), DATEDIFF('ISO_WEEK', DATE '2018-02-02', DATE '2018-02-03'); SELECT DATEDIFF('WEEK', DATE '2018-02-02', DATE '2018-02-03'), DATEDIFF('ISO_WEEK', DATE '2018-02-02', DATE '2018-02-03');
> 0 0 > 0 0
> - - > - -
......
...@@ -3,6 +3,20 @@ ...@@ -3,6 +3,20 @@
-- Initial Developer: H2 Group -- Initial Developer: H2 Group
-- --
SELECT EXTRACT (MICROSECOND FROM TIME '10:00:00.123456789'),
EXTRACT (MCS FROM TIMESTAMP '2015-01-01 11:22:33.987654321');
> 123456 987654
> ------ ------
> 123456 987654
> rows: 1
SELECT EXTRACT (NANOSECOND FROM TIME '10:00:00.123456789'),
EXTRACT (NS FROM TIMESTAMP '2015-01-01 11:22:33.987654321');
> 123456789 987654321
> --------- ---------
> 123456789 987654321
> rows: 1
select EXTRACT (EPOCH from time '00:00:00'); select EXTRACT (EPOCH from time '00:00:00');
> 0 > 0
......
...@@ -124,14 +124,6 @@ public class TestMVStoreBenchmark extends TestBase { ...@@ -124,14 +124,6 @@ public class TestMVStoreBenchmark extends TestBase {
} }
static long getMemory() { static long getMemory() {
try {
LinkedList<byte[]> list = new LinkedList<>();
while (true) {
list.add(new byte[1024]);
}
} catch (OutOfMemoryError e) {
// ok
}
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
System.gc(); System.gc();
try { try {
......
...@@ -10,6 +10,7 @@ import java.sql.PreparedStatement; ...@@ -10,6 +10,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
...@@ -107,7 +108,22 @@ public class TestFuzzOptimizations extends TestBase { ...@@ -107,7 +108,22 @@ public class TestFuzzOptimizations extends TestBase {
ArrayList<String> params = New.arrayList(); ArrayList<String> params = New.arrayList();
String condition = getRandomCondition(random, params, columns, String condition = getRandomCondition(random, params, columns,
compares, values); compares, values);
// System.out.println(condition + " " + params); String message = "seed: " + seed + " " + condition;
executeAndCompare(condition, params, message);
if (params.size() > 0) {
for (int j = 0; j < params.size(); j++) {
String value = values[random.nextInt(values.length - 2)];
params.set(j, value);
}
executeAndCompare(condition, params, message);
}
}
executeAndCompare("a >=0 and b in(?, 2) and a in(1, ?, null)", Arrays.asList("10", "2"),
"seed=-6191135606105920350L");
db.execute("drop table test0, test1");
}
private void executeAndCompare(String condition, List<String> params, String message) throws SQLException {
PreparedStatement prep0 = conn.prepareStatement( PreparedStatement prep0 = conn.prepareStatement(
"select * from test0 where " + condition "select * from test0 where " + condition
+ " order by 1, 2, 3"); + " order by 1, 2, 3");
...@@ -120,18 +136,7 @@ public class TestFuzzOptimizations extends TestBase { ...@@ -120,18 +136,7 @@ public class TestFuzzOptimizations extends TestBase {
} }
ResultSet rs0 = prep0.executeQuery(); ResultSet rs0 = prep0.executeQuery();
ResultSet rs1 = prep1.executeQuery(); ResultSet rs1 = prep1.executeQuery();
assertEquals("seed: " + seed + " " + condition, rs0, rs1); assertEquals(message, rs0, rs1);
if (params.size() > 0) {
for (int j = 0; j < params.size(); j++) {
String value = values[random.nextInt(values.length - 2)];
params.set(j, value);
prep0.setString(j + 1, value);
prep1.setString(j + 1, value);
}
assertEquals("seed: " + seed + " " + condition, rs0, rs1);
}
}
db.execute("drop table test0, test1");
} }
private String getRandomCondition(Random random, ArrayList<String> params, private String getRandomCondition(Random random, ArrayList<String> params,
......
...@@ -26,6 +26,7 @@ public class TestClearReferences extends TestBase { ...@@ -26,6 +26,7 @@ public class TestClearReferences extends TestBase {
"org.h2.compress.CompressLZF.cachedHashTable", "org.h2.compress.CompressLZF.cachedHashTable",
"org.h2.engine.DbSettings.defaultSettings", "org.h2.engine.DbSettings.defaultSettings",
"org.h2.engine.SessionRemote.sessionFactory", "org.h2.engine.SessionRemote.sessionFactory",
"org.h2.expression.Function.MONTHS_AND_WEEKS",
"org.h2.jdbcx.JdbcDataSourceFactory.cachedTraceSystem", "org.h2.jdbcx.JdbcDataSourceFactory.cachedTraceSystem",
"org.h2.store.RecoverTester.instance", "org.h2.store.RecoverTester.instance",
"org.h2.store.fs.FilePath.defaultProvider", "org.h2.store.fs.FilePath.defaultProvider",
...@@ -36,6 +37,7 @@ public class TestClearReferences extends TestBase { ...@@ -36,6 +37,7 @@ public class TestClearReferences extends TestBase {
"org.h2.tools.CompressTool.cachedBuffer", "org.h2.tools.CompressTool.cachedBuffer",
"org.h2.util.CloseWatcher.queue", "org.h2.util.CloseWatcher.queue",
"org.h2.util.CloseWatcher.refs", "org.h2.util.CloseWatcher.refs",
"org.h2.util.DateTimeUtils.timeZone",
"org.h2.util.MathUtils.cachedSecureRandom", "org.h2.util.MathUtils.cachedSecureRandom",
"org.h2.util.NetUtils.cachedLocalAddress", "org.h2.util.NetUtils.cachedLocalAddress",
"org.h2.util.StringUtils.softCache", "org.h2.util.StringUtils.softCache",
...@@ -43,6 +45,7 @@ public class TestClearReferences extends TestBase { ...@@ -43,6 +45,7 @@ public class TestClearReferences extends TestBase {
"org.h2.util.JdbcUtils.allowedClassNamePrefixes", "org.h2.util.JdbcUtils.allowedClassNamePrefixes",
"org.h2.util.JdbcUtils.userClassFactories", "org.h2.util.JdbcUtils.userClassFactories",
"org.h2.util.Task.counter", "org.h2.util.Task.counter",
"org.h2.util.ToChar.NAMES",
"org.h2.value.CompareMode.lastUsed", "org.h2.value.CompareMode.lastUsed",
"org.h2.value.Value.softCache", "org.h2.value.Value.softCache",
}; };
......
...@@ -5,13 +5,16 @@ ...@@ -5,13 +5,16 @@
*/ */
package org.h2.test.unit; package org.h2.test.unit;
import static org.h2.util.DateTimeUtils.dateValue;
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 org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.value.ValueTimestamp;
import static org.h2.util.DateTimeUtils.dateValue;
/** /**
* Unit tests for the DateTimeUtils class * Unit tests for the DateTimeUtils class
...@@ -21,9 +24,18 @@ public class TestDateTimeUtils extends TestBase { ...@@ -21,9 +24,18 @@ public class TestDateTimeUtils extends TestBase {
/** /**
* Run just this test. * Run just this test.
* *
* @param a ignored * @param a
* if {@code "testUtc2Value"} only {@link #testUTC2Value(boolean)}
* will be executed with all time zones (slow). Otherwise all tests
* in this test unit will be executed with local time zone.
*/ */
public static void main(String... a) throws Exception { public static void main(String... a) throws Exception {
if (a.length == 1) {
if ("testUtc2Value".equals(a[0])) {
new TestDateTimeUtils().testUTC2Value(true);
return;
}
}
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
...@@ -33,6 +45,7 @@ public class TestDateTimeUtils extends TestBase { ...@@ -33,6 +45,7 @@ public class TestDateTimeUtils extends TestBase {
testDayOfWeek(); testDayOfWeek();
testWeekOfYear(); testWeekOfYear();
testDateValueFromDenormalizedDate(); testDateValueFromDenormalizedDate();
testUTC2Value(false);
} }
private void testParseTimeNanosDB2Format() { private void testParseTimeNanosDB2Format() {
...@@ -44,7 +57,7 @@ public class TestDateTimeUtils extends TestBase { ...@@ -44,7 +57,7 @@ public class TestDateTimeUtils extends TestBase {
} }
/** /**
* Test for {@link DateTimeUtils#getSundayDayOfWeek()} and * Test for {@link DateTimeUtils#getSundayDayOfWeek(long)} and
* {@link DateTimeUtils#getIsoDayOfWeek(long)}. * {@link DateTimeUtils#getIsoDayOfWeek(long)}.
*/ */
private void testDayOfWeek() { private void testDayOfWeek() {
...@@ -106,4 +119,45 @@ public class TestDateTimeUtils extends TestBase { ...@@ -106,4 +119,45 @@ public class TestDateTimeUtils extends TestBase {
assertEquals(dateValue(-100, 2, 29), DateTimeUtils.dateValueFromDenormalizedDate(-100, 2, 30)); assertEquals(dateValue(-100, 2, 29), DateTimeUtils.dateValueFromDenormalizedDate(-100, 2, 30));
} }
private void testUTC2Value(boolean allTimeZones) {
TimeZone def = TimeZone.getDefault();
GregorianCalendar gc = new GregorianCalendar();
if (allTimeZones) {
try {
for (String id : TimeZone.getAvailableIDs()) {
System.out.println(id);
TimeZone tz = TimeZone.getTimeZone(id);
TimeZone.setDefault(tz);
DateTimeUtils.resetCalendar();
testUTC2ValueImpl(tz, gc);
}
} finally {
TimeZone.setDefault(def);
DateTimeUtils.resetCalendar();
}
} else {
testUTC2ValueImpl(def, gc);
}
}
private void testUTC2ValueImpl(TimeZone tz, GregorianCalendar gc) {
gc.setTimeZone(tz);
gc.set(Calendar.MILLISECOND, 0);
long absoluteStart = DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.dateValue(1950, 01, 01));
long absoluteEnd = DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.dateValue(2050, 01, 01));
for (long i = absoluteStart; i < absoluteEnd; i++) {
long dateValue = DateTimeUtils.dateValueFromAbsoluteDay(i);
int year = DateTimeUtils.yearFromDateValue(dateValue);
int month = DateTimeUtils.monthFromDateValue(dateValue);
int day = DateTimeUtils.dayFromDateValue(dateValue);
for (int j = 0; j < 48; j++) {
gc.set(year, month - 1, day, j / 2, (j & 1) * 30, 0);
long timeMillis = gc.getTimeInMillis();
ValueTimestamp ts = DateTimeUtils.convertTimestamp(new Timestamp(timeMillis), gc);
assertEquals(ts.getDateValue(), DateTimeUtils.dateValueFromDate(timeMillis));
assertEquals(ts.getTimeNanos(), DateTimeUtils.nanosFromDate(timeMillis));
}
}
}
} }
...@@ -126,21 +126,27 @@ public class TestTimeStampWithTimeZone extends TestBase { ...@@ -126,21 +126,27 @@ public class TestTimeStampWithTimeZone extends TestBase {
ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-01 12:00:00.00+00:15"); ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-01 12:00:00.00+00:15");
ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 12:00:01.00+01:15"); ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 12:00:01.00+01:15");
int c = a.compareTo(b, null); int c = a.compareTo(b, null);
assertEquals(c, 1); assertEquals(1, c);
c = b.compareTo(a, null);
assertEquals(-1, c);
} }
private void test3() { private void test3() {
ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-02 00:00:02.00+01:15"); ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-02 00:00:02.00+01:15");
ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 23:00:01.00+00:15"); ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 23:00:01.00+00:15");
int c = a.compareTo(b, null); int c = a.compareTo(b, null);
assertEquals(c, 1); assertEquals(1, c);
c = b.compareTo(a, null);
assertEquals(-1, c);
} }
private void test4() { private void test4() {
ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-02 00:00:01.00+01:15"); ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-02 00:00:01.00+01:15");
ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 23:00:01.00+00:15"); ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 23:00:01.00+00:15");
int c = a.compareTo(b, null); int c = a.compareTo(b, null);
assertEquals(c, 0); assertEquals(0, c);
c = b.compareTo(a, null);
assertEquals(0, c);
} }
private void test5() throws SQLException { private void test5() throws SQLException {
......
...@@ -764,5 +764,5 @@ mdy destfile hclf forbids spellchecking selfdestruct expects accident jacocoagen ...@@ -764,5 +764,5 @@ mdy destfile hclf forbids spellchecking selfdestruct expects accident jacocoagen
jacoco xdata invokes sourcefiles classfiles duplication crypto stacktraces prt directions handled overly asm hardcoded jacoco xdata invokes sourcefiles classfiles duplication crypto stacktraces prt directions handled overly asm hardcoded
interpolated thead interpolated thead
die weekdiff osx subprocess dow proleptic die weekdiff osx subprocess dow proleptic microsecond microseconds divisible cmp denormalized suppressed saturated mcs
london
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论