提交 86dc865d authored 作者: Noel Grandin's avatar Noel Grandin

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	h2/src/docsrc/html/changelog.html
......@@ -11,5 +11,9 @@
/test.out.txt
.DS_Store
/.idea/
*.iml
*.ipr
*.iws
.checkstyle
/temp/
/h2web/
\ No newline at end of file
sudo: false
language: java
jdk:
- openjdk7
- oraclejdk8
before_script: cd h2
script: ./build.sh jar testFast
cache:
directories:
- $HOME/.m2/repository
matrix:
include:
- jdk: oraclejdk8
dist: trusty
group: edge
sudo: required
addons:
apt:
packages:
- oracle-java8-installer
before_script:
- "cd h2"
- "echo $JAVA_OPTS"
- "export JAVA_OPTS=-Xmx512m"
- jdk: openjdk7
dist: trusty
group: edge
sudo: required
addons:
apt:
packages:
- openjdk-7-jdk
before_script:
- "cd h2"
......@@ -4,7 +4,7 @@
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.195-SNAPSHOT</version>
<version>1.4.196-SNAPSHOT</version>
<packaging>jar</packaging>
<name>H2 Database Engine</name>
<url>http://www.h2database.com</url>
......
......@@ -197,12 +197,10 @@ WITH [ RECURSIVE ] { name [( columnName [,...] )]
AS ( select ) [,...] }
select
","
Can be used to create a recursive query.
Can be used to create a recursive or non-recursive query (common table expression).
For recursive queries the first select has to be a UNION.
Non-recursive queries are also supported.
One or more common table entries can be use referred to by name..
One or more common table entries can be referred to by name.
Column name declarations are now optional - the column names will be inferred from the named select queries.
Positional parameters are not currently supported.
","
WITH RECURSIVE t(n) AS (
SELECT 1
......@@ -215,8 +213,7 @@ SELECT sum(n) FROM t;
","
WITH t1 AS (
SELECT 1 AS FIRST_COLUMN
),
t2 AS (
), t2 AS (
SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM t1
)
SELECT sum(FIRST_COLUMN) FROM t2;
......
......@@ -23,6 +23,14 @@ Change Log
<ul>
<li>Fix bug in parsing ANALYZE TABLE xxx SAMPLE_SIZE yyy
</li>
<li>Add padding for CHAR(N) values in PostgreSQL mode
</li>
<li>Issue #89: Add DB2 timestamp format compatibility
</li>
</ul>
<h2>Version 1.4.196 (2017-06-10)</h2>
<ul>
<li>Issue#479 Allow non-recursive CTEs (WITH statements), patch from stumc
</li>
<li>Fix startup issue when using "CHECK" as a column name.
......@@ -37,6 +45,8 @@ Change Log
</li>
<li>Issue #479: Allow non-recursive Common Table Expressions (CTE)
</li>
<li>On Mac OS X, with IPv6 and no network connection, the Console tool was not working as expected.
</li>
</ul>
<h2>Version 1.4.195 (2017-04-23)</h2>
......
......@@ -28,7 +28,7 @@ Add to http://twitter.com
Sign files and publish files on Maven Central
(check java version is 1.7)
./build.sh compile jar mavenDeployCentral
./build.sh clean compile jar mavenDeployCentral
cd /data/h2database/m2-repo/com/h2database
# remove sha and md5 files:
find . -name "*.sha1" -delete
......
......@@ -2906,7 +2906,7 @@ public class Parser {
String timestamp = currentValue.getString();
read();
r = ValueExpression
.get(ValueTimestamp.parse(timestamp));
.get(ValueTimestamp.parse(timestamp, session.getDatabase().getMode()));
} else if (equalsToken("X", name)) {
read();
byte[] buffer = StringUtils
......
......@@ -140,7 +140,7 @@ public class Insert extends Prepared implements ResultTarget {
// e can be null (DEFAULT)
e = e.optimize(session);
try {
Value v = c.convert(e.getValue(session));
Value v = c.convert(e.getValue(session), session.getDatabase().getMode());
newRow.setValue(index, v);
} catch (DbException ex) {
throw setRow(ex, x, getSQL(expr));
......@@ -186,7 +186,7 @@ public class Insert extends Prepared implements ResultTarget {
Column c = columns[j];
int index = c.getColumnId();
try {
Value v = c.convert(values[j]);
Value v = c.convert(values[j], session.getDatabase().getMode());
newRow.setValue(index, v);
} catch (DbException ex) {
throw setRow(ex, rowNumber, getSQL(values));
......
......@@ -16,22 +16,22 @@ public class Constants {
/**
* The build date is updated for each public release.
*/
public static final String BUILD_DATE = "2017-04-23";
public static final String BUILD_DATE = "2017-06-10";
/**
* The build date of the last stable release.
*/
public static final String BUILD_DATE_STABLE = "2017-03-10";
public static final String BUILD_DATE_STABLE = "2017-04-23";
/**
* The build id is incremented for each public release.
*/
public static final int BUILD_ID = 195;
public static final int BUILD_ID = 196;
/**
* The build id of the last stable release.
*/
public static final int BUILD_ID_STABLE = 194;
public static final int BUILD_ID_STABLE = 195;
/**
* Whether this is a snapshot version.
......
......@@ -439,7 +439,7 @@ public class FunctionAlias extends SchemaObjectBase {
}
o = objArray;
} else {
v = v.convertTo(type);
v = v.convertTo(type, -1, session.getDatabase().getMode());
o = v.getObject();
}
if (o == null) {
......
......@@ -160,6 +160,16 @@ public class Mode {
*/
public boolean allowAffinityKey;
/**
* Whether to right-pad fixed strings with spaces.
*/
public boolean padFixedLengthStrings;
/**
* Whether DB2 TIMESTAMP formats are allowed.
*/
public boolean allowDB2TimestampFormat;
private final String name;
static {
......@@ -179,6 +189,7 @@ public class Mode {
Pattern.compile("ApplicationName|ClientAccountingInformation|" +
"ClientUser|ClientCorrelationToken");
mode.prohibitEmptyInPredicate = true;
mode.allowDB2TimestampFormat = true;
add(mode);
mode = new Mode("Derby");
......@@ -256,6 +267,7 @@ public class Mode {
mode.supportedClientInfoPropertiesRegEx =
Pattern.compile("ApplicationName");
mode.prohibitEmptyInPredicate = true;
mode.padFixedLengthStrings = true;
add(mode);
mode = new Mode("Ignite");
......
......@@ -13,6 +13,7 @@ import org.h2.index.IndexCondition;
import org.h2.message.DbException;
import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
import org.h2.util.MathUtils;
import org.h2.util.New;
import org.h2.value.Value;
import org.h2.value.ValueBoolean;
......@@ -201,7 +202,7 @@ public class Comparison extends Condition {
// to constant type, but vise versa, then let's do this here
// once.
if (constType != resType) {
right = ValueExpression.get(r.convertTo(resType));
right = ValueExpression.get(r.convertTo(resType, MathUtils.convertLongToInt(left.getPrecision()), session.getDatabase().getMode()));
}
} else if (right instanceof Parameter) {
((Parameter) right).setColumn(
......
......@@ -1263,7 +1263,7 @@ public class Function extends Expression implements FunctionCall {
} else if (v0.getType() == Value.STRING) {
ValueString vd = (ValueString) v0;
Calendar c = Calendar.getInstance();
c.setTime(ValueTimestamp.parse(vd.getString()).getDate());
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);
......
......@@ -71,7 +71,7 @@ WITH [ RECURSIVE ] { name [( columnName [,...] )]
AS ( select ) [,...] }
select
","
Can be used to create a recursive query."
Can be used to create a recursive or non-recursive query (common table expression)."
"Commands (DDL)","ALTER INDEX RENAME","
ALTER INDEX [ IF EXISTS ] indexName RENAME TO newIndexName
","
......
......@@ -163,8 +163,21 @@ public class Column {
* @return the value
*/
public Value convert(Value v) {
return convert(v, null);
}
/**
* Convert a value to this column's type using the given {@link Mode}.
* <p>
* Use this method in case the conversion is Mode-dependent.
*
* @param v the value
* @param mode the database {@link Mode} to use
* @return the value
*/
public Value convert(Value v, Mode mode) {
try {
return v.convertTo(type);
return v.convertTo(type, MathUtils.convertLongToInt(precision), mode);
} catch (DbException e) {
if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) {
String target = (table == null ? "" : table.getName() + ": ") +
......
......@@ -144,7 +144,7 @@ public class RangeTable extends Table {
@Override
public TableType getTableType() {
return TableType.SYSTEM_TABLE;
return TableType.SYSTEM_TABLE;
}
@Override
......
......@@ -207,7 +207,8 @@ public class TableView extends Table {
// If it can't be compiled, then it's a 'zero column table'
// this avoids problems when creating the view when opening the
// database.
// If it can not be compiled - it could also be a recursive common table expression query.
// If it can not be compiled - it could also be a recursive common
// table expression query.
if (isRecursiveQueryExceptionDetected(createException)) {
this.isRecursiveQueryDetected = true;
}
......@@ -673,6 +674,8 @@ public class TableView extends Table {
/**
* Was query recursion detected during compiling.
*
* @return true if yes
*/
public boolean isRecursiveQueryDetected() {
return isRecursiveQueryDetected;
......
......@@ -44,12 +44,9 @@ public class NetUtils {
*/
public static Socket createLoopbackSocket(int port, boolean ssl)
throws IOException {
InetAddress address = getBindAddress();
if (address == null) {
address = InetAddress.getLocalHost();
}
String local = getLocalAddress();
try {
return createSocket(getHostAddress(address), port, ssl);
return createSocket(local, port, ssl);
} catch (IOException e) {
try {
return createSocket("localhost", port, ssl);
......@@ -60,23 +57,6 @@ public class NetUtils {
}
}
/**
* Get the host address. This method adds '[' and ']' if required for
* Inet6Address that contain a ':'.
*
* @param address the address
* @return the host address
*/
private static String getHostAddress(InetAddress address) {
String host = address.getHostAddress();
if (address instanceof Inet6Address) {
if (host.indexOf(':') >= 0 && !host.startsWith("[")) {
host = "[" + host + "]";
}
}
return host;
}
/**
* Create a client socket that is connected to the given address and port.
*
......@@ -274,7 +254,21 @@ public class NetUtils {
throw DbException.convert(e);
}
}
String address = bind == null ? "localhost" : getHostAddress(bind);
String address;
if (bind == null) {
address = "localhost";
} else {
address = bind.getHostAddress();
if (bind instanceof Inet6Address) {
if (address.indexOf("%") >= 0) {
address = "localhost";
} else if (address.indexOf(':') >= 0 && !address.startsWith("[")) {
// adds'[' and ']' if required for
// Inet6Address that contain a ':'.
address = "[" + address + "]";
}
}
}
if (address.equals("127.0.0.1")) {
address = "localhost";
}
......
......@@ -902,6 +902,8 @@ public class DataType {
return Value.TIME;
case Types.TIMESTAMP:
return Value.TIMESTAMP;
case 2014: // Types.TIMESTAMP_WITH_TIMEZONE
return Value.TIMESTAMP_TZ;
case Types.BLOB:
return Value.BLOB;
case Types.CLOB:
......
......@@ -615,7 +615,7 @@ public class Transfer {
case Value.STRING_IGNORECASE:
return ValueStringIgnoreCase.get(readString());
case Value.STRING_FIXED:
return ValueStringFixed.get(readString());
return ValueStringFixed.get(readString(), ValueStringFixed.PRECISION_DO_NOT_TRIM, null);
case Value.BLOB: {
long length = readLong();
if (version >= Constants.TCP_PROTOCOL_VERSION_11) {
......
......@@ -20,6 +20,7 @@ import java.sql.Timestamp;
import java.sql.Types;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.store.DataHandler;
......@@ -541,6 +542,21 @@ public abstract class Value {
* @return the converted value
*/
public Value convertTo(int targetType) {
// Use -1 to indicate "default behaviour" where value conversion should not
// depend on any datatype precision.
return convertTo(targetType, -1, null);
}
/**
* Compare a value to the specified type.
*
* @param targetType the type of the returned value
* @param the precision of the column to convert this value to.
* The special constant <code>-1</code> is used to indicate that
* the precision plays no role when converting the value
* @return the converted value
*/
public Value convertTo(int targetType, int precision, Mode mode) {
// converting NULL is done in ValueNull
// converting BLOB to CLOB and vice versa is done in ValueLob
if (getType() == targetType) {
......@@ -947,7 +963,7 @@ public abstract class Value {
case DATE:
return ValueDate.parse(s.trim());
case TIMESTAMP:
return ValueTimestamp.parse(s.trim());
return ValueTimestamp.parse(s.trim(), mode);
case TIMESTAMP_TZ:
return ValueTimestampTimeZone.parse(s.trim());
case BYTES:
......@@ -962,7 +978,7 @@ public abstract class Value {
case STRING_IGNORECASE:
return ValueStringIgnoreCase.get(s);
case STRING_FIXED:
return ValueStringFixed.get(s);
return ValueStringFixed.get(s, precision, mode);
case DOUBLE:
return ValueDouble.get(Double.parseDouble(s.trim()));
case FLOAT:
......
......@@ -12,6 +12,8 @@ import java.util.Arrays;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.PrecisionModel;
import org.h2.engine.Mode;
import org.h2.message.DbException;
import org.h2.util.StringUtils;
import com.vividsolutions.jts.geom.Envelope;
......@@ -272,11 +274,11 @@ public class ValueGeometry extends Value {
}
@Override
public Value convertTo(int targetType) {
public Value convertTo(int targetType, int precision, Mode mode) {
if (targetType == Value.JAVA_OBJECT) {
return this;
}
return super.convertTo(targetType);
return super.convertTo(targetType, precision, mode);
}
/**
......
......@@ -14,6 +14,7 @@ import java.io.Reader;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
......@@ -443,7 +444,7 @@ public class ValueLob extends Value {
* @return the converted value
*/
@Override
public Value convertTo(int t) {
public Value convertTo(int t, int precision, Mode mode) {
if (t == type) {
return this;
} else if (t == Value.CLOB) {
......@@ -453,7 +454,7 @@ public class ValueLob extends Value {
ValueLob copy = ValueLob.createBlob(getInputStream(), -1, handler);
return copy;
}
return super.convertTo(t);
return super.convertTo(t, precision, mode);
}
@Override
......
......@@ -14,6 +14,7 @@ import java.io.Reader;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
......@@ -184,7 +185,7 @@ public class ValueLobDb extends Value implements Value.ValueClob,
* @return the converted value
*/
@Override
public Value convertTo(int t) {
public Value convertTo(int t, int precision, Mode mode) {
if (t == type) {
return this;
} else if (t == Value.CLOB) {
......@@ -204,7 +205,7 @@ public class ValueLobDb extends Value implements Value.ValueClob,
return ValueLobDb.createSmallLob(t, small);
}
}
return super.convertTo(t);
return super.convertTo(t, precision, mode);
}
@Override
......
......@@ -14,6 +14,7 @@ import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import org.h2.engine.Mode;
import org.h2.message.DbException;
/**
......@@ -132,7 +133,7 @@ public class ValueNull extends Value {
}
@Override
public Value convertTo(int type) {
public Value convertTo(int type, int precision, Mode mode) {
return this;
}
......
......@@ -5,6 +5,9 @@
*/
package org.h2.value;
import java.util.Arrays;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties;
import org.h2.util.StringUtils;
......@@ -13,6 +16,18 @@ import org.h2.util.StringUtils;
*/
public class ValueStringFixed extends ValueString {
/**
* Special value for the precision in {@link #get(String, int, Mode)} to indicate that the value
* should <i>not</i> be trimmed.
*/
public static final int PRECISION_DO_NOT_TRIM = Integer.MIN_VALUE;
/**
* Special value for the precision in {@link #get(String, int, Mode)} to indicate that the default
* behaviour should of trimming the value should apply.
*/
public static final int PRECISION_TRIM = -1;
private static final ValueStringFixed EMPTY = new ValueStringFixed("");
protected ValueStringFixed(String value) {
......@@ -20,15 +35,29 @@ public class ValueStringFixed extends ValueString {
}
private static String trimRight(String s) {
return trimRight(s, 0);
}
private static String trimRight(String s, int minLength) {
int endIndex = s.length() - 1;
int i = endIndex;
while (i >= 0 && s.charAt(i) == ' ') {
while (i >= minLength && s.charAt(i) == ' ') {
i--;
}
s = i == endIndex ? s : s.substring(0, i + 1);
return s;
}
private static String rightPadWithSpaces(String s, int length) {
int pad = length - s.length();
if (pad <= 0) {
return s;
}
char[] res = new char[length];
s.getChars(0, s.length(), res, 0);
Arrays.fill(res, s.length(), length, ' ');
return new String(res);
}
@Override
public int getType() {
return Value.STRING_FIXED;
......@@ -42,7 +71,42 @@ public class ValueStringFixed extends ValueString {
* @return the value
*/
public static ValueStringFixed get(String s) {
s = trimRight(s);
// Use the special precision constant PRECISION_TRIM to indicate
// default H2 behaviour of trimming the value.
return get(s, PRECISION_TRIM, null);
}
/**
* Get or create a fixed length string value for the given string.
* <p>
* This method will use a {@link Mode}-specific conversion when <code>mode</code> is not <code>null</code>.
* Otherwise it will use the default H2 behaviour of trimming the given string if <code>precision</code>
* is not {@link #PRECISION_DO_NOT_TRIM}.
*
* @param s the string
* @param precision if the {@link Mode#padFixedLengthStrings} indicates that strings should be padded, this
* defines the overall length of the (potentially padded) string.
* If the special constant {@link #PRECISION_DO_NOT_TRIM} is used the value will not be trimmed.
* @return the value
*/
public static ValueStringFixed get(String s, int precision, Mode mode) {
// Should fixed strings be padded?
if (mode != null && mode.padFixedLengthStrings) {
if (precision == Integer.MAX_VALUE) {
// CHAR without a length specification is identical to CHAR(1)
precision = 1;
}
if (s.length() < precision) {
// We have to pad
s = rightPadWithSpaces(s, precision);
} else {
// We should trim, because inserting 'A ' into a CHAR(1) is possible!
s = trimRight(s, precision);
}
} else if (precision != PRECISION_DO_NOT_TRIM) {
// Default behaviour of H2
s = trimRight(s);
}
if (s.length() == 0) {
return EMPTY;
}
......
......@@ -13,6 +13,7 @@ import java.sql.Timestamp;
import java.util.Calendar;
import java.util.TimeZone;
import org.h2.api.ErrorCode;
import org.h2.engine.Mode;
import org.h2.message.DbException;
import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils;
......@@ -109,26 +110,53 @@ public class ValueTimestamp extends Value {
/**
* Parse a string to a ValueTimestamp. This method supports the format
* +/-year-month-day hour:minute:seconds.fractional and an optional timezone
* +/-year-month-day hour[:.]minute[:.]seconds.fractional and an optional timezone
* part.
*
* @param s the string to parse
* @return the date
*/
public static ValueTimestamp parse(String s) {
return parse(s, null);
}
/**
* Parse a string to a ValueTimestamp, using the given {@link Mode}.
* This method supports the format +/-year-month-day[ -]hour[:.]minute[:.]seconds.fractional
* and an optional timezone part.
*
* @param s the string to parse
* @param mode the database {@link Mode}
* @return the date
*/
public static ValueTimestamp parse(String s, Mode mode) {
try {
return parseTry(s);
return parseTry(s, mode);
} catch (Exception e) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
e, "TIMESTAMP", s);
}
}
private static ValueTimestamp parseTry(String s) {
/**
* See: https://stackoverflow.com/questions/3976616/how-to-find-nth-occurrence-of-character-in-a-string#answer-3976656
*/
private static int findNthIndexOf(String str, char chr, int n) {
int pos = str.indexOf(chr);
while (--n > 0 && pos != -1)
pos = str.indexOf(chr, pos + 1);
return pos;
}
private static ValueTimestamp parseTry(String s, Mode mode) {
int dateEnd = s.indexOf(' ');
if (dateEnd < 0) {
// ISO 8601 compatibility
dateEnd = s.indexOf('T');
if (dateEnd < 0 && mode != null && mode.allowDB2TimestampFormat) {
// DB2 also allows dash between date and time
dateEnd = findNthIndexOf(s, '-', 3);
}
}
int timeStart;
if (dateEnd < 0) {
......@@ -148,9 +176,9 @@ public class ValueTimestamp extends Value {
tz = TimeZone.getTimeZone("UTC");
timeEnd--;
} else {
int timeZoneStart = s.indexOf('+', dateEnd);
int timeZoneStart = s.indexOf('+', dateEnd + 1);
if (timeZoneStart < 0) {
timeZoneStart = s.indexOf('-', dateEnd);
timeZoneStart = s.indexOf('-', dateEnd + 1);
}
if (timeZoneStart >= 0) {
String tzName = "GMT" + s.substring(timeZoneStart);
......
......@@ -7,6 +7,7 @@
CREATE TABLE VERSION(ID INT PRIMARY KEY, VERSION VARCHAR, CREATED VARCHAR);
INSERT INTO VERSION VALUES
(146, '1.4.197', '2017-06-10'),
(145, '1.4.195', '2017-04-23'),
(144, '1.4.194', '2017-03-10'),
(143, '1.4.193', '2016-10-31'),
......
......@@ -245,6 +245,34 @@ public class TestCompatibility extends TestBase {
assertResult("ABC", stat, "SELECT SUBSTRING('ABCDEF' FOR 3)");
assertResult("ABCD", stat, "SELECT SUBSTRING('0ABCDEF' FROM 2 FOR 4)");
/* Test right-padding of CHAR(N) at INSERT */
stat.execute("CREATE TABLE TEST(CH CHAR(10))");
stat.execute("INSERT INTO TEST (CH) VALUES ('Hello')");
assertResult("Hello ", stat, "SELECT CH FROM TEST");
/* Test that WHERE clauses accept unpadded values and will pad before comparison */
assertResult("Hello ", stat, "SELECT CH FROM TEST WHERE CH = 'Hello'");
/* Test CHAR which is identical to CHAR(1) */
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("CREATE TABLE TEST(CH CHAR)");
stat.execute("INSERT INTO TEST (CH) VALUES ('')");
assertResult(" ", stat, "SELECT CH FROM TEST");
assertResult(" ", stat, "SELECT CH FROM TEST WHERE CH = ''");
/* Test that excessive spaces are trimmed */
stat.execute("DELETE FROM TEST");
stat.execute("INSERT INTO TEST (CH) VALUES ('1 ')");
assertResult("1", stat, "SELECT CH FROM TEST");
assertResult("1", stat, "SELECT CH FROM TEST WHERE CH = '1 '");
/* Test that we do not trim too far */
stat.execute("DROP TABLE IF EXISTS TEST");
stat.execute("CREATE TABLE TEST(CH CHAR(2))");
stat.execute("INSERT INTO TEST (CH) VALUES ('1 ')");
assertResult("1 ", stat, "SELECT CH FROM TEST");
assertResult("1 ", stat, "SELECT CH FROM TEST WHERE CH = '1 '");
}
private void testMySQL() throws SQLException {
......@@ -469,6 +497,14 @@ public class TestCompatibility extends TestBase {
"fetch next 2 rows only with rs use and keep update locks");
res = stat.executeQuery("select * from test order by id " +
"fetch next 2 rows only with rr use and keep exclusive locks");
// Test DB2 TIMESTAMP format with dash separating date and time
stat.execute("drop table test if exists");
stat.execute("create table test(date TIMESTAMP)");
stat.executeUpdate("insert into test (date) values ('2014-04-05-09.48.28.020005')");
assertResult("2014-04-05 09:48:28.020005", stat, "select date from test"); // <- result is always H2 format timestamp!
assertResult("2014-04-05 09:48:28.020005", stat, "select date from test where date = '2014-04-05-09.48.28.020005'");
assertResult("2014-04-05 09:48:28.020005", stat, "select date from test where date = '2014-04-05 09:48:28.020005'");
}
private void testDerby() throws SQLException {
......
......@@ -16,7 +16,7 @@ import org.h2.test.TestBase;
*/
public class TestGeneralCommonTableQueries extends TestBase {
/**
/**
* Run just this test.
*
* @param a ignored
......@@ -30,6 +30,8 @@ public class TestGeneralCommonTableQueries extends TestBase {
testSimple();
testImpliedColumnNames();
testChainedQuery();
testParameterizedQuery();
testNumberedParameterizedQuery();
}
private void testSimple() throws Exception {
......@@ -43,14 +45,14 @@ public class TestGeneralCommonTableQueries extends TestBase {
final String simple_two_column_query = "with " +
"t1(n) as (select 1 as first) " +
",t2(n) as (select 2 as first) " +
"select * from t1 union all select * from t2";
"select * from t1 union all select * from t2";
rs = stat.executeQuery(simple_two_column_query);
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertFalse(rs.next());
prep = conn.prepareStatement(simple_two_column_query);
rs = prep.executeQuery();
assertTrue(rs.next());
......@@ -58,7 +60,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertFalse(rs.next());
prep = conn.prepareStatement("with " +
"t1(n) as (select 2 as first) " +
",t2(n) as (select 3 as first) " +
......@@ -70,7 +72,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertTrue(rs.next());
assertEquals(3, rs.getInt(1));
assertFalse(rs.next());
prep = conn.prepareStatement("with " +
"t1(n) as (select 2 as first) " +
",t2(n) as (select 3 as first) " +
......@@ -83,7 +85,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertTrue(rs.next());
assertEquals(3, rs.getInt(1));
assertFalse(rs.next());
conn.close();
deleteDb("commonTableExpressionQueries");
}
......@@ -93,7 +95,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep;
ResultSet rs;
prep = conn.prepareStatement("with " +
"t1 as (select 2 as first_col) " +
",t2 as (select first_col+1 from t1) " +
......@@ -108,30 +110,107 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertFalse(rs.next());
assertEquals(rs.getMetaData().getColumnCount(),1);
assertEquals("FIRST_COL",rs.getMetaData().getColumnLabel(1));
conn.close();
deleteDb("commonTableExpressionQueries");
}
private void testChainedQuery() throws Exception {
deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep;
ResultSet rs;
prep = conn.prepareStatement(" WITH t1 AS ("
+" SELECT 1 AS FIRST_COLUMN"
+"),"
+" t2 AS ("
+" SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM t1 "
+") "
+"SELECT sum(FIRST_COLUMN) FROM t2");
prep = conn.prepareStatement(
" WITH t1 AS (" +
" SELECT 1 AS FIRST_COLUMN" +
")," +
" t2 AS (" +
" SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM t1 " +
") " +
"SELECT sum(FIRST_COLUMN) FROM t2");
rs = prep.executeQuery();
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertFalse(rs.next());
conn.close();
deleteDb("commonTableExpressionQueries");
}
private void testParameterizedQuery() throws Exception {
deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep;
ResultSet rs;
prep = conn.prepareStatement("WITH t1 AS (" +
" SELECT X, 'T1' FROM SYSTEM_RANGE(?,?)" +
")," +
"t2 AS (" +
" SELECT X, 'T2' FROM SYSTEM_RANGE(?,?)" +
") " +
"SELECT * FROM t1 UNION ALL SELECT * FROM t2 " +
"UNION ALL SELECT X, 'Q' FROM SYSTEM_RANGE(?,?)");
prep.setInt(1, 1);
prep.setInt(2, 2);
prep.setInt(3, 3);
prep.setInt(4, 4);
prep.setInt(5, 5);
prep.setInt(6, 6);
rs = prep.executeQuery();
for(int n: new int[]{1,2,3,4,5,6} ){
assertTrue(rs.next());
assertEquals(n, rs.getInt(1));
}
assertFalse(rs.next());
// call it twice
rs = prep.executeQuery();
for(int n: new int[]{1,2,3,4,5,6} ){
assertTrue(rs.next());
assertEquals(n, rs.getInt(1));
}
assertFalse(rs.next());
conn.close();
deleteDb("commonTableExpressionQueries");
}
}
private void testNumberedParameterizedQuery() throws Exception {
deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep;
ResultSet rs;
prep = conn.prepareStatement("WITH t1 AS ("
+" SELECT R.X, 'T1' FROM SYSTEM_RANGE(?1,?2) R"
+"),"
+"t2 AS ("
+" SELECT R.X, 'T2' FROM SYSTEM_RANGE(?3,?4) R"
+") "
+"SELECT * FROM t1 UNION ALL SELECT * FROM t2 UNION ALL SELECT X, 'Q' FROM SYSTEM_RANGE(?5,?6)");
prep.setInt(1, 1);
prep.setInt(2, 2);
prep.setInt(3, 3);
prep.setInt(4, 4);
prep.setInt(5, 5);
prep.setInt(6, 6);
rs = prep.executeQuery();
for (int n : new int[] { 1, 2, 3, 4, 5, 6 }) {
assertTrue(rs.next());
assertEquals(n, rs.getInt(1));
}
assertEquals("X",rs.getMetaData().getColumnLabel(1));
assertEquals("'T1'",rs.getMetaData().getColumnLabel(2));
assertFalse(rs.next());
conn.close();
deleteDb("commonTableExpressionQueries");
}
}
......@@ -50,6 +50,8 @@ public class TestQueryCache extends TestBase {
}
String query = queryBuilder.toString();
conn.prepareStatement(query);
int firstGreater = 0;
int firstSmaller = 0;
long time;
ResultSet rs;
long first = 0;
......@@ -77,9 +79,17 @@ public class TestQueryCache extends TestBase {
// try to avoid pauses in subsequent iterations
System.gc();
} else if (i > 1001) {
assertSmaller(time, first);
if (first > time) {
firstGreater++;
} else {
firstSmaller++;
}
}
}
// first prepare time must be always greater because of query cache,
// but JVM is too unpredictable to assert that, so just check that
// usually this is true
assertSmaller(firstSmaller, firstGreater);
stat.execute("drop table test");
conn.close();
}
......
......@@ -682,6 +682,15 @@ public class TestPreparedStatement extends TestBase {
rs.next();
Object offsetDateTime2 = rs.getObject(1, LocalDateTimeUtils.getOffsetDateTimeClass());
assertEquals(offsetDateTime, offsetDateTime2);
assertFalse(rs.next());
rs.close();
prep.setObject(1, offsetDateTime, 2014); // Types.TIMESTAMP_WITH_TIMEZONE
rs = prep.executeQuery();
rs.next();
offsetDateTime2 = rs.getObject(1, LocalDateTimeUtils.getOffsetDateTimeClass());
assertEquals(offsetDateTime, offsetDateTime2);
assertFalse(rs.next());
rs.close();
}
......
......@@ -7,7 +7,9 @@ package org.h2.test.mvcc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import org.h2.api.ErrorCode;
......@@ -30,6 +32,7 @@ public class TestMvccMultiThreaded extends TestBase {
@Override
public void test() throws Exception {
testConcurrentSelectForUpdate();
testMergeWithUniqueKeyViolation();
// not supported currently
if (!config.multiThreaded) {
......@@ -38,6 +41,50 @@ public class TestMvccMultiThreaded extends TestBase {
}
}
private void testConcurrentSelectForUpdate() throws Exception {
deleteDb(getTestName());
Connection conn = getConnection(getTestName() + ";MULTI_THREADED=TRUE");
Statement stat = conn.createStatement();
stat.execute("create table test(id int not null primary key, updated int not null)");
stat.execute("insert into test(id, updated) values(1, 100)");
ArrayList<Task> tasks = new ArrayList<>();
int count = 3;
for (int i = 0; i < count; i++) {
Task task = new Task() {
@Override
public void call() throws Exception {
Connection conn = getConnection(getTestName());
Statement stat = conn.createStatement();
try {
while (!stop) {
try {
stat.execute("select * from test where id=1 for update");
} catch (SQLException e) {
int errorCode = e.getErrorCode();
assertEquals(e.getMessage(),
errorCode == ErrorCode.DEADLOCK_1 ||
errorCode == ErrorCode.LOCK_TIMEOUT_1);
}
}
} finally {
conn.close();
}
}
}.execute();
tasks.add(task);
}
for (int i = 0; i < 10; i++) {
Thread.sleep(100);
ResultSet rs = stat.executeQuery("select * from test");
assertTrue(rs.next());
}
for (Task t : tasks) {
t.get();
}
conn.close();
deleteDb(getTestName());
}
private void testMergeWithUniqueKeyViolation() throws Exception {
deleteDb(getTestName());
Connection conn = getConnection(getTestName());
......
......@@ -736,3 +736,4 @@ arbonaut exposing obscure determined turkey buildings indexhints acct
choosing optimise arte preparator katzyn bla jenkins tot artes pgserver npe
suffers closeablem mni significance vise identiy vitalus aka ilike uppercasing reentrant
aff ignite warm upstream producing sfu jit smtm affinity stashed tbl
stumc numbered
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论