提交 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 @@ ...@@ -11,5 +11,9 @@
/test.out.txt /test.out.txt
.DS_Store .DS_Store
/.idea/ /.idea/
*.iml
*.ipr
*.iws
.checkstyle .checkstyle
/temp/ /temp/
/h2web/
\ No newline at end of file
sudo: false
language: java language: java
jdk:
- openjdk7
- oraclejdk8
before_script: cd h2
script: ./build.sh jar testFast script: ./build.sh jar testFast
cache: cache:
directories: directories:
- $HOME/.m2/repository - $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 @@ ...@@ -4,7 +4,7 @@
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
<version>1.4.195-SNAPSHOT</version> <version>1.4.196-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>H2 Database Engine</name> <name>H2 Database Engine</name>
<url>http://www.h2database.com</url> <url>http://www.h2database.com</url>
......
...@@ -197,12 +197,10 @@ WITH [ RECURSIVE ] { name [( columnName [,...] )] ...@@ -197,12 +197,10 @@ WITH [ RECURSIVE ] { name [( columnName [,...] )]
AS ( select ) [,...] } AS ( select ) [,...] }
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. 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 referred to by name.
One or more common table entries can be use referred to by name..
Column name declarations are now optional - the column names will be inferred from the named select queries. 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 ( WITH RECURSIVE t(n) AS (
SELECT 1 SELECT 1
...@@ -215,8 +213,7 @@ SELECT sum(n) FROM t; ...@@ -215,8 +213,7 @@ SELECT sum(n) FROM t;
"," ","
WITH t1 AS ( WITH t1 AS (
SELECT 1 AS FIRST_COLUMN SELECT 1 AS FIRST_COLUMN
), ), t2 AS (
t2 AS (
SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM t1 SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM t1
) )
SELECT sum(FIRST_COLUMN) FROM t2; SELECT sum(FIRST_COLUMN) FROM t2;
......
...@@ -23,6 +23,14 @@ Change Log ...@@ -23,6 +23,14 @@ Change Log
<ul> <ul>
<li>Fix bug in parsing ANALYZE TABLE xxx SAMPLE_SIZE yyy <li>Fix bug in parsing ANALYZE TABLE xxx SAMPLE_SIZE yyy
</li> </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>Issue#479 Allow non-recursive CTEs (WITH statements), patch from stumc
</li> </li>
<li>Fix startup issue when using "CHECK" as a column name. <li>Fix startup issue when using "CHECK" as a column name.
...@@ -37,6 +45,8 @@ Change Log ...@@ -37,6 +45,8 @@ Change Log
</li> </li>
<li>Issue #479: Allow non-recursive Common Table Expressions (CTE) <li>Issue #479: Allow non-recursive Common Table Expressions (CTE)
</li> </li>
<li>On Mac OS X, with IPv6 and no network connection, the Console tool was not working as expected.
</li>
</ul> </ul>
<h2>Version 1.4.195 (2017-04-23)</h2> <h2>Version 1.4.195 (2017-04-23)</h2>
......
...@@ -28,7 +28,7 @@ Add to http://twitter.com ...@@ -28,7 +28,7 @@ Add to http://twitter.com
Sign files and publish files on Maven Central Sign files and publish files on Maven Central
(check java version is 1.7) (check java version is 1.7)
./build.sh compile jar mavenDeployCentral ./build.sh clean compile jar mavenDeployCentral
cd /data/h2database/m2-repo/com/h2database cd /data/h2database/m2-repo/com/h2database
# remove sha and md5 files: # remove sha and md5 files:
find . -name "*.sha1" -delete find . -name "*.sha1" -delete
......
...@@ -2906,7 +2906,7 @@ public class Parser { ...@@ -2906,7 +2906,7 @@ public class Parser {
String timestamp = currentValue.getString(); String timestamp = currentValue.getString();
read(); read();
r = ValueExpression r = ValueExpression
.get(ValueTimestamp.parse(timestamp)); .get(ValueTimestamp.parse(timestamp, session.getDatabase().getMode()));
} else if (equalsToken("X", name)) { } else if (equalsToken("X", name)) {
read(); read();
byte[] buffer = StringUtils byte[] buffer = StringUtils
......
...@@ -140,7 +140,7 @@ public class Insert extends Prepared implements ResultTarget { ...@@ -140,7 +140,7 @@ public class Insert extends Prepared implements ResultTarget {
// e can be null (DEFAULT) // e can be null (DEFAULT)
e = e.optimize(session); e = e.optimize(session);
try { try {
Value v = c.convert(e.getValue(session)); Value v = c.convert(e.getValue(session), session.getDatabase().getMode());
newRow.setValue(index, v); newRow.setValue(index, v);
} catch (DbException ex) { } catch (DbException ex) {
throw setRow(ex, x, getSQL(expr)); throw setRow(ex, x, getSQL(expr));
...@@ -186,7 +186,7 @@ public class Insert extends Prepared implements ResultTarget { ...@@ -186,7 +186,7 @@ public class Insert extends Prepared implements ResultTarget {
Column c = columns[j]; Column c = columns[j];
int index = c.getColumnId(); int index = c.getColumnId();
try { try {
Value v = c.convert(values[j]); Value v = c.convert(values[j], session.getDatabase().getMode());
newRow.setValue(index, v); newRow.setValue(index, v);
} catch (DbException ex) { } catch (DbException ex) {
throw setRow(ex, rowNumber, getSQL(values)); throw setRow(ex, rowNumber, getSQL(values));
......
...@@ -16,22 +16,22 @@ public class Constants { ...@@ -16,22 +16,22 @@ public class Constants {
/** /**
* The build date is updated for each public release. * 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. * 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. * 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. * 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. * Whether this is a snapshot version.
......
...@@ -439,7 +439,7 @@ public class FunctionAlias extends SchemaObjectBase { ...@@ -439,7 +439,7 @@ public class FunctionAlias extends SchemaObjectBase {
} }
o = objArray; o = objArray;
} else { } else {
v = v.convertTo(type); v = v.convertTo(type, -1, session.getDatabase().getMode());
o = v.getObject(); o = v.getObject();
} }
if (o == null) { if (o == null) {
......
...@@ -160,6 +160,16 @@ public class Mode { ...@@ -160,6 +160,16 @@ public class Mode {
*/ */
public boolean allowAffinityKey; 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; private final String name;
static { static {
...@@ -179,6 +189,7 @@ public class Mode { ...@@ -179,6 +189,7 @@ public class Mode {
Pattern.compile("ApplicationName|ClientAccountingInformation|" + Pattern.compile("ApplicationName|ClientAccountingInformation|" +
"ClientUser|ClientCorrelationToken"); "ClientUser|ClientCorrelationToken");
mode.prohibitEmptyInPredicate = true; mode.prohibitEmptyInPredicate = true;
mode.allowDB2TimestampFormat = true;
add(mode); add(mode);
mode = new Mode("Derby"); mode = new Mode("Derby");
...@@ -256,6 +267,7 @@ public class Mode { ...@@ -256,6 +267,7 @@ public class Mode {
mode.supportedClientInfoPropertiesRegEx = mode.supportedClientInfoPropertiesRegEx =
Pattern.compile("ApplicationName"); Pattern.compile("ApplicationName");
mode.prohibitEmptyInPredicate = true; mode.prohibitEmptyInPredicate = true;
mode.padFixedLengthStrings = true;
add(mode); add(mode);
mode = new Mode("Ignite"); mode = new Mode("Ignite");
......
...@@ -13,6 +13,7 @@ import org.h2.index.IndexCondition; ...@@ -13,6 +13,7 @@ import org.h2.index.IndexCondition;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.table.ColumnResolver; import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.util.MathUtils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueBoolean; import org.h2.value.ValueBoolean;
...@@ -201,7 +202,7 @@ public class Comparison extends Condition { ...@@ -201,7 +202,7 @@ public class Comparison extends Condition {
// to constant type, but vise versa, then let's do this here // to constant type, but vise versa, then let's do this here
// once. // once.
if (constType != resType) { 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) { } else if (right instanceof Parameter) {
((Parameter) right).setColumn( ((Parameter) right).setColumn(
......
...@@ -1263,7 +1263,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -1263,7 +1263,7 @@ public class Function extends Expression implements FunctionCall {
} else if (v0.getType() == Value.STRING) { } else if (v0.getType() == Value.STRING) {
ValueString vd = (ValueString) v0; ValueString vd = (ValueString) v0;
Calendar c = Calendar.getInstance(); 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.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0); c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0); c.set(Calendar.SECOND, 0);
......
...@@ -71,7 +71,7 @@ WITH [ RECURSIVE ] { name [( columnName [,...] )] ...@@ -71,7 +71,7 @@ WITH [ RECURSIVE ] { name [( columnName [,...] )]
AS ( select ) [,...] } AS ( select ) [,...] }
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"," "Commands (DDL)","ALTER INDEX RENAME","
ALTER INDEX [ IF EXISTS ] indexName RENAME TO newIndexName ALTER INDEX [ IF EXISTS ] indexName RENAME TO newIndexName
"," ","
......
...@@ -163,8 +163,21 @@ public class Column { ...@@ -163,8 +163,21 @@ public class Column {
* @return the value * @return the value
*/ */
public Value convert(Value v) { 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 { try {
return v.convertTo(type); return v.convertTo(type, MathUtils.convertLongToInt(precision), mode);
} catch (DbException e) { } catch (DbException e) {
if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) { if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) {
String target = (table == null ? "" : table.getName() + ": ") + String target = (table == null ? "" : table.getName() + ": ") +
......
...@@ -144,7 +144,7 @@ public class RangeTable extends Table { ...@@ -144,7 +144,7 @@ public class RangeTable extends Table {
@Override @Override
public TableType getTableType() { public TableType getTableType() {
return TableType.SYSTEM_TABLE; return TableType.SYSTEM_TABLE;
} }
@Override @Override
......
...@@ -207,7 +207,8 @@ public class TableView extends Table { ...@@ -207,7 +207,8 @@ public class TableView extends Table {
// If it can't be compiled, then it's a 'zero column table' // If it can't be compiled, then it's a 'zero column table'
// this avoids problems when creating the view when opening the // this avoids problems when creating the view when opening the
// database. // 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)) { if (isRecursiveQueryExceptionDetected(createException)) {
this.isRecursiveQueryDetected = true; this.isRecursiveQueryDetected = true;
} }
...@@ -673,6 +674,8 @@ public class TableView extends Table { ...@@ -673,6 +674,8 @@ public class TableView extends Table {
/** /**
* Was query recursion detected during compiling. * Was query recursion detected during compiling.
*
* @return true if yes
*/ */
public boolean isRecursiveQueryDetected() { public boolean isRecursiveQueryDetected() {
return isRecursiveQueryDetected; return isRecursiveQueryDetected;
......
...@@ -44,12 +44,9 @@ public class NetUtils { ...@@ -44,12 +44,9 @@ public class NetUtils {
*/ */
public static Socket createLoopbackSocket(int port, boolean ssl) public static Socket createLoopbackSocket(int port, boolean ssl)
throws IOException { throws IOException {
InetAddress address = getBindAddress(); String local = getLocalAddress();
if (address == null) {
address = InetAddress.getLocalHost();
}
try { try {
return createSocket(getHostAddress(address), port, ssl); return createSocket(local, port, ssl);
} catch (IOException e) { } catch (IOException e) {
try { try {
return createSocket("localhost", port, ssl); return createSocket("localhost", port, ssl);
...@@ -60,23 +57,6 @@ public class NetUtils { ...@@ -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. * Create a client socket that is connected to the given address and port.
* *
...@@ -274,7 +254,21 @@ public class NetUtils { ...@@ -274,7 +254,21 @@ public class NetUtils {
throw DbException.convert(e); 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")) { if (address.equals("127.0.0.1")) {
address = "localhost"; address = "localhost";
} }
......
...@@ -902,6 +902,8 @@ public class DataType { ...@@ -902,6 +902,8 @@ public class DataType {
return Value.TIME; return Value.TIME;
case Types.TIMESTAMP: case Types.TIMESTAMP:
return Value.TIMESTAMP; return Value.TIMESTAMP;
case 2014: // Types.TIMESTAMP_WITH_TIMEZONE
return Value.TIMESTAMP_TZ;
case Types.BLOB: case Types.BLOB:
return Value.BLOB; return Value.BLOB;
case Types.CLOB: case Types.CLOB:
......
...@@ -615,7 +615,7 @@ public class Transfer { ...@@ -615,7 +615,7 @@ public class Transfer {
case Value.STRING_IGNORECASE: case Value.STRING_IGNORECASE:
return ValueStringIgnoreCase.get(readString()); return ValueStringIgnoreCase.get(readString());
case Value.STRING_FIXED: case Value.STRING_FIXED:
return ValueStringFixed.get(readString()); return ValueStringFixed.get(readString(), ValueStringFixed.PRECISION_DO_NOT_TRIM, null);
case Value.BLOB: { case Value.BLOB: {
long length = readLong(); long length = readLong();
if (version >= Constants.TCP_PROTOCOL_VERSION_11) { if (version >= Constants.TCP_PROTOCOL_VERSION_11) {
......
...@@ -20,6 +20,7 @@ import java.sql.Timestamp; ...@@ -20,6 +20,7 @@ import java.sql.Timestamp;
import java.sql.Types; import java.sql.Types;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.store.DataHandler; import org.h2.store.DataHandler;
...@@ -541,6 +542,21 @@ public abstract class Value { ...@@ -541,6 +542,21 @@ public abstract class Value {
* @return the converted value * @return the converted value
*/ */
public Value convertTo(int targetType) { 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 NULL is done in ValueNull
// converting BLOB to CLOB and vice versa is done in ValueLob // converting BLOB to CLOB and vice versa is done in ValueLob
if (getType() == targetType) { if (getType() == targetType) {
...@@ -947,7 +963,7 @@ public abstract class Value { ...@@ -947,7 +963,7 @@ public abstract class Value {
case DATE: case DATE:
return ValueDate.parse(s.trim()); return ValueDate.parse(s.trim());
case TIMESTAMP: case TIMESTAMP:
return ValueTimestamp.parse(s.trim()); return ValueTimestamp.parse(s.trim(), mode);
case TIMESTAMP_TZ: case TIMESTAMP_TZ:
return ValueTimestampTimeZone.parse(s.trim()); return ValueTimestampTimeZone.parse(s.trim());
case BYTES: case BYTES:
...@@ -962,7 +978,7 @@ public abstract class Value { ...@@ -962,7 +978,7 @@ public abstract class Value {
case STRING_IGNORECASE: case STRING_IGNORECASE:
return ValueStringIgnoreCase.get(s); return ValueStringIgnoreCase.get(s);
case STRING_FIXED: case STRING_FIXED:
return ValueStringFixed.get(s); return ValueStringFixed.get(s, precision, mode);
case DOUBLE: case DOUBLE:
return ValueDouble.get(Double.parseDouble(s.trim())); return ValueDouble.get(Double.parseDouble(s.trim()));
case FLOAT: case FLOAT:
......
...@@ -12,6 +12,8 @@ import java.util.Arrays; ...@@ -12,6 +12,8 @@ import java.util.Arrays;
import com.vividsolutions.jts.geom.CoordinateSequence; import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter; import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.PrecisionModel; import com.vividsolutions.jts.geom.PrecisionModel;
import org.h2.engine.Mode;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Envelope;
...@@ -272,11 +274,11 @@ public class ValueGeometry extends Value { ...@@ -272,11 +274,11 @@ public class ValueGeometry extends Value {
} }
@Override @Override
public Value convertTo(int targetType) { public Value convertTo(int targetType, int precision, Mode mode) {
if (targetType == Value.JAVA_OBJECT) { if (targetType == Value.JAVA_OBJECT) {
return this; return this;
} }
return super.convertTo(targetType); return super.convertTo(targetType, precision, mode);
} }
/** /**
......
...@@ -14,6 +14,7 @@ import java.io.Reader; ...@@ -14,6 +14,7 @@ import java.io.Reader;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
...@@ -443,7 +444,7 @@ public class ValueLob extends Value { ...@@ -443,7 +444,7 @@ public class ValueLob extends Value {
* @return the converted value * @return the converted value
*/ */
@Override @Override
public Value convertTo(int t) { public Value convertTo(int t, int precision, Mode mode) {
if (t == type) { if (t == type) {
return this; return this;
} else if (t == Value.CLOB) { } else if (t == Value.CLOB) {
...@@ -453,7 +454,7 @@ public class ValueLob extends Value { ...@@ -453,7 +454,7 @@ public class ValueLob extends Value {
ValueLob copy = ValueLob.createBlob(getInputStream(), -1, handler); ValueLob copy = ValueLob.createBlob(getInputStream(), -1, handler);
return copy; return copy;
} }
return super.convertTo(t); return super.convertTo(t, precision, mode);
} }
@Override @Override
......
...@@ -14,6 +14,7 @@ import java.io.Reader; ...@@ -14,6 +14,7 @@ import java.io.Reader;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
...@@ -184,7 +185,7 @@ public class ValueLobDb extends Value implements Value.ValueClob, ...@@ -184,7 +185,7 @@ public class ValueLobDb extends Value implements Value.ValueClob,
* @return the converted value * @return the converted value
*/ */
@Override @Override
public Value convertTo(int t) { public Value convertTo(int t, int precision, Mode mode) {
if (t == type) { if (t == type) {
return this; return this;
} else if (t == Value.CLOB) { } else if (t == Value.CLOB) {
...@@ -204,7 +205,7 @@ public class ValueLobDb extends Value implements Value.ValueClob, ...@@ -204,7 +205,7 @@ public class ValueLobDb extends Value implements Value.ValueClob,
return ValueLobDb.createSmallLob(t, small); return ValueLobDb.createSmallLob(t, small);
} }
} }
return super.convertTo(t); return super.convertTo(t, precision, mode);
} }
@Override @Override
......
...@@ -14,6 +14,7 @@ import java.sql.SQLException; ...@@ -14,6 +14,7 @@ import java.sql.SQLException;
import java.sql.Time; import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import org.h2.engine.Mode;
import org.h2.message.DbException; import org.h2.message.DbException;
/** /**
...@@ -132,7 +133,7 @@ public class ValueNull extends Value { ...@@ -132,7 +133,7 @@ public class ValueNull extends Value {
} }
@Override @Override
public Value convertTo(int type) { public Value convertTo(int type, int precision, Mode mode) {
return this; return this;
} }
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
*/ */
package org.h2.value; package org.h2.value;
import java.util.Arrays;
import org.h2.engine.Mode;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
...@@ -13,6 +16,18 @@ import org.h2.util.StringUtils; ...@@ -13,6 +16,18 @@ import org.h2.util.StringUtils;
*/ */
public class ValueStringFixed extends ValueString { 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(""); private static final ValueStringFixed EMPTY = new ValueStringFixed("");
protected ValueStringFixed(String value) { protected ValueStringFixed(String value) {
...@@ -20,15 +35,29 @@ public class ValueStringFixed extends ValueString { ...@@ -20,15 +35,29 @@ public class ValueStringFixed extends ValueString {
} }
private static String trimRight(String s) { private static String trimRight(String s) {
return trimRight(s, 0);
}
private static String trimRight(String s, int minLength) {
int endIndex = s.length() - 1; int endIndex = s.length() - 1;
int i = endIndex; int i = endIndex;
while (i >= 0 && s.charAt(i) == ' ') { while (i >= minLength && s.charAt(i) == ' ') {
i--; i--;
} }
s = i == endIndex ? s : s.substring(0, i + 1); s = i == endIndex ? s : s.substring(0, i + 1);
return s; 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 @Override
public int getType() { public int getType() {
return Value.STRING_FIXED; return Value.STRING_FIXED;
...@@ -42,7 +71,42 @@ public class ValueStringFixed extends ValueString { ...@@ -42,7 +71,42 @@ public class ValueStringFixed extends ValueString {
* @return the value * @return the value
*/ */
public static ValueStringFixed get(String s) { 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) { if (s.length() == 0) {
return EMPTY; return EMPTY;
} }
......
...@@ -13,6 +13,7 @@ import java.sql.Timestamp; ...@@ -13,6 +13,7 @@ import java.sql.Timestamp;
import java.util.Calendar; import java.util.Calendar;
import java.util.TimeZone; import java.util.TimeZone;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Mode;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.util.DateTimeUtils; import org.h2.util.DateTimeUtils;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
...@@ -109,26 +110,53 @@ public class ValueTimestamp extends Value { ...@@ -109,26 +110,53 @@ public class ValueTimestamp extends Value {
/** /**
* Parse a string to a ValueTimestamp. This method supports the format * 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. * part.
* *
* @param s the string to parse * @param s the string to parse
* @return the date * @return the date
*/ */
public static ValueTimestamp parse(String s) { 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 { try {
return parseTry(s); return parseTry(s, mode);
} catch (Exception e) { } catch (Exception e) {
throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2,
e, "TIMESTAMP", s); 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(' '); int dateEnd = s.indexOf(' ');
if (dateEnd < 0) { if (dateEnd < 0) {
// ISO 8601 compatibility // ISO 8601 compatibility
dateEnd = s.indexOf('T'); 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; int timeStart;
if (dateEnd < 0) { if (dateEnd < 0) {
...@@ -148,9 +176,9 @@ public class ValueTimestamp extends Value { ...@@ -148,9 +176,9 @@ public class ValueTimestamp extends Value {
tz = TimeZone.getTimeZone("UTC"); tz = TimeZone.getTimeZone("UTC");
timeEnd--; timeEnd--;
} else { } else {
int timeZoneStart = s.indexOf('+', dateEnd); int timeZoneStart = s.indexOf('+', dateEnd + 1);
if (timeZoneStart < 0) { if (timeZoneStart < 0) {
timeZoneStart = s.indexOf('-', dateEnd); timeZoneStart = s.indexOf('-', dateEnd + 1);
} }
if (timeZoneStart >= 0) { if (timeZoneStart >= 0) {
String tzName = "GMT" + s.substring(timeZoneStart); String tzName = "GMT" + s.substring(timeZoneStart);
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
CREATE TABLE VERSION(ID INT PRIMARY KEY, VERSION VARCHAR, CREATED VARCHAR); CREATE TABLE VERSION(ID INT PRIMARY KEY, VERSION VARCHAR, CREATED VARCHAR);
INSERT INTO VERSION VALUES INSERT INTO VERSION VALUES
(146, '1.4.197', '2017-06-10'),
(145, '1.4.195', '2017-04-23'), (145, '1.4.195', '2017-04-23'),
(144, '1.4.194', '2017-03-10'), (144, '1.4.194', '2017-03-10'),
(143, '1.4.193', '2016-10-31'), (143, '1.4.193', '2016-10-31'),
......
...@@ -245,6 +245,34 @@ public class TestCompatibility extends TestBase { ...@@ -245,6 +245,34 @@ public class TestCompatibility extends TestBase {
assertResult("ABC", stat, "SELECT SUBSTRING('ABCDEF' FOR 3)"); assertResult("ABC", stat, "SELECT SUBSTRING('ABCDEF' FOR 3)");
assertResult("ABCD", stat, "SELECT SUBSTRING('0ABCDEF' FROM 2 FOR 4)"); 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 { private void testMySQL() throws SQLException {
...@@ -469,6 +497,14 @@ public class TestCompatibility extends TestBase { ...@@ -469,6 +497,14 @@ public class TestCompatibility extends TestBase {
"fetch next 2 rows only with rs use and keep update locks"); "fetch next 2 rows only with rs use and keep update locks");
res = stat.executeQuery("select * from test order by id " + res = stat.executeQuery("select * from test order by id " +
"fetch next 2 rows only with rr use and keep exclusive locks"); "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 { private void testDerby() throws SQLException {
......
...@@ -16,7 +16,7 @@ import org.h2.test.TestBase; ...@@ -16,7 +16,7 @@ import org.h2.test.TestBase;
*/ */
public class TestGeneralCommonTableQueries extends TestBase { public class TestGeneralCommonTableQueries extends TestBase {
/** /**
* Run just this test. * Run just this test.
* *
* @param a ignored * @param a ignored
...@@ -30,6 +30,8 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -30,6 +30,8 @@ public class TestGeneralCommonTableQueries extends TestBase {
testSimple(); testSimple();
testImpliedColumnNames(); testImpliedColumnNames();
testChainedQuery(); testChainedQuery();
testParameterizedQuery();
testNumberedParameterizedQuery();
} }
private void testSimple() throws Exception { private void testSimple() throws Exception {
...@@ -43,14 +45,14 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -43,14 +45,14 @@ public class TestGeneralCommonTableQueries extends TestBase {
final String simple_two_column_query = "with " + final String simple_two_column_query = "with " +
"t1(n) as (select 1 as first) " + "t1(n) as (select 1 as first) " +
",t2(n) as (select 2 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); rs = stat.executeQuery(simple_two_column_query);
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(1, rs.getInt(1)); assertEquals(1, rs.getInt(1));
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(2, rs.getInt(1)); assertEquals(2, rs.getInt(1));
assertFalse(rs.next()); assertFalse(rs.next());
prep = conn.prepareStatement(simple_two_column_query); prep = conn.prepareStatement(simple_two_column_query);
rs = prep.executeQuery(); rs = prep.executeQuery();
assertTrue(rs.next()); assertTrue(rs.next());
...@@ -58,7 +60,7 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -58,7 +60,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(2, rs.getInt(1)); assertEquals(2, rs.getInt(1));
assertFalse(rs.next()); assertFalse(rs.next());
prep = conn.prepareStatement("with " + prep = conn.prepareStatement("with " +
"t1(n) as (select 2 as first) " + "t1(n) as (select 2 as first) " +
",t2(n) as (select 3 as first) " + ",t2(n) as (select 3 as first) " +
...@@ -70,7 +72,7 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -70,7 +72,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(3, rs.getInt(1)); assertEquals(3, rs.getInt(1));
assertFalse(rs.next()); assertFalse(rs.next());
prep = conn.prepareStatement("with " + prep = conn.prepareStatement("with " +
"t1(n) as (select 2 as first) " + "t1(n) as (select 2 as first) " +
",t2(n) as (select 3 as first) " + ",t2(n) as (select 3 as first) " +
...@@ -83,7 +85,7 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -83,7 +85,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(3, rs.getInt(1)); assertEquals(3, rs.getInt(1));
assertFalse(rs.next()); assertFalse(rs.next());
conn.close(); conn.close();
deleteDb("commonTableExpressionQueries"); deleteDb("commonTableExpressionQueries");
} }
...@@ -93,7 +95,7 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -93,7 +95,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
Connection conn = getConnection("commonTableExpressionQueries"); Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep; PreparedStatement prep;
ResultSet rs; ResultSet rs;
prep = conn.prepareStatement("with " + prep = conn.prepareStatement("with " +
"t1 as (select 2 as first_col) " + "t1 as (select 2 as first_col) " +
",t2 as (select first_col+1 from t1) " + ",t2 as (select first_col+1 from t1) " +
...@@ -108,30 +110,107 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -108,30 +110,107 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertFalse(rs.next()); assertFalse(rs.next());
assertEquals(rs.getMetaData().getColumnCount(),1); assertEquals(rs.getMetaData().getColumnCount(),1);
assertEquals("FIRST_COL",rs.getMetaData().getColumnLabel(1)); assertEquals("FIRST_COL",rs.getMetaData().getColumnLabel(1));
conn.close(); conn.close();
deleteDb("commonTableExpressionQueries"); deleteDb("commonTableExpressionQueries");
} }
private void testChainedQuery() throws Exception { private void testChainedQuery() throws Exception {
deleteDb("commonTableExpressionQueries"); deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries"); Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep; PreparedStatement prep;
ResultSet rs; ResultSet rs;
prep = conn.prepareStatement(" WITH t1 AS (" prep = conn.prepareStatement(
+" SELECT 1 AS FIRST_COLUMN" " WITH t1 AS (" +
+")," " SELECT 1 AS FIRST_COLUMN" +
+" t2 AS (" ")," +
+" SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM t1 " " t2 AS (" +
+") " " SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM t1 " +
+"SELECT sum(FIRST_COLUMN) FROM t2"); ") " +
"SELECT sum(FIRST_COLUMN) FROM t2");
rs = prep.executeQuery(); rs = prep.executeQuery();
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(2, rs.getInt(1)); assertEquals(2, rs.getInt(1));
assertFalse(rs.next()); 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(); conn.close();
deleteDb("commonTableExpressionQueries"); 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 { ...@@ -50,6 +50,8 @@ public class TestQueryCache extends TestBase {
} }
String query = queryBuilder.toString(); String query = queryBuilder.toString();
conn.prepareStatement(query); conn.prepareStatement(query);
int firstGreater = 0;
int firstSmaller = 0;
long time; long time;
ResultSet rs; ResultSet rs;
long first = 0; long first = 0;
...@@ -77,9 +79,17 @@ public class TestQueryCache extends TestBase { ...@@ -77,9 +79,17 @@ public class TestQueryCache extends TestBase {
// try to avoid pauses in subsequent iterations // try to avoid pauses in subsequent iterations
System.gc(); System.gc();
} else if (i > 1001) { } 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"); stat.execute("drop table test");
conn.close(); conn.close();
} }
......
...@@ -682,6 +682,15 @@ public class TestPreparedStatement extends TestBase { ...@@ -682,6 +682,15 @@ public class TestPreparedStatement extends TestBase {
rs.next(); rs.next();
Object offsetDateTime2 = rs.getObject(1, LocalDateTimeUtils.getOffsetDateTimeClass()); Object offsetDateTime2 = rs.getObject(1, LocalDateTimeUtils.getOffsetDateTimeClass());
assertEquals(offsetDateTime, offsetDateTime2); 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(); rs.close();
} }
......
...@@ -7,7 +7,9 @@ package org.h2.test.mvcc; ...@@ -7,7 +7,9 @@ package org.h2.test.mvcc;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
...@@ -30,6 +32,7 @@ public class TestMvccMultiThreaded extends TestBase { ...@@ -30,6 +32,7 @@ public class TestMvccMultiThreaded extends TestBase {
@Override @Override
public void test() throws Exception { public void test() throws Exception {
testConcurrentSelectForUpdate();
testMergeWithUniqueKeyViolation(); testMergeWithUniqueKeyViolation();
// not supported currently // not supported currently
if (!config.multiThreaded) { if (!config.multiThreaded) {
...@@ -38,6 +41,50 @@ public class TestMvccMultiThreaded extends TestBase { ...@@ -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 { private void testMergeWithUniqueKeyViolation() throws Exception {
deleteDb(getTestName()); deleteDb(getTestName());
Connection conn = getConnection(getTestName()); Connection conn = getConnection(getTestName());
......
...@@ -736,3 +736,4 @@ arbonaut exposing obscure determined turkey buildings indexhints acct ...@@ -736,3 +736,4 @@ arbonaut exposing obscure determined turkey buildings indexhints acct
choosing optimise arte preparator katzyn bla jenkins tot artes pgserver npe choosing optimise arte preparator katzyn bla jenkins tot artes pgserver npe
suffers closeablem mni significance vise identiy vitalus aka ilike uppercasing reentrant suffers closeablem mni significance vise identiy vitalus aka ilike uppercasing reentrant
aff ignite warm upstream producing sfu jit smtm affinity stashed tbl aff ignite warm upstream producing sfu jit smtm affinity stashed tbl
stumc numbered
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论