提交 6e2cfde3 authored 作者: andrei's avatar andrei

Merge remote-tracking branch 'h2database/master' into non_blocking

......@@ -21,6 +21,32 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Issue #992: 1.4.196+ client cannot use DatabaseMetaData with 1.4.195- server
</li>
<li>Issue #1016: ResultSet.getObject() should return enum value, not ordinal
</li>
<li>Issue #1012: NPE when querying INFORMATION_SCHEMA.COLUMNS on a view that references an ENUM column
</li>
<li>Issue #1010: MERGE USING table not found with qualified table
</li>
<li>PR #1009: Fix ARRAY_AGG with ORDER BY and refactor aggregates
</li>
<li>Issue #1006: "Empty enums are not allowed" in 1.4.197 (mode=MYSQL)
</li>
<li>PR #1007: Copy also SRID in ValueGeometry.getGeometry()
</li>
<li>PR #1004: Preserve type names in more places especially for UUID
</li>
<li>Issue #1000: Regression in INFORMATION_SCHEMA.CONSTRAINTS.CONSTRAINT_TYPE content
</li>
<li>Issue #997: Can not delete from tables with enums
</li>
<li>Issue #994: Too much column in result set for GENERATED_KEYS on table with DEFAULT
</li>
<li>PR #993: Fix some compiler warnings and improve assert*() methods
</li>
<li>PR #991: Generate shorter queries in JdbcDatabaseMetaData.getTables() and remove some dead code
</li>
<li>PR #989: Fix more issues with range table and improve its documentation
</li>
</ul>
......@@ -389,7 +415,7 @@ Change Log
</li>
<li>PR #743: Change REGEXP_REPLACE mode for MariaDB and PostgreSQL
</li>
<li>Issue#646 NPE in CREATE VIEW WITH RECURSIVE & NON_RECURSIVE CTE
<li>Issue#646 NPE in CREATE VIEW WITH RECURSIVE &amp; NON_RECURSIVE CTE
</li>
<li>PR #738: Copy javadoc to *BackwardsCompat to fix building of documentation
</li>
......@@ -696,7 +722,7 @@ changed from Types.OTHER (1111) to Types.TIMESTAMP_WITH_TIMEZONE (2014)
</li>
<li>Issue #266: Spatial index not updating, fixed by merging PR #267
</li>
<li>PR #302: add support for "with"-subqueries into "join" & "sub-query" statements
<li>PR #302: add support for "with"-subqueries into "join" &amp; "sub-query" statements
</li>
<li>Issue #299: Nested derived tables did not always work as expected.
</li>
......
......@@ -1150,11 +1150,8 @@ public class Parser {
TableFilter sourceTableFilter = readSimpleTableFilter(0, excludeIdentifiers);
command.setSourceTableFilter(sourceTableFilter);
StringBuilder buff = new StringBuilder("SELECT * FROM ")
.append(sourceTableFilter.getTable().getName());
if (sourceTableFilter.getTableAlias() != null) {
buff.append(" AS ").append(sourceTableFilter.getTableAlias());
}
StringBuilder buff = new StringBuilder("SELECT * FROM ");
appendTableWithSchemaAndAlias(buff, sourceTableFilter.getTable(), sourceTableFilter.getTableAlias());
Prepared preparedQuery = prepare(session, buff.toString(), null/*paramValues*/);
command.setQuery((Select) preparedQuery);
......@@ -1197,11 +1194,9 @@ public class Parser {
// build and prepare the targetMatchQuery ready to test each rows
// existence in the target table (using source row to match)
StringBuilder targetMatchQuerySQL = new StringBuilder(
"SELECT _ROWID_ FROM " + command.getTargetTable().getName());
if (command.getTargetTableFilter().getTableAlias() != null) {
targetMatchQuerySQL.append(" AS ").append(command.getTargetTableFilter().getTableAlias());
}
StringBuilder targetMatchQuerySQL = new StringBuilder("SELECT _ROWID_ FROM ");
appendTableWithSchemaAndAlias(targetMatchQuerySQL, command.getTargetTable(),
command.getTargetTableFilter().getTableAlias());
targetMatchQuerySQL
.append(" WHERE ").append(command.getOnCondition().getSQL());
command.setTargetMatchQuery(
......@@ -1210,6 +1205,14 @@ public class Parser {
return command;
}
private static void appendTableWithSchemaAndAlias(StringBuilder buff, Table table, String alias) {
buff.append(quoteIdentifier(table.getSchema().getName()))
.append('.').append(quoteIdentifier(table.getName()));
if (alias != null) {
buff.append(" AS ").append(quoteIdentifier(alias));
}
}
private Insert parseInsert() {
Insert command = new Insert(session);
currentPrepared = command;
......@@ -2628,7 +2631,7 @@ public class Parser {
readExpression(), currentSelect, distinct);
if (readIf("ORDER")) {
read("BY");
r.setGroupConcatOrder(parseSimpleOrderList());
r.setOrderByList(parseSimpleOrderList());
}
if (readIf("SEPARATOR")) {
......@@ -2642,7 +2645,7 @@ public class Parser {
r.setGroupConcatSeparator(readExpression());
if (readIf("ORDER")) {
read("BY");
r.setGroupConcatOrder(parseSimpleOrderList());
r.setOrderByList(parseSimpleOrderList());
}
} else {
r = null;
......@@ -2654,7 +2657,7 @@ public class Parser {
readExpression(), currentSelect, distinct);
if (readIf("ORDER")) {
read("BY");
r.setArrayAggOrder(parseSimpleOrderList());
r.setOrderByList(parseSimpleOrderList());
}
} else {
boolean distinct = readIf("DISTINCT");
......@@ -3027,7 +3030,7 @@ public class Parser {
int index = currentValue.getInt() - 1;
if (index < 0 || index >= Constants.MAX_PARAMETER_INDEX) {
throw DbException.getInvalidValueException(
"parameter index", index);
"parameter index", index + 1);
}
if (indexedParameterList.size() <= index) {
indexedParameterList.ensureCapacity(index + 1);
......
......@@ -21,21 +21,21 @@ abstract class AggregateData {
* @return the aggregate data object of the specified type
*/
static AggregateData create(AggregateType aggregateType) {
if (aggregateType == AggregateType.SELECTIVITY) {
switch (aggregateType) {
case SELECTIVITY:
return new AggregateDataSelectivity();
} else if (aggregateType == AggregateType.GROUP_CONCAT) {
return new AggregateDataArrayCollecting();
} else if (aggregateType == AggregateType.ARRAY_AGG) {
return new AggregateDataArrayCollecting();
} else if (aggregateType == AggregateType.COUNT_ALL) {
case GROUP_CONCAT:
case ARRAY_AGG:
return new AggregateDataCollecting();
case COUNT_ALL:
return new AggregateDataCountAll();
} else if (aggregateType == AggregateType.COUNT) {
case COUNT:
return new AggregateDataCount();
} else if (aggregateType == AggregateType.HISTOGRAM) {
case HISTOGRAM:
return new AggregateDataHistogram();
} else if (aggregateType == AggregateType.MEDIAN) {
case MEDIAN:
return new AggregateDataMedian();
} else {
default:
return new AggregateDataDefault(aggregateType);
}
}
......
......@@ -6,55 +6,54 @@
package org.h2.expression;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import org.h2.engine.Database;
import org.h2.util.New;
import org.h2.util.ValueHashMap;
import org.h2.value.Value;
import org.h2.value.ValueNull;
/**
* Data stored while calculating a GROUP_CONCAT/ARRAY_AGG aggregate.
* Data stored while calculating an aggregate that needs collecting of all
* values.
*
* <p>
* NULL values are not collected. {@link #getValue(Database, int, boolean)}
* method returns {@code null}. Use {@link #getArray()} for instances of this
* class instead. Notice that subclasses like {@link AggregateDataMedian} may
* override {@link #getValue(Database, int, boolean)} to return useful result.
* </p>
*/
class AggregateDataArrayCollecting extends AggregateData {
private ArrayList<Value> list;
private ValueHashMap<AggregateDataArrayCollecting> distinctValues;
class AggregateDataCollecting extends AggregateData {
Collection<Value> values;
@Override
void add(Database database, int dataType, boolean distinct, Value v) {
if (v == ValueNull.INSTANCE) {
return;
}
if (distinct) {
if (distinctValues == null) {
distinctValues = ValueHashMap.newInstance();
}
distinctValues.put(v, this);
return;
}
if (list == null) {
list = New.arrayList();
Collection<Value> c = values;
if (c == null) {
values = c = distinct ? new HashSet<Value>() : new ArrayList<Value>();
}
list.add(v);
c.add(v);
}
@Override
Value getValue(Database database, int dataType, boolean distinct) {
if (distinct) {
distinct(database, dataType);
}
return null;
}
ArrayList<Value> getList() {
return list;
}
private void distinct(Database database, int dataType) {
if (distinctValues == null) {
return;
}
for (Value v : distinctValues.keys()) {
add(database, dataType, false, v);
/**
* Returns array with values or {@code null}.
*
* @return array with values or {@code null}
*/
Value[] getArray() {
Collection<Value> values = this.values;
if (values == null) {
return null;
}
return values.toArray(new Value[0]);
}
}
......@@ -8,9 +8,7 @@ package org.h2.expression;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import org.h2.engine.Database;
import org.h2.engine.Session;
......@@ -39,9 +37,7 @@ import org.h2.value.ValueTimestampTimeZone;
/**
* Data stored while calculating a MEDIAN aggregate.
*/
class AggregateDataMedian extends AggregateData {
private Collection<Value> values;
class AggregateDataMedian extends AggregateDataCollecting {
private static boolean isNullsLast(Index index) {
IndexColumn ic = index.getIndexColumns()[0];
int sortType = ic.sortType;
......@@ -168,29 +164,12 @@ class AggregateDataMedian extends AggregateData {
return v;
}
@Override
void add(Database database, int dataType, boolean distinct, Value v) {
if (v == ValueNull.INSTANCE) {
return;
}
Collection<Value> c = values;
if (c == null) {
values = c = distinct ? new HashSet<Value>() : new ArrayList<Value>();
}
c.add(v);
}
@Override
Value getValue(Database database, int dataType, boolean distinct) {
Collection<Value> c = values;
// Non-null collection cannot be empty here
if (c == null) {
Value[] a = getArray();
if (a == null) {
return ValueNull.INSTANCE;
}
if (distinct && c instanceof ArrayList) {
c = new HashSet<>(c);
}
Value[] a = c.toArray(new Value[0]);
final CompareMode mode = database.getCompareMode();
Arrays.sort(a, new Comparator<Value>() {
@Override
......
......@@ -186,6 +186,8 @@ public class Operation extends Expression {
dataType = left.getType();
if (dataType == Value.UNKNOWN) {
dataType = Value.DECIMAL;
} else if (dataType == Value.ENUM) {
dataType = Value.INT;
}
break;
case CONCAT:
......@@ -315,7 +317,9 @@ public class Operation extends Expression {
DataType.getDataType(r).name);
} else {
dataType = Value.getHigherOrder(l, r);
if (DataType.isStringType(dataType) &&
if (dataType == Value.ENUM) {
dataType = Value.INT;
} else if (DataType.isStringType(dataType) &&
session.getDatabase().getMode().allowPlusForStringConcat) {
opType = OpType.CONCAT;
}
......
......@@ -135,14 +135,12 @@ public class TableFunction extends Function {
simple.setAutoClose(false);
for (int i = 0; i < columnCount; i++) {
String name = rs.getColumnName(i);
/*
* TODO Some types, such as Value.BYTES and Value.UUID are mapped to the same
* SQL type and we can lose real type here.
*/
int sqlType = DataType.convertTypeToSQLType(rs.getColumnType(i));
DataType dataType = DataType.getDataType(rs.getColumnType(i));
int sqlType = dataType.sqlType;
String sqlTypeName = dataType.name;
int precision = MathUtils.convertLongToInt(rs.getColumnPrecision(i));
int scale = rs.getColumnScale(i);
simple.addColumn(name, sqlType, precision, scale);
simple.addColumn(name, sqlType, sqlTypeName, precision, scale);
}
rs.reset();
for (int i = 0; i < maxrows && rs.next(); i++) {
......
......@@ -323,7 +323,7 @@ public final class MVSecondaryIndex extends BaseIndex implements MVIndex {
int idx = c.getColumnId();
Value v = r.getValue(idx);
if (v != null) {
array[i] = v.convertTo(c.getType());
array[i] = v.convertTo(c.getType(), -1, null, null, c.getEnumerators());
}
}
array[keyColumns - 1] = ValueLong.get(r.getKey());
......
......@@ -334,11 +334,12 @@ public class Column {
synchronized (this) {
localDefaultExpression = defaultExpression;
}
Mode mode = session.getDatabase().getMode();
if (value == null) {
if (localDefaultExpression == null) {
value = ValueNull.INSTANCE;
} else {
value = localDefaultExpression.getValue(session).convertTo(type);
value = convert(localDefaultExpression.getValue(session), mode);
if (!localDefaultExpression.isConstant()) {
session.getGeneratedKeys().add(this);
}
......@@ -347,10 +348,9 @@ public class Column {
}
}
}
Mode mode = session.getDatabase().getMode();
if (value == ValueNull.INSTANCE) {
if (convertNullToDefault) {
value = localDefaultExpression.getValue(session).convertTo(type);
value = convert(localDefaultExpression.getValue(session), mode);
if (!localDefaultExpression.isConstant()) {
session.getGeneratedKeys().add(this);
}
......
......@@ -1688,7 +1688,8 @@ public class MetaTable extends Table {
// CONSTRAINT_NAME
identifier(constraint.getName()),
// CONSTRAINT_TYPE
constraintType.toString(),
constraintType == Constraint.Type.PRIMARY_KEY ?
constraintType.getSqlName() : constraintType.name(),
// TABLE_CATALOG
catalog,
// TABLE_SCHEMA
......
......@@ -193,7 +193,13 @@ public class TableView extends Table {
long precision = expr.getPrecision();
int scale = expr.getScale();
int displaySize = expr.getDisplaySize();
Column col = new Column(name, type, precision, scale, displaySize);
String[] enumerators = null;
if (type == Value.ENUM) {
if (expr instanceof ExpressionColumn) {
enumerators = ((ExpressionColumn) expr).getColumn().getEnumerators();
}
}
Column col = new Column(name, type, precision, scale, displaySize, enumerators);
col.setTable(this, i);
// Fetch check constraint from view column source
ExpressionColumn fromColumn = null;
......
......@@ -38,6 +38,7 @@ import org.h2.util.Bits;
import org.h2.util.JdbcUtils;
import org.h2.util.MathUtils;
import org.h2.util.New;
import org.h2.util.SimpleColumnInfo;
import org.h2.util.Utils;
import org.h2.value.DataType;
......@@ -67,7 +68,7 @@ public class SimpleResultSet implements ResultSet, ResultSetMetaData,
private int rowId = -1;
private boolean wasNull;
private SimpleRowSource source;
private ArrayList<Column> columns = New.arrayList();
private ArrayList<SimpleColumnInfo> columns = New.arrayList();
private boolean autoClose = true;
/**
......@@ -123,13 +124,7 @@ public class SimpleResultSet implements ResultSet, ResultSetMetaData,
if (name == null) {
name = "C" + (columns.size() + 1);
}
Column column = new Column();
column.name = name;
column.sqlType = sqlType;
column.precision = precision;
column.scale = scale;
column.sqlTypeName = sqlTypeName;
columns.add(column);
columns.add(new SimpleColumnInfo(name, sqlType, sqlTypeName, precision, scale));
}
/**
......@@ -1012,7 +1007,7 @@ public class SimpleResultSet implements ResultSet, ResultSetMetaData,
if (o == null) {
return null;
}
switch (columns.get(columnIndex - 1).sqlType) {
switch (columns.get(columnIndex - 1).type) {
case Types.CLOB:
Clob c = (Clob) o;
return c.getSubString(1, MathUtils.convertLongToInt(c.length()));
......@@ -1868,7 +1863,7 @@ public class SimpleResultSet implements ResultSet, ResultSetMetaData,
*/
@Override
public int getColumnType(int columnIndex) throws SQLException {
return getColumn(columnIndex - 1).sqlType;
return getColumn(columnIndex - 1).type;
}
/**
......@@ -2045,7 +2040,7 @@ public class SimpleResultSet implements ResultSet, ResultSetMetaData,
*/
@Override
public String getColumnTypeName(int columnIndex) throws SQLException {
return getColumn(columnIndex - 1).sqlTypeName;
return getColumn(columnIndex - 1).typeName;
}
/**
......@@ -2295,7 +2290,7 @@ public class SimpleResultSet implements ResultSet, ResultSetMetaData,
return o;
}
private Column getColumn(int i) throws SQLException {
private SimpleColumnInfo getColumn(int i) throws SQLException {
checkColumnIndex(i + 1);
return columns.get(i);
}
......@@ -2355,37 +2350,6 @@ public class SimpleResultSet implements ResultSet, ResultSetMetaData,
return autoClose;
}
/**
* This class holds the data of a result column.
*/
static class Column {
/**
* The column label.
*/
String name;
/**
* The column type Name
*/
String sqlTypeName;
/**
* The SQL type.
*/
int sqlType;
/**
* The precision.
*/
int precision;
/**
* The scale.
*/
int scale;
}
/**
* A simple array implementation,
* backed by an object array
......
......@@ -24,59 +24,9 @@ import org.h2.tools.SimpleResultSet;
* that have {@code NAME} column should also define it with the same type.
*/
public final class MergedResultSet {
/**
* Metadata of a column.
*/
private static final class ColumnInfo {
final String name;
final int type;
final int precision;
final int scale;
/**
* Creates metadata.
*
* @param name
* name of the column
* @param type
* type of the column, see {@link java.sql.Types}
* @param precision
* precision of the column
* @param scale
* scale of the column
*/
ColumnInfo(String name, int type, int precision, int scale) {
this.name = name;
this.type = type;
this.precision = precision;
this.scale = scale;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ColumnInfo other = (ColumnInfo) obj;
return name.equals(other.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
}
private final ArrayList<Map<ColumnInfo, Object>> data = New.arrayList();
private final ArrayList<Map<SimpleColumnInfo, Object>> data = New.arrayList();
private final ArrayList<ColumnInfo> columns = New.arrayList();
private final ArrayList<SimpleColumnInfo> columns = New.arrayList();
/**
* Appends a result set.
......@@ -92,10 +42,10 @@ public final class MergedResultSet {
if (cols == 0) {
return;
}
ColumnInfo[] info = new ColumnInfo[cols];
SimpleColumnInfo[] info = new SimpleColumnInfo[cols];
for (int i = 1; i <= cols; i++) {
ColumnInfo ci = new ColumnInfo(meta.getColumnName(i), meta.getColumnType(i), meta.getPrecision(i),
meta.getScale(i));
SimpleColumnInfo ci = new SimpleColumnInfo(meta.getColumnName(i), meta.getColumnType(i),
meta.getColumnTypeName(i), meta.getPrecision(i), meta.getScale(i));
info[i - 1] = ci;
if (!columns.contains(ci)) {
columns.add(ci);
......@@ -105,9 +55,9 @@ public final class MergedResultSet {
if (cols == 1) {
data.add(Collections.singletonMap(info[0], rs.getObject(1)));
} else {
HashMap<ColumnInfo, Object> map = new HashMap<>();
HashMap<SimpleColumnInfo, Object> map = new HashMap<>();
for (int i = 1; i <= cols; i++) {
ColumnInfo ci = info[i - 1];
SimpleColumnInfo ci = info[i - 1];
map.put(ci, rs.getObject(i));
}
data.add(map);
......@@ -122,12 +72,12 @@ public final class MergedResultSet {
*/
public SimpleResultSet getResult() {
SimpleResultSet rs = new SimpleResultSet();
for (ColumnInfo ci : columns) {
rs.addColumn(ci.name, ci.type, ci.precision, ci.scale);
for (SimpleColumnInfo ci : columns) {
rs.addColumn(ci.name, ci.type, ci.typeName, ci.precision, ci.scale);
}
for (Map<ColumnInfo, Object> map : data) {
for (Map<SimpleColumnInfo, Object> map : data) {
Object[] row = new Object[columns.size()];
for (Map.Entry<ColumnInfo, Object> entry : map.entrySet()) {
for (Map.Entry<SimpleColumnInfo, Object> entry : map.entrySet()) {
row[columns.indexOf(entry.getKey())] = entry.getValue();
}
rs.addRow(row);
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.util;
/**
* Metadata of a column.
*
* <p>
* Notice: {@linkplain #equals(Object)} and {@linkplain #hashCode()} use only
* {@linkplain #name} field.
* </p>
*/
public final class SimpleColumnInfo {
/**
* Name of the column.
*/
public final String name;
/**
* Type of the column, see {@link java.sql.Types}.
*/
public final int type;
/**
* Type name of the column.
*/
public final String typeName;
/**
* Precision of the column
*/
public final int precision;
/**
* Scale of the column.
*/
public final int scale;
/**
* Creates metadata.
*
* @param name
* name of the column
* @param type
* type of the column, see {@link java.sql.Types}
* @param typeName
* type name of the column
* @param precision
* precision of the column
* @param scale
* scale of the column
*/
public SimpleColumnInfo(String name, int type, String typeName, int precision, int scale) {
this.name = name;
this.type = type;
this.typeName = typeName;
this.precision = precision;
this.scale = scale;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
SimpleColumnInfo other = (SimpleColumnInfo) obj;
return name.equals(other.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
}
......@@ -856,6 +856,11 @@ public class DataType {
*/
public static int convertSQLTypeToValueType(int sqlType, String sqlTypeName) {
switch (sqlType) {
case Types.BINARY:
if (sqlTypeName.equalsIgnoreCase("UUID")) {
return Value.UUID;
}
break;
case Types.OTHER:
case Types.JAVA_OBJECT:
if (sqlTypeName.equalsIgnoreCase("geometry")) {
......
......@@ -982,6 +982,15 @@ public abstract class Value {
case STRING_IGNORECASE:
case STRING_FIXED:
return ValueEnum.get(enumerators, getString());
case JAVA_OBJECT:
Object object = JdbcUtils.deserialize(getBytesNoCopy(),
getDataHandler());
if (object instanceof String) {
return ValueEnum.get(enumerators, (String) object);
} else if (object instanceof Integer) {
return ValueEnum.get(enumerators, (int) object);
}
//$FALL-THROUGH$
default:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
......
......@@ -77,7 +77,7 @@ public class ValueEnumBase extends Value {
@Override
public Object getObject() {
return ordinal;
return label;
}
@Override
......
......@@ -11,6 +11,7 @@ import java.util.Arrays;
import org.h2.engine.Mode;
import org.h2.message.DbException;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.CoordinateSequenceFilter;
import org.locationtech.jts.geom.Envelope;
......@@ -140,7 +141,20 @@ public class ValueGeometry extends Value {
* @return a copy of the geometry object
*/
public Geometry getGeometry() {
return getGeometryNoCopy().copy();
Geometry geometry = getGeometryNoCopy();
Geometry copy = geometry.copy();
/*
* Geometry factory is not preserved in WKB format, but SRID is preserved.
*
* We use a new factory to read geometries from WKB with default SRID value of
* 0.
*
* Geometry.copy() copies the geometry factory and copied value has SRID form
* the factory instead of original SRID value. So we need to copy SRID here with
* non-recommended (but not deprecated) setSRID() method.
*/
copy.setSRID(geometry.getSRID());
return copy;
}
public Geometry getGeometryNoCopy() {
......@@ -221,7 +235,7 @@ public class ValueGeometry extends Value {
@Override
public byte[] getBytes() {
return getWKB();
return Utils.cloneByteArray(getWKB());
}
@Override
......
......@@ -6,6 +6,7 @@
package org.h2.test.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
......@@ -99,6 +100,9 @@ public class TestIndex extends TestBase {
conn.close();
deleteDb("index");
// This test uses own connection
testEnumIndex();
}
private void testOrderIndex() throws SQLException {
......@@ -750,4 +754,27 @@ public class TestIndex extends TestBase {
assertEquals(1, testFunctionIndexCounter);
}
private void testEnumIndex() throws SQLException {
if (config.memory || config.networked) {
return;
}
deleteDb("index");
String url = "jdbc:h2:" + getBaseDir() + "/index;DB_CLOSE_DELAY=0";
Connection conn = DriverManager.getConnection(url);
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE TEST(ID INT, V ENUM('A', 'B'), CONSTRAINT PK PRIMARY KEY(ID, V))");
stat.execute("INSERT INTO TEST VALUES (1, 'A'), (2, 'B')");
conn.close();
conn = DriverManager.getConnection(url);
stat = conn.createStatement();
stat.execute("DELETE FROM TEST WHERE V = 'A'");
stat.execute("DROP TABLE TEST");
conn.close();
deleteDb("index");
}
}
......@@ -648,13 +648,14 @@ public class TestSpatial extends TestBase {
GeometryFactory geometryFactory = new GeometryFactory();
Geometry geometry = geometryFactory.createPoint(new Coordinate(0, 0));
geometry.setSRID(27572);
ValueGeometry valueGeometry =
ValueGeometry.getFromGeometry(geometry);
ValueGeometry valueGeometry = ValueGeometry.getFromGeometry(geometry);
Geometry geometry2 = geometryFactory.createPoint(new Coordinate(0, 0));
geometry2.setSRID(5326);
ValueGeometry valueGeometry2 =
ValueGeometry.getFromGeometry(geometry2);
ValueGeometry valueGeometry2 = ValueGeometry.getFromGeometry(geometry2);
assertFalse(valueGeometry.equals(valueGeometry2));
ValueGeometry valueGeometry3 = ValueGeometry.getFromGeometry(geometry);
assertEquals(valueGeometry, valueGeometry3);
assertEquals(geometry.getSRID(), valueGeometry3.getGeometry().getSRID());
// Check illegal geometry (no WKB representation)
try {
ValueGeometry.get("POINT EMPTY");
......
......@@ -8,6 +8,7 @@ package org.h2.test.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.UUID;
......@@ -127,6 +128,9 @@ public class TestGetGeneratedKeys extends TestBase {
prep.addBatch();
prep.executeBatch();
ResultSet rs = prep.getGeneratedKeys();
ResultSetMetaData meta = rs.getMetaData();
assertEquals("BIGINT", meta.getColumnTypeName(1));
assertEquals("UUID", meta.getColumnTypeName(2));
rs.next();
assertEquals(1L, rs.getLong(1));
UUID u1 = (UUID) rs.getObject(2);
......
......@@ -8,11 +8,14 @@ package org.h2.test.jdbc;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.UUID;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties;
......@@ -135,6 +138,13 @@ public class TestMetaData extends TestBase {
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, conn.getHoldability());
assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, rs.getHoldability());
stat.executeUpdate("drop table test");
PreparedStatement prep = conn.prepareStatement("SELECT X FROM TABLE (X UUID = ?)");
prep.setObject(1, UUID.randomUUID());
rs = prep.executeQuery();
rsMeta = rs.getMetaData();
assertEquals("UUID", rsMeta.getColumnTypeName(1));
conn.close();
}
......
......@@ -476,7 +476,7 @@ public class TestPreparedStatement extends TestBase {
assertEquals(goodSizes[i], rs.getString(1));
assertEquals(i, rs.getInt(1));
Object o = rs.getObject(1);
assertEquals(Integer.class, o.getClass());
assertEquals(String.class, o.getClass());
}
for (int i = 0; i < goodSizes.length; i++) {
......
......@@ -87,6 +87,7 @@ public class TestResultSet extends TestBase {
testFindColumn();
testColumnLength();
testArray();
testEnum();
testLimitMaxRows();
trace("max rows=" + stat.getMaxRows());
......@@ -1798,6 +1799,56 @@ public class TestResultSet extends TestBase {
stat.execute("DROP TABLE TEST");
}
private void testEnum() throws SQLException {
trace("Test ENUM");
stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, VALUE ENUM('A', 'B', 'C', 'D', 'E', 'F', 'G'))");
PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?)");
prep.setInt(1, 1);
prep.setString(2, "A");
prep.executeUpdate();
prep.setInt(1, 2);
prep.setObject(2, "B");
prep.executeUpdate();
prep.setInt(1, 3);
prep.setInt(2, 2);
prep.executeUpdate();
prep.setInt(1, 4);
prep.setObject(2, "D", Types.VARCHAR);
prep.executeUpdate();
prep.setInt(1, 5);
prep.setObject(2, "E", Types.OTHER);
prep.executeUpdate();
prep.setInt(1, 6);
prep.setObject(2, 5, Types.OTHER);
prep.executeUpdate();
prep.setInt(1, 7);
prep.setObject(2, 6, Types.INTEGER);
prep.executeUpdate();
ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID");
testEnumResult(rs, 1, "A", 0);
testEnumResult(rs, 2, "B", 1);
testEnumResult(rs, 3, "C", 2);
testEnumResult(rs, 4, "D", 3);
testEnumResult(rs, 5, "E", 4);
testEnumResult(rs, 6, "F", 5);
testEnumResult(rs, 7, "G", 6);
assertFalse(rs.next());
stat.execute("DROP TABLE TEST");
}
private void testEnumResult(ResultSet rs, int id, String name, int ordinal) throws SQLException {
assertTrue(rs.next());
assertEquals(id, rs.getInt(1));
assertEquals(name, rs.getString(2));
assertEquals(name, rs.getObject(2));
assertEquals(name, rs.getObject(2, String.class));
assertEquals(ordinal, rs.getInt(2));
assertEquals((Integer) ordinal, rs.getObject(2, Integer.class));
}
private byte[] readAllBytes(InputStream in) {
if (in == null) {
return null;
......
......@@ -162,3 +162,93 @@ drop table card;
drop type CARD_SUIT;
> ok
CREATE TABLE TEST(ID INT, E1 ENUM('A', 'B') DEFAULT 'A', E2 ENUM('C', 'D') DEFAULT 'C' ON UPDATE 'D');
> ok
INSERT INTO TEST(ID) VALUES (1);
> update count: 1
SELECT * FROM TEST;
> ID E1 E2
> -- -- --
> 1 A C
> rows: 1
UPDATE TEST SET E1 = 'B';
> update count: 1
SELECT * FROM TEST;
> ID E1 E2
> -- -- --
> 1 B D
> rows: 1
DROP TABLE TEST;
> ok
CREATE TABLE TEST(E ENUM('A', 'B'));
> ok
INSERT INTO TEST VALUES ('B');
> update count: 1
CREATE VIEW V AS SELECT * FROM TEST;
> ok
SELECT * FROM V;
> E
> -
> B
> rows: 1
CREATE VIEW V1 AS SELECT E + 2 AS E FROM TEST;
> ok
SELECT * FROM V1;
> E
> -
> 3
> rows: 1
CREATE VIEW V2 AS SELECT E + E AS E FROM TEST;
> ok
SELECT * FROM V2;
> E
> -
> 2
> rows: 1
CREATE VIEW V3 AS SELECT -E AS E FROM TEST;
> ok
SELECT * FROM V3;
> E
> --
> -1
> rows: 1
SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'E' ORDER BY TABLE_NAME;
> TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION COLUMN_DEFAULT IS_NULLABLE DATA_TYPE CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_PRECISION_RADIX NUMERIC_SCALE CHARACTER_SET_NAME COLLATION_NAME TYPE_NAME NULLABLE IS_COMPUTED SELECTIVITY CHECK_CONSTRAINT SEQUENCE_NAME REMARKS SOURCE_DATA_TYPE COLUMN_TYPE COLUMN_ON_UPDATE
> ------------- ------------ ---------- ----------- ---------------- -------------- ----------- --------- ------------------------ ---------------------- ----------------- ----------------------- ------------- ------------------ -------------- --------- -------- ----------- ----------- ---------------- ------------- ------- ---------------- ------------- ----------------
> SCRIPT PUBLIC TEST E 1 null YES 1111 2147483647 2147483647 2147483647 10 0 Unicode OFF ENUM 1 FALSE 50 null null ENUM('A','B') null
> SCRIPT PUBLIC V E 1 null YES 1111 2147483647 2147483647 2147483647 10 0 Unicode OFF ENUM 1 FALSE 50 null null ENUM('A','B') null
> SCRIPT PUBLIC V1 E 1 null YES 4 2147483647 2147483647 2147483647 10 0 Unicode OFF INTEGER 1 FALSE 50 null null INTEGER null
> SCRIPT PUBLIC V2 E 1 null YES 4 2147483647 2147483647 2147483647 10 0 Unicode OFF INTEGER 1 FALSE 50 null null INTEGER null
> SCRIPT PUBLIC V3 E 1 null YES 4 2147483647 2147483647 2147483647 10 0 Unicode OFF INTEGER 1 FALSE 50 null null INTEGER null
> rows (ordered): 5
DROP VIEW V;
> ok
DROP VIEW V1;
> ok
DROP VIEW V2;
> ok
DROP VIEW V3;
> ok
DROP TABLE TEST;
> ok
......@@ -31,3 +31,44 @@ EXPLAIN PLAN
> PLAN
> ---------------------------------------------------------------------------------------------------------------------------------
> MERGE INTO PUBLIC.PARENT(ID, NAME) KEY(ID) SELECT X AS ID, ('Coco' || X) AS NAME FROM SYSTEM_RANGE(1, 2) /* PUBLIC.RANGE_INDEX */
DROP TABLE PARENT;
> ok
CREATE SCHEMA SOURCESCHEMA;
> ok
CREATE TABLE SOURCESCHEMA.SOURCE(ID INT PRIMARY KEY, VALUE INT);
> ok
INSERT INTO SOURCESCHEMA.SOURCE VALUES (1, 10), (3, 30), (5, 50);
> update count: 3
CREATE SCHEMA DESTSCHEMA;
> ok
CREATE TABLE DESTSCHEMA.DESTINATION(ID INT PRIMARY KEY, VALUE INT);
> ok
INSERT INTO DESTSCHEMA.DESTINATION VALUES (3, 300), (6, 600);
> update count: 2
MERGE INTO DESTSCHEMA.DESTINATION USING SOURCESCHEMA.SOURCE ON (DESTSCHEMA.DESTINATION.ID = SOURCESCHEMA.SOURCE.ID)
WHEN MATCHED THEN UPDATE SET VALUE = SOURCESCHEMA.SOURCE.VALUE
WHEN NOT MATCHED THEN INSERT (ID, VALUE) VALUES (SOURCESCHEMA.SOURCE.ID, SOURCESCHEMA.SOURCE.VALUE);
> update count: 3
SELECT * FROM DESTSCHEMA.DESTINATION;
> ID VALUE
> -- -----
> 1 10
> 3 30
> 5 50
> 6 600
> rows: 4
DROP SCHEMA SOURCESCHEMA CASCADE;
> ok
DROP SCHEMA DESTSCHEMA CASCADE;
> ok
......@@ -15,8 +15,7 @@ select array_agg(v order by v asc),
array_agg(v order by v desc) filter (where v >= '4')
from test where v >= '2';
> ARRAY_AGG(V ORDER BY V) ARRAY_AGG(V ORDER BY V DESC) FILTER (WHERE (V >= '4'))
> ---------------------------------------------------------------- ------------------------------------------------------
------------------------------
> ------------------------ ------------------------------------------------------
> (2, 3, 4, 5, 6, 7, 8, 9) (9, 8, 7, 6, 5, 4)
> rows (ordered): 1
......@@ -26,8 +25,7 @@ select ARRAY_AGG(v order by v asc),
ARRAY_AGG(v order by v desc) filter (where v >= '4')
from test where v >= '2';
> ARRAY_AGG(V ORDER BY V) ARRAY_AGG(V ORDER BY V DESC) FILTER (WHERE (V >= '4'))
> ---------------------------------------------------------------- ------------------------------------------------------
------------------------------
> ------------------------ ------------------------------------------------------
> (2, 3, 4, 5, 6, 7, 8, 9) (9, 8, 7, 6, 5, 4)
> rows (ordered): 1
......@@ -35,11 +33,36 @@ select ARRAY_AGG(v order by v asc),
ARRAY_AGG(v order by v desc) filter (where v >= '4')
from test;
> ARRAY_AGG(V ORDER BY V) ARRAY_AGG(V ORDER BY V DESC) FILTER (WHERE (V >= '4'))
> ------------------------------------------------------------------------ ------------------------------------------------------
------------------------------
> --------------------------- ------------------------------------------------------
> (1, 2, 3, 4, 5, 6, 7, 8, 9) (9, 8, 7, 6, 5, 4)
> rows (ordered): 1
drop table test;
> ok
create table test (id int auto_increment primary key, v int);
> ok
insert into test(v) values (7), (2), (8), (3), (7), (3), (9), (-1);
> update count: 8
select array_agg(v) from test;
> ARRAY_AGG(V)
> -------------------------
> (7, 2, 8, 3, 7, 3, 9, -1)
> rows: 1
select array_agg(distinct v) from test;
> ARRAY_AGG(DISTINCT V)
> ---------------------
> (-1, 2, 3, 7, 8, 9)
> rows: 1
select array_agg(distinct v order by v desc) from test;
> ARRAY_AGG(DISTINCT V ORDER BY V DESC)
> -------------------------------------
> (9, 8, 7, 3, 2, -1)
> rows (ordered): 1
drop table test;
> ok
......@@ -37,6 +37,32 @@ select group_concat(v order by v asc separator '-'),
> 1-2-3-4-5-6-7-8-9 9-8-7-6-5-4
> rows (ordered): 1
drop table test;
> ok
create table test (id int auto_increment primary key, v int);
> ok
insert into test(v) values (7), (2), (8), (3), (7), (3), (9), (-1);
> update count: 8
select group_concat(v) from test;
> GROUP_CONCAT(V)
> ----------------
> 7,2,8,3,7,3,9,-1
> rows: 1
select group_concat(distinct v) from test;
> GROUP_CONCAT(DISTINCT V)
> ------------------------
> -1,2,3,7,8,9
> rows: 1
select group_concat(distinct v order by v desc) from test;
> GROUP_CONCAT(DISTINCT V ORDER BY V DESC)
> ----------------------------------------
> 9,8,7,3,2,-1
> rows (ordered): 1
drop table test;
> ok
......@@ -41,6 +41,18 @@ SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME, IS_DEFERRABLE, INITIALLY_DE
> FK_2 FOREIGN KEY T2 NO NO
> rows (ordered): 5
SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME, COLUMN_LIST FROM INFORMATION_SCHEMA.CONSTRAINTS
WHERE CONSTRAINT_CATALOG = DATABASE() AND CONSTRAINT_SCHEMA = SCHEMA() AND TABLE_CATALOG = DATABASE() AND TABLE_SCHEMA = SCHEMA()
ORDER BY TABLE_NAME, CONSTRAINT_NAME;
> CONSTRAINT_NAME CONSTRAINT_TYPE TABLE_NAME COLUMN_LIST
> --------------- --------------- ---------- -----------
> PK_1 PRIMARY KEY T1 C1,C2
> U_1 UNIQUE T1 C3,C4
> CH_1 CHECK T2 null
> FK_1 REFERENTIAL T2 C3,C4
> FK_2 REFERENTIAL T2 C3,C4
> rows (ordered): 5
SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE LIMIT 0;
> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION POSITION_IN_UNIQUE_CONSTRAINT
> ------------------ ----------------- --------------- ------------- ------------ ---------- ----------- ---------------- -----------------------------
......
......@@ -770,5 +770,5 @@ openoffice organize libre systemtables gmane sea borders announced millennium al
opti excessively
iterators tech enums incompatibilities loses reimplement readme reorganize milli subdirectory
iterators tech enums incompatibilities loses reimplement readme reorganize milli subdirectory linkplain inspections
geometries sourceschema destschema
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论