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

Merge pull request #1610 from katzyn/row

Separate array and row value expressions
...@@ -2023,23 +2023,12 @@ ID=1 AND NAME='Hi' ...@@ -2023,23 +2023,12 @@ ID=1 AND NAME='Hi'
"Other Grammar","Array"," "Other Grammar","Array","
ARRAY '[' [ expression, [,...] ] ']' ARRAY '[' [ expression, [,...] ] ']'
| ( [ expression, [ expression [,...] ] ] )
"," ","
An array of values. An array of values.
The array can be declared with standard ARRAY[] syntax or with H2 syntax.
With standard syntax trailing comma is not allowed.
With H2 syntax an empty array is '()'. Trailing comma is ignored.
An array with one element must contain a trailing comma to be parsed as an array.
"," ","
ARRAY[1, 2] ARRAY[1, 2]
ARRAY[1] ARRAY[1]
ARRAY[] ARRAY[]
(1, 2)
(1, )
()
" "
"Other Grammar","Boolean"," "Other Grammar","Boolean","
...@@ -2520,6 +2509,16 @@ LZF is faster but uses more space. ...@@ -2520,6 +2509,16 @@ LZF is faster but uses more space.
COMPRESSION LZF COMPRESSION LZF
" "
"Other Grammar","Row value expression","
ROW (expression, [,...])
| ( [ expression, expression [,...] ] )
","
A row value expression.
","
ROW (1)
(1, 2)
"
"Other Grammar","Select Expression"," "Other Grammar","Select Expression","
wildcardExpression | expression [ [ AS ] columnAlias ] wildcardExpression | expression [ [ AS ] columnAlias ]
"," ","
......
...@@ -475,11 +475,14 @@ There is a list of keywords that can't be used as identifiers (table names, colu ...@@ -475,11 +475,14 @@ There is a list of keywords that can't be used as identifiers (table names, colu
unless they are quoted (surrounded with double quotes). The list is currently: unless they are quoted (surrounded with double quotes). The list is currently:
</p><p> </p><p>
<code> <code>
ALL, CHECK, CONSTRAINT, CROSS, CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, DISTINCT, EXCEPT, ALL, ARRAY, CASE, CHECK, CONSTRAINT, CROSS, CURRENT_DATE,
EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, HAVING, INNER, INTERSECT, INTERSECTS, CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, DISTINCT, EXCEPT,
IS, JOIN, LIKE, LIMIT, LOCALTIME, LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL, OFFSET, ON, ORDER, EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, HAVING,
PRIMARY, ROWNUM, SELECT, SYSDATE, SYSTIME, SYSTIMESTAMP, TODAY, TOP, TRUE, UNION, UNIQUE, WHERE, IF, INNER, INTERSECT, INTERSECTS, INTERVAL, IS, JOIN, LIKE,
WINDOW, WITH LIMIT, LOCALTIME, LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL,
OFFSET, ON, ORDER, PRIMARY, ROW, ROWNUM, SELECT, SYSDATE,
SYSTIME, SYSTIMESTAMP, TODAY, TOP, TRUE, UNION, UNIQUE, VALUES,
WHERE, WINDOW, WITH
</code> </code>
</p><p> </p><p>
Certain words of this list are keywords because they are functions that can be used without '()', Certain words of this list are keywords because they are functions that can be used without '()',
......
...@@ -11,17 +11,20 @@ import org.h2.table.ColumnResolver; ...@@ -11,17 +11,20 @@ import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueArray; import org.h2.value.ValueArray;
import org.h2.value.ValueRow;
/** /**
* A list of expressions, as in (ID, NAME). * A list of expressions, as in (ID, NAME).
* The result of this expression is an array. * The result of this expression is a row or an array.
*/ */
public class ExpressionList extends Expression { public class ExpressionList extends Expression {
private final Expression[] list; private final Expression[] list;
private final boolean isArray;
public ExpressionList(Expression[] list) { public ExpressionList(Expression[] list, boolean isArray) {
this.list = list; this.list = list;
this.isArray = isArray;
} }
@Override @Override
...@@ -30,12 +33,12 @@ public class ExpressionList extends Expression { ...@@ -30,12 +33,12 @@ public class ExpressionList extends Expression {
for (int i = 0; i < list.length; i++) { for (int i = 0; i < list.length; i++) {
v[i] = list[i].getValue(session); v[i] = list[i].getValue(session);
} }
return ValueArray.get(v); return isArray ? ValueArray.get(v) : ValueRow.get(v);
} }
@Override @Override
public int getType() { public int getType() {
return Value.ARRAY; return isArray ? Value.ARRAY : Value.ROW;
} }
@Override @Override
...@@ -85,12 +88,9 @@ public class ExpressionList extends Expression { ...@@ -85,12 +88,9 @@ public class ExpressionList extends Expression {
@Override @Override
public StringBuilder getSQL(StringBuilder builder) { public StringBuilder getSQL(StringBuilder builder) {
builder.append('('); builder.append(isArray ? "ARRAY [" : "ROW (");
writeExpressions(builder, list); writeExpressions(builder, list);
if (list.length == 1) { return builder.append(isArray ? ']' : ')');
builder.append(',');
}
return builder.append(')');
} }
@Override @Override
......
...@@ -14,8 +14,8 @@ import org.h2.result.ResultInterface; ...@@ -14,8 +14,8 @@ import org.h2.result.ResultInterface;
import org.h2.table.ColumnResolver; import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
import org.h2.value.ValueRow;
/** /**
* A query returning a single value. * A query returning a single value.
...@@ -42,7 +42,7 @@ public class Subquery extends Expression { ...@@ -42,7 +42,7 @@ public class Subquery extends Expression {
if (result.getVisibleColumnCount() == 1) { if (result.getVisibleColumnCount() == 1) {
v = values[0]; v = values[0];
} else { } else {
v = ValueArray.get(values); v = ValueRow.get(values);
} }
if (result.hasNext()) { if (result.hasNext()) {
throw DbException.get(ErrorCode.SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW); throw DbException.get(ErrorCode.SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW);
...@@ -109,7 +109,7 @@ public class Subquery extends Expression { ...@@ -109,7 +109,7 @@ public class Subquery extends Expression {
for (int i = 0; i < columnCount; i++) { for (int i = 0; i < columnCount; i++) {
list[i] = expressions.get(i); list[i] = expressions.get(i);
} }
expression = new ExpressionList(list); expression = new ExpressionList(list, false);
} }
} }
return expression; return expression;
......
...@@ -205,6 +205,7 @@ public class Comparison extends Condition { ...@@ -205,6 +205,7 @@ public class Comparison extends Condition {
left = left.optimize(session); left = left.optimize(session);
if (right != null) { if (right != null) {
right = right.optimize(session); right = right.optimize(session);
// TODO check row values too
if (right.getType() == Value.ARRAY && left.getType() != Value.ARRAY) { if (right.getType() == Value.ARRAY && left.getType() != Value.ARRAY) {
throw DbException.get(ErrorCode.COMPARING_ARRAY_TO_SCALAR); throw DbException.get(ErrorCode.COMPARING_ARRAY_TO_SCALAR);
} }
......
...@@ -70,6 +70,7 @@ import org.h2.value.ValueInt; ...@@ -70,6 +70,7 @@ import org.h2.value.ValueInt;
import org.h2.value.ValueLong; import org.h2.value.ValueLong;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
import org.h2.value.ValueResultSet; import org.h2.value.ValueResultSet;
import org.h2.value.ValueRow;
import org.h2.value.ValueString; import org.h2.value.ValueString;
import org.h2.value.ValueTime; import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp; import org.h2.value.ValueTimestamp;
...@@ -1038,10 +1039,10 @@ public class Function extends Expression implements FunctionCall { ...@@ -1038,10 +1039,10 @@ public class Function extends Expression implements FunctionCall {
break; break;
} }
case ARRAY_GET: { case ARRAY_GET: {
if (v0.getType() == Value.ARRAY) { Value[] list = getArray(v0);
if (list != null) {
Value v1 = getNullOrValue(session, args, values, 1); Value v1 = getNullOrValue(session, args, values, 1);
int element = v1.getInt(); int element = v1.getInt();
Value[] list = ((ValueArray) v0).getList();
if (element < 1 || element > list.length) { if (element < 1 || element > list.length) {
result = ValueNull.INSTANCE; result = ValueNull.INSTANCE;
} else { } else {
...@@ -1053,8 +1054,8 @@ public class Function extends Expression implements FunctionCall { ...@@ -1053,8 +1054,8 @@ public class Function extends Expression implements FunctionCall {
break; break;
} }
case ARRAY_LENGTH: { case ARRAY_LENGTH: {
if (v0.getType() == Value.ARRAY) { Value[] list = getArray(v0);
Value[] list = ((ValueArray) v0).getList(); if (list != null) {
result = ValueInt.get(list.length); result = ValueInt.get(list.length);
} else { } else {
result = ValueNull.INSTANCE; result = ValueNull.INSTANCE;
...@@ -1063,9 +1064,9 @@ public class Function extends Expression implements FunctionCall { ...@@ -1063,9 +1064,9 @@ public class Function extends Expression implements FunctionCall {
} }
case ARRAY_CONTAINS: { case ARRAY_CONTAINS: {
result = ValueBoolean.FALSE; result = ValueBoolean.FALSE;
if (v0.getType() == Value.ARRAY) { Value[] list = getArray(v0);
if (list != null) {
Value v1 = getNullOrValue(session, args, values, 1); Value v1 = getNullOrValue(session, args, values, 1);
Value[] list = ((ValueArray) v0).getList();
for (Value v : list) { for (Value v : list) {
if (database.areEqual(v, v1)) { if (database.areEqual(v, v1)) {
result = ValueBoolean.TRUE; result = ValueBoolean.TRUE;
...@@ -1091,6 +1092,19 @@ public class Function extends Expression implements FunctionCall { ...@@ -1091,6 +1092,19 @@ public class Function extends Expression implements FunctionCall {
return result; return result;
} }
private Value[] getArray(Value v0) {
int t = v0.getType();
Value[] list;
if (t == Value.ARRAY) {
list = ((ValueArray) v0).getList();
} else if (t == Value.ROW) {
list = ((ValueRow) v0).getList();
} else {
list = null;
}
return list;
}
private static boolean cancelStatement(Session session, int targetSessionId) { private static boolean cancelStatement(Session session, int targetSessionId) {
session.getUser().checkAdmin(); session.getUser().checkAdmin();
Session[] sessions = session.getDatabase().getSessions(false); Session[] sessions = session.getDatabase().getSessions(false);
......
...@@ -1547,12 +1547,14 @@ public class JdbcDatabaseMetaData extends TraceObject implements ...@@ -1547,12 +1547,14 @@ public class JdbcDatabaseMetaData extends TraceObject implements
* </pre> * </pre>
* The complete list of keywords (including SQL-2003 keywords) is: * The complete list of keywords (including SQL-2003 keywords) is:
* <pre> * <pre>
* ALL, CHECK, CONSTRAINT, CROSS, CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, * ALL, ARRAY, CASE, CHECK, CONSTRAINT, CROSS, CURRENT_DATE,
* DISTINCT, EXCEPT, EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, * CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, DISTINCT, EXCEPT,
* HAVING, INNER, INTERSECT, INTERSECTS, IS, JOIN, LIKE, LIMIT, LOCALTIME, * EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, HAVING,
* LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL, OFFSET, ON, ORDER, PRIMARY, ROWNUM, * IF, INNER, INTERSECT, INTERSECTS, INTERVAL, IS, JOIN, LIKE,
* SELECT, SYSDATE, SYSTIME, SYSTIMESTAMP, TODAY, TOP, TRUE, UNION, UNIQUE, WHERE, * LIMIT, LOCALTIME, LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL,
* WINDOW, WITH * OFFSET, ON, ORDER, PRIMARY, ROW, ROWNUM, SELECT, SYSDATE,
* SYSTIME, SYSTIMESTAMP, TODAY, TOP, TRUE, UNION, UNIQUE, VALUES,
* WHERE, WINDOW, WITH
* </pre> * </pre>
* *
* @return a list of additional the keywords * @return a list of additional the keywords
...@@ -1560,7 +1562,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements ...@@ -1560,7 +1562,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements
@Override @Override
public String getSQLKeywords() { public String getSQLKeywords() {
debugCodeCall("getSQLKeywords"); debugCodeCall("getSQLKeywords");
return "INTERSECTS,LIMIT,MINUS,OFFSET,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP"; return "IF,INTERSECTS,LIMIT,MINUS,OFFSET,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP";
} }
/** /**
......
...@@ -22,10 +22,20 @@ public class ParserUtil { ...@@ -22,10 +22,20 @@ public class ParserUtil {
*/ */
public static final int ALL = IDENTIFIER + 1; public static final int ALL = IDENTIFIER + 1;
/**
* The token "ARRAY".
*/
public static final int ARRAY = ALL + 1;
/**
* The token "CASE".
*/
public static final int CASE = ARRAY + 1;
/** /**
* The token "CHECK". * The token "CHECK".
*/ */
public static final int CHECK = ALL + 1; public static final int CHECK = CASE + 1;
/** /**
* The token "CONSTRAINT". * The token "CONSTRAINT".
...@@ -52,10 +62,15 @@ public class ParserUtil { ...@@ -52,10 +62,15 @@ public class ParserUtil {
*/ */
public static final int CURRENT_TIMESTAMP = CURRENT_TIME + 1; public static final int CURRENT_TIMESTAMP = CURRENT_TIME + 1;
/**
* The token "CURRENT_USER".
*/
public static final int CURRENT_USER = CURRENT_TIMESTAMP + 1;
/** /**
* The token "DISTINCT". * The token "DISTINCT".
*/ */
public static final int DISTINCT = CURRENT_TIMESTAMP + 1; public static final int DISTINCT = CURRENT_USER + 1;
/** /**
* The token "EXCEPT". * The token "EXCEPT".
...@@ -107,20 +122,35 @@ public class ParserUtil { ...@@ -107,20 +122,35 @@ public class ParserUtil {
*/ */
public static final int HAVING = GROUP + 1; public static final int HAVING = GROUP + 1;
/**
* The token "IF".
*/
public static final int IF = HAVING + 1;
/** /**
* The token "INNER". * The token "INNER".
*/ */
public static final int INNER = HAVING + 1; public static final int INNER = IF + 1;
/** /**
* The token "INTERSECT". * The token "INTERSECT".
*/ */
public static final int INTERSECT = INNER + 1; public static final int INTERSECT = INNER + 1;
/**
* The token "INTERSECTS".
*/
public static final int INTERSECTS = INTERSECT + 1;
/**
* The token "INTERVAL".
*/
public static final int INTERVAL = INTERSECTS + 1;
/** /**
* The token "IS". * The token "IS".
*/ */
public static final int IS = INTERSECT + 1; public static final int IS = INTERVAL + 1;
/** /**
* The token "JOIN". * The token "JOIN".
...@@ -187,10 +217,15 @@ public class ParserUtil { ...@@ -187,10 +217,15 @@ public class ParserUtil {
*/ */
public static final int PRIMARY = ORDER + 1; public static final int PRIMARY = ORDER + 1;
/**
* The token "ROW".
*/
public static final int ROW = PRIMARY + 1;
/** /**
* The token "ROWNUM". * The token "ROWNUM".
*/ */
public static final int ROWNUM = PRIMARY + 1; public static final int ROWNUM = ROW + 1;
/** /**
* The token "SELECT". * The token "SELECT".
...@@ -212,10 +247,15 @@ public class ParserUtil { ...@@ -212,10 +247,15 @@ public class ParserUtil {
*/ */
public static final int UNIQUE = UNION + 1; public static final int UNIQUE = UNION + 1;
/**
* The token "VALUES".
*/
public static final int VALUES = UNIQUE + 1;
/** /**
* The token "WHERE". * The token "WHERE".
*/ */
public static final int WHERE = UNIQUE + 1; public static final int WHERE = VALUES + 1;
/** /**
* The token "WINDOW". * The token "WINDOW".
...@@ -312,10 +352,14 @@ public class ParserUtil { ...@@ -312,10 +352,14 @@ public class ParserUtil {
case 'A': case 'A':
if (eq("ALL", s, ignoreCase, start, end)) { if (eq("ALL", s, ignoreCase, start, end)) {
return ALL; return ALL;
} else if (eq("ARRAY", s, ignoreCase, start, end)) {
return ARRAY;
} }
return IDENTIFIER; return IDENTIFIER;
case 'C': case 'C':
if (eq("CHECK", s, ignoreCase, start, end)) { if (eq("CASE", s, ignoreCase, start, end)) {
return CASE;
} else if (eq("CHECK", s, ignoreCase, start, end)) {
return CHECK; return CHECK;
} else if (eq("CONSTRAINT", s, ignoreCase, start, end)) { } else if (eq("CONSTRAINT", s, ignoreCase, start, end)) {
return CONSTRAINT; return CONSTRAINT;
...@@ -327,6 +371,8 @@ public class ParserUtil { ...@@ -327,6 +371,8 @@ public class ParserUtil {
return CURRENT_TIME; return CURRENT_TIME;
} else if (eq("CURRENT_TIMESTAMP", s, ignoreCase, start, end)) { } else if (eq("CURRENT_TIMESTAMP", s, ignoreCase, start, end)) {
return CURRENT_TIMESTAMP; return CURRENT_TIMESTAMP;
} else if (eq("CURRENT_USER", s, ignoreCase, start, end)) {
return CURRENT_USER;
} }
return IDENTIFIER; return IDENTIFIER;
case 'D': case 'D':
...@@ -367,18 +413,19 @@ public class ParserUtil { ...@@ -367,18 +413,19 @@ public class ParserUtil {
} }
return IDENTIFIER; return IDENTIFIER;
case 'I': case 'I':
if (eq("INNER", s, ignoreCase, start, end)) { if (eq("IF", s, ignoreCase, start, end)) {
return IF;
} else if (eq("INNER", s, ignoreCase, start, end)) {
return INNER; return INNER;
} else if (eq("INTERSECT", s, ignoreCase, start, end)) { } else if (eq("INTERSECT", s, ignoreCase, start, end)) {
return INTERSECT; return INTERSECT;
} else if (eq("INTERSECTS", s, ignoreCase, start, end)) {
return INTERSECTS;
} else if (eq("INTERVAL", s, ignoreCase, start, end)) {
return INTERVAL;
} else if (eq("IS", s, ignoreCase, start, end)) { } else if (eq("IS", s, ignoreCase, start, end)) {
return IS; return IS;
} }
if (additionalKeywords) {
if (eq("INTERSECTS", s, ignoreCase, start, end)) {
return KEYWORD;
}
}
return IDENTIFIER; return IDENTIFIER;
case 'J': case 'J':
if (eq("JOIN", s, ignoreCase, start, end)) { if (eq("JOIN", s, ignoreCase, start, end)) {
...@@ -425,7 +472,9 @@ public class ParserUtil { ...@@ -425,7 +472,9 @@ public class ParserUtil {
} }
return IDENTIFIER; return IDENTIFIER;
case 'R': case 'R':
if (eq("ROWNUM", s, ignoreCase, start, end)) { if (eq("ROW", s, ignoreCase, start, end)) {
return ROW;
} else if (eq("ROWNUM", s, ignoreCase, start, end)) {
return ROWNUM; return ROWNUM;
} }
return IDENTIFIER; return IDENTIFIER;
...@@ -457,6 +506,11 @@ public class ParserUtil { ...@@ -457,6 +506,11 @@ public class ParserUtil {
return UNION; return UNION;
} }
return IDENTIFIER; return IDENTIFIER;
case 'V':
if (eq("VALUES", s, ignoreCase, start, end)) {
return VALUES;
}
return IDENTIFIER;
case 'W': case 'W':
if (eq("WHERE", s, ignoreCase, start, end)) { if (eq("WHERE", s, ignoreCase, start, end)) {
return WHERE; return WHERE;
......
...@@ -773,6 +773,19 @@ public class DataType { ...@@ -773,6 +773,19 @@ public class DataType {
ValueInt.get(value); ValueInt.get(value);
break; break;
} }
case Value.ROW: {
Object[] list = (Object[]) rs.getObject(columnIndex);
if (list == null) {
return ValueNull.INSTANCE;
}
int len = list.length;
Value[] values = new Value[len];
for (int i = 0; i < len; i++) {
values[i] = DataType.convertToValue(session, list[i], Value.NULL);
}
v = ValueRow.get(values);
break;
}
case Value.RESULT_SET: { case Value.RESULT_SET: {
ResultSet x = (ResultSet) rs.getObject(columnIndex); ResultSet x = (ResultSet) rs.getObject(columnIndex);
if (x == null) { if (x == null) {
......
...@@ -322,27 +322,32 @@ public class Transfer { ...@@ -322,27 +322,32 @@ public class Transfer {
*/ */
public void writeValue(Value v) throws IOException { public void writeValue(Value v) throws IOException {
int type = v.getType(); int type = v.getType();
writeInt(type);
switch (type) { switch (type) {
case Value.NULL: case Value.NULL:
writeInt(Value.NULL);
break; break;
case Value.BYTES: case Value.BYTES:
case Value.JAVA_OBJECT: case Value.JAVA_OBJECT:
writeInt(type);
writeBytes(v.getBytesNoCopy()); writeBytes(v.getBytesNoCopy());
break; break;
case Value.UUID: { case Value.UUID: {
writeInt(Value.UUID);
ValueUuid uuid = (ValueUuid) v; ValueUuid uuid = (ValueUuid) v;
writeLong(uuid.getHigh()); writeLong(uuid.getHigh());
writeLong(uuid.getLow()); writeLong(uuid.getLow());
break; break;
} }
case Value.BOOLEAN: case Value.BOOLEAN:
writeInt(Value.BOOLEAN);
writeBoolean(v.getBoolean()); writeBoolean(v.getBoolean());
break; break;
case Value.BYTE: case Value.BYTE:
writeInt(Value.BYTE);
writeByte(v.getByte()); writeByte(v.getByte());
break; break;
case Value.TIME: case Value.TIME:
writeInt(Value.TIME);
if (version >= Constants.TCP_PROTOCOL_VERSION_9) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
writeLong(((ValueTime) v).getNanos()); writeLong(((ValueTime) v).getNanos());
} else { } else {
...@@ -350,6 +355,7 @@ public class Transfer { ...@@ -350,6 +355,7 @@ public class Transfer {
} }
break; break;
case Value.DATE: case Value.DATE:
writeInt(Value.DATE);
if (version >= Constants.TCP_PROTOCOL_VERSION_9) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
writeLong(((ValueDate) v).getDateValue()); writeLong(((ValueDate) v).getDateValue());
} else { } else {
...@@ -357,6 +363,7 @@ public class Transfer { ...@@ -357,6 +363,7 @@ public class Transfer {
} }
break; break;
case Value.TIMESTAMP: { case Value.TIMESTAMP: {
writeInt(Value.TIMESTAMP);
if (version >= Constants.TCP_PROTOCOL_VERSION_9) { if (version >= Constants.TCP_PROTOCOL_VERSION_9) {
ValueTimestamp ts = (ValueTimestamp) v; ValueTimestamp ts = (ValueTimestamp) v;
writeLong(ts.getDateValue()); writeLong(ts.getDateValue());
...@@ -369,6 +376,7 @@ public class Transfer { ...@@ -369,6 +376,7 @@ public class Transfer {
break; break;
} }
case Value.TIMESTAMP_TZ: { case Value.TIMESTAMP_TZ: {
writeInt(Value.TIMESTAMP_TZ);
ValueTimestampTimeZone ts = (ValueTimestampTimeZone) v; ValueTimestampTimeZone ts = (ValueTimestampTimeZone) v;
writeLong(ts.getDateValue()); writeLong(ts.getDateValue());
writeLong(ts.getTimeNanos()); writeLong(ts.getTimeNanos());
...@@ -376,29 +384,37 @@ public class Transfer { ...@@ -376,29 +384,37 @@ public class Transfer {
break; break;
} }
case Value.DECIMAL: case Value.DECIMAL:
writeInt(Value.DECIMAL);
writeString(v.getString()); writeString(v.getString());
break; break;
case Value.DOUBLE: case Value.DOUBLE:
writeInt(Value.DOUBLE);
writeDouble(v.getDouble()); writeDouble(v.getDouble());
break; break;
case Value.FLOAT: case Value.FLOAT:
writeInt(Value.FLOAT);
writeFloat(v.getFloat()); writeFloat(v.getFloat());
break; break;
case Value.INT: case Value.INT:
writeInt(Value.INT);
writeInt(v.getInt()); writeInt(v.getInt());
break; break;
case Value.LONG: case Value.LONG:
writeInt(Value.LONG);
writeLong(v.getLong()); writeLong(v.getLong());
break; break;
case Value.SHORT: case Value.SHORT:
writeInt(Value.SHORT);
writeInt(v.getShort()); writeInt(v.getShort());
break; break;
case Value.STRING: case Value.STRING:
case Value.STRING_IGNORECASE: case Value.STRING_IGNORECASE:
case Value.STRING_FIXED: case Value.STRING_FIXED:
writeInt(type);
writeString(v.getString()); writeString(v.getString());
break; break;
case Value.BLOB: { case Value.BLOB: {
writeInt(Value.BLOB);
if (version >= Constants.TCP_PROTOCOL_VERSION_11) { if (version >= Constants.TCP_PROTOCOL_VERSION_11) {
if (v instanceof ValueLobDb) { if (v instanceof ValueLobDb) {
ValueLobDb lob = (ValueLobDb) v; ValueLobDb lob = (ValueLobDb) v;
...@@ -429,6 +445,7 @@ public class Transfer { ...@@ -429,6 +445,7 @@ public class Transfer {
break; break;
} }
case Value.CLOB: { case Value.CLOB: {
writeInt(Value.CLOB);
if (version >= Constants.TCP_PROTOCOL_VERSION_11) { if (version >= Constants.TCP_PROTOCOL_VERSION_11) {
if (v instanceof ValueLobDb) { if (v instanceof ValueLobDb) {
ValueLobDb lob = (ValueLobDb) v; ValueLobDb lob = (ValueLobDb) v;
...@@ -456,6 +473,7 @@ public class Transfer { ...@@ -456,6 +473,7 @@ public class Transfer {
break; break;
} }
case Value.ARRAY: { case Value.ARRAY: {
writeInt(Value.ARRAY);
ValueArray va = (ValueArray) v; ValueArray va = (ValueArray) v;
Value[] list = va.getList(); Value[] list = va.getList();
int len = list.length; int len = list.length;
...@@ -471,12 +489,25 @@ public class Transfer { ...@@ -471,12 +489,25 @@ public class Transfer {
} }
break; break;
} }
case Value.ROW: {
writeInt(version >= Constants.TCP_PROTOCOL_VERSION_18 ? Value.ROW : Value.ARRAY);
ValueRow va = (ValueRow) v;
Value[] list = va.getList();
int len = list.length;
writeInt(len);
for (Value value : list) {
writeValue(value);
}
break;
}
case Value.ENUM: { case Value.ENUM: {
writeInt(Value.ENUM);
writeInt(v.getInt()); writeInt(v.getInt());
writeString(v.getString()); writeString(v.getString());
break; break;
} }
case Value.RESULT_SET: { case Value.RESULT_SET: {
writeInt(Value.RESULT_SET);
ResultInterface result = ((ValueResultSet) v).getResult(); ResultInterface result = ((ValueResultSet) v).getResult();
int columnCount = result.getVisibleColumnCount(); int columnCount = result.getVisibleColumnCount();
writeInt(columnCount); writeInt(columnCount);
...@@ -506,6 +537,7 @@ public class Transfer { ...@@ -506,6 +537,7 @@ public class Transfer {
break; break;
} }
case Value.GEOMETRY: case Value.GEOMETRY:
writeInt(Value.GEOMETRY);
if (version >= Constants.TCP_PROTOCOL_VERSION_14) { if (version >= Constants.TCP_PROTOCOL_VERSION_14) {
writeBytes(v.getBytesNoCopy()); writeBytes(v.getBytesNoCopy());
} else { } else {
...@@ -516,12 +548,17 @@ public class Transfer { ...@@ -516,12 +548,17 @@ public class Transfer {
case Value.INTERVAL_MONTH: case Value.INTERVAL_MONTH:
case Value.INTERVAL_DAY: case Value.INTERVAL_DAY:
case Value.INTERVAL_HOUR: case Value.INTERVAL_HOUR:
case Value.INTERVAL_MINUTE: { case Value.INTERVAL_MINUTE:
if (version >= Constants.TCP_PROTOCOL_VERSION_18) {
writeInt(type);
ValueInterval interval = (ValueInterval) v; ValueInterval interval = (ValueInterval) v;
writeBoolean(interval.isNegative()); writeBoolean(interval.isNegative());
writeLong(interval.getLeading()); writeLong(interval.getLeading());
break; } else {
writeInt(Value.STRING);
writeString(v.getString());
} }
break;
case Value.INTERVAL_SECOND: case Value.INTERVAL_SECOND:
case Value.INTERVAL_YEAR_TO_MONTH: case Value.INTERVAL_YEAR_TO_MONTH:
case Value.INTERVAL_DAY_TO_HOUR: case Value.INTERVAL_DAY_TO_HOUR:
...@@ -529,15 +566,21 @@ public class Transfer { ...@@ -529,15 +566,21 @@ public class Transfer {
case Value.INTERVAL_DAY_TO_SECOND: case Value.INTERVAL_DAY_TO_SECOND:
case Value.INTERVAL_HOUR_TO_MINUTE: case Value.INTERVAL_HOUR_TO_MINUTE:
case Value.INTERVAL_HOUR_TO_SECOND: case Value.INTERVAL_HOUR_TO_SECOND:
case Value.INTERVAL_MINUTE_TO_SECOND: { case Value.INTERVAL_MINUTE_TO_SECOND:
if (version >= Constants.TCP_PROTOCOL_VERSION_18) {
writeInt(type);
ValueInterval interval = (ValueInterval) v; ValueInterval interval = (ValueInterval) v;
writeBoolean(interval.isNegative()); writeBoolean(interval.isNegative());
writeLong(interval.getLeading()); writeLong(interval.getLeading());
writeLong(interval.getRemaining()); writeLong(interval.getRemaining());
break; } else {
writeInt(Value.STRING);
writeString(v.getString());
} }
break;
default: default:
if (JdbcUtils.customDataTypesHandler != null) { if (JdbcUtils.customDataTypesHandler != null) {
writeInt(type);
writeBytes(v.getBytesNoCopy()); writeBytes(v.getBytesNoCopy());
break; break;
} }
...@@ -682,6 +725,14 @@ public class Transfer { ...@@ -682,6 +725,14 @@ public class Transfer {
} }
return ValueArray.get(componentType, list); return ValueArray.get(componentType, list);
} }
case Value.ROW: {
int len = readInt();
Value[] list = new Value[len];
for (int i = 0; i < len; i++) {
list[i] = readValue();
}
return ValueRow.get(list);
}
case Value.RESULT_SET: { case Value.RESULT_SET: {
SimpleResult rs = new SimpleResult(); SimpleResult rs = new SimpleResult();
int columns = readInt(); int columns = readInt();
......
...@@ -241,10 +241,15 @@ public abstract class Value { ...@@ -241,10 +241,15 @@ public abstract class Value {
*/ */
public static final int INTERVAL_MINUTE_TO_SECOND = 38; public static final int INTERVAL_MINUTE_TO_SECOND = 38;
/**
* The value type for ROW values.
*/
public static final int ROW = 39;
/** /**
* The number of value types. * The number of value types.
*/ */
public static final int TYPE_COUNT = INTERVAL_MINUTE_TO_SECOND + 1; public static final int TYPE_COUNT = ROW + 1;
private static SoftReference<Value[]> softCache; private static SoftReference<Value[]> softCache;
...@@ -439,6 +444,8 @@ public abstract class Value { ...@@ -439,6 +444,8 @@ public abstract class Value {
return 44_000; return 44_000;
case ARRAY: case ARRAY:
return 50_000; return 50_000;
case ROW:
return 50_500;
case RESULT_SET: case RESULT_SET:
return 51_000; return 51_000;
case ENUM: case ENUM:
...@@ -790,6 +797,8 @@ public abstract class Value { ...@@ -790,6 +797,8 @@ public abstract class Value {
return convertToIntervalDayTime(targetType); return convertToIntervalDayTime(targetType);
case ARRAY: case ARRAY:
return convertToArray(); return convertToArray();
case ROW:
return convertToRow();
case RESULT_SET: case RESULT_SET:
return convertToResultSet(); return convertToResultSet();
default: default:
...@@ -1298,7 +1307,37 @@ public abstract class Value { ...@@ -1298,7 +1307,37 @@ public abstract class Value {
} }
private ValueArray convertToArray() { private ValueArray convertToArray() {
return ValueArray.get(new Value[] { ValueString.get(getString()) }); Value[] a;
switch (getType()) {
case ROW:
a = ((ValueRow) this).getList();
break;
case BLOB:
case CLOB:
case RESULT_SET:
a = new Value[] { ValueString.get(getString()) };
break;
default:
a = new Value[] { this };
}
return ValueArray.get(a);
}
private ValueRow convertToRow() {
Value[] a;
switch (getType()) {
case ARRAY:
a = ((ValueArray) this).getList();
break;
case BLOB:
case CLOB:
case RESULT_SET:
a = new Value[] { ValueString.get(getString()) };
break;
default:
a = new Value[] { this };
}
return ValueRow.get(a);
} }
private ValueResultSet convertToResultSet() { private ValueResultSet convertToResultSet() {
......
...@@ -13,7 +13,6 @@ import java.util.Arrays; ...@@ -13,7 +13,6 @@ import java.util.Arrays;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
import org.h2.util.StatementBuilder;
/** /**
* Implementation of the ARRAY data type. * Implementation of the ARRAY data type.
...@@ -103,12 +102,14 @@ public class ValueArray extends Value { ...@@ -103,12 +102,14 @@ public class ValueArray extends Value {
@Override @Override
public String getString() { public String getString() {
StatementBuilder buff = new StatementBuilder("("); StringBuilder builder = new StringBuilder().append('[');
for (Value v : values) { for (int i = 0; i < values.length; i++) {
buff.appendExceptFirst(", "); if (i > 0) {
buff.append(v.getString()); builder.append(", ");
} }
return buff.append(')').toString(); builder.append(values[i].getString());
}
return builder.append(']').toString();
} }
@Override @Override
...@@ -156,7 +157,7 @@ public class ValueArray extends Value { ...@@ -156,7 +157,7 @@ public class ValueArray extends Value {
@Override @Override
public StringBuilder getSQL(StringBuilder builder) { public StringBuilder getSQL(StringBuilder builder) {
builder.append('('); builder.append("ARRAY [");
int length = values.length; int length = values.length;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
if (i > 0) { if (i > 0) {
...@@ -164,20 +165,20 @@ public class ValueArray extends Value { ...@@ -164,20 +165,20 @@ public class ValueArray extends Value {
} }
values[i].getSQL(builder); values[i].getSQL(builder);
} }
if (length == 1) { return builder.append(']');
builder.append(',');
}
return builder.append(')');
} }
@Override @Override
public String getTraceSQL() { public String getTraceSQL() {
StatementBuilder buff = new StatementBuilder("("); StringBuilder builder = new StringBuilder("[");
for (Value v : values) { for (int i = 0; i < values.length; i++) {
buff.appendExceptFirst(", "); if (i > 0) {
buff.append(v == null ? "null" : v.getTraceSQL()); builder.append(", ");
}
Value v = values[i];
builder.append(v == null ? "null" : v.getTraceSQL());
} }
return buff.append(')').toString(); return builder.append(']').toString();
} }
@Override @Override
......
/*
* 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.value;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.util.MathUtils;
/**
* Row value.
*/
public class ValueRow extends Value {
/**
* Empty row.
*/
private static final Object EMPTY = get(new Value[0]);
private final Value[] values;
private int hash;
private ValueRow(Value[] list) {
this.values = list;
}
/**
* Get or create a row value for the given value array.
* Do not clone the data.
*
* @param list the value array
* @return the value
*/
public static ValueRow get(Value[] list) {
return new ValueRow(list);
}
/**
* Returns empty row.
*
* @return empty row
*/
public static ValueRow getEmpty() {
return (ValueRow) EMPTY;
}
@Override
public int hashCode() {
if (hash != 0) {
return hash;
}
int h = 1;
for (Value v : values) {
h = h * 31 + v.hashCode();
}
hash = h;
return h;
}
public Value[] getList() {
return values;
}
@Override
public int getType() {
return Value.ROW;
}
@Override
public long getPrecision() {
long p = 0;
for (Value v : values) {
p += v.getPrecision();
}
return p;
}
@Override
public String getString() {
StringBuilder builder = new StringBuilder("ROW (");
for (int i = 0; i < values.length; i++) {
if (i > 0) {
builder.append(", ");
}
builder.append(values[i].getString());
}
return builder.append(')').toString();
}
@Override
public int compareTypeSafe(Value o, CompareMode mode) {
ValueRow v = (ValueRow) o;
if (values == v.values) {
return 0;
}
int len = values.length;
if (len != v.values.length) {
throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
}
for (int i = 0; i < len; i++) {
Value v1 = values[i];
Value v2 = v.values[i];
int comp = v1.compareTo(v2, /* TODO */ null, mode);
if (comp != 0) {
return comp;
}
}
return 0;
}
@Override
public Object getObject() {
int len = values.length;
Object[] list = new Object[len];
for (int i = 0; i < len; i++) {
final Value value = values[i];
if (!SysProperties.OLD_RESULT_SET_GET_OBJECT) {
final int type = value.getType();
if (type == Value.BYTE || type == Value.SHORT) {
list[i] = value.getInt();
continue;
}
}
list[i] = value.getObject();
}
return list;
}
@Override
public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
throw getUnsupportedExceptionForOperation("PreparedStatement.set");
}
@Override
public StringBuilder getSQL(StringBuilder builder) {
builder.append("ROW (");
int length = values.length;
for (int i = 0; i < length; i++) {
if (i > 0) {
builder.append(", ");
}
values[i].getSQL(builder);
}
return builder.append(')');
}
@Override
public String getTraceSQL() {
StringBuilder builder = new StringBuilder("ROW (");
for (int i = 0; i < values.length; i++) {
if (i > 0) {
builder.append(", ");
}
Value v = values[i];
builder.append(v == null ? "null" : v.getTraceSQL());
}
return builder.append(')').toString();
}
@Override
public int getDisplaySize() {
long size = 0;
for (Value v : values) {
size += v.getDisplaySize();
}
return MathUtils.convertLongToInt(size);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof ValueRow)) {
return false;
}
ValueRow v = (ValueRow) other;
if (values == v.values) {
return true;
}
int len = values.length;
if (len != v.values.length) {
return false;
}
for (int i = 0; i < len; i++) {
if (!values[i].equals(v.values[i])) {
return false;
}
}
return true;
}
@Override
public int getMemory() {
int memory = 32;
for (Value v : values) {
memory += v.getMemory() + Constants.MEMORY_POINTER;
}
return memory;
}
@Override
public Value convertPrecision(long precision, boolean force) {
if (!force) {
return this;
}
int length = values.length;
Value[] newValues = new Value[length];
int i = 0;
boolean modified = false;
for (; i < length; i++) {
Value old = values[i];
Value v = old.convertPrecision(precision, true);
if (v != old) {
modified = true;
}
// empty byte arrays or strings have precision 0
// they count as precision 1 here
precision -= Math.max(1, v.getPrecision());
if (precision < 0) {
break;
}
newValues[i] = v;
}
if (i < length) {
return get(Arrays.copyOf(newValues, i));
}
return modified ? get(newValues) : this;
}
}
...@@ -196,6 +196,7 @@ import org.h2.test.unit.TestIntIntHashMap; ...@@ -196,6 +196,7 @@ import org.h2.test.unit.TestIntIntHashMap;
import org.h2.test.unit.TestIntPerfectHash; import org.h2.test.unit.TestIntPerfectHash;
import org.h2.test.unit.TestInterval; import org.h2.test.unit.TestInterval;
import org.h2.test.unit.TestJmx; import org.h2.test.unit.TestJmx;
import org.h2.test.unit.TestKeywords;
import org.h2.test.unit.TestLocalResultFactory; import org.h2.test.unit.TestLocalResultFactory;
import org.h2.test.unit.TestLocale; import org.h2.test.unit.TestLocale;
import org.h2.test.unit.TestMathUtils; import org.h2.test.unit.TestMathUtils;
...@@ -973,6 +974,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -973,6 +974,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestIntArray()); addTest(new TestIntArray());
addTest(new TestIntIntHashMap()); addTest(new TestIntIntHashMap());
addTest(new TestIntPerfectHash()); addTest(new TestIntPerfectHash());
addTest(new TestKeywords());
addTest(new TestMathUtils()); addTest(new TestMathUtils());
addTest(new TestMemoryUnmapper()); addTest(new TestMemoryUnmapper());
addTest(new TestMode()); addTest(new TestMode());
......
...@@ -157,8 +157,8 @@ public class TestFullText extends TestDb { ...@@ -157,8 +157,8 @@ public class TestFullText extends TestDb {
assertEquals("KEYS", rs.getMetaData().getColumnLabel(4)); assertEquals("KEYS", rs.getMetaData().getColumnLabel(4));
assertEquals("PUBLIC", rs.getString(1)); assertEquals("PUBLIC", rs.getString(1));
assertEquals("TEST", rs.getString(2)); assertEquals("TEST", rs.getString(2));
assertEquals("(ID)", rs.getString(3)); assertEquals("[ID]", rs.getString(3));
assertEquals("(1)", rs.getString(4)); assertEquals("[1]", rs.getString(4));
rs = stat.executeQuery("SELECT * FROM FT_SEARCH('this', 0, 0)"); rs = stat.executeQuery("SELECT * FROM FT_SEARCH('this', 0, 0)");
assertFalse(rs.next()); assertFalse(rs.next());
......
...@@ -958,9 +958,9 @@ public class TestFunctions extends TestDb implements AggregateFunction { ...@@ -958,9 +958,9 @@ public class TestFunctions extends TestDb implements AggregateFunction {
assertEquals("Hello", rs.getString(2)); assertEquals("Hello", rs.getString(2));
assertFalse(rs.next()); assertFalse(rs.next());
stat.execute("CREATE ALIAS ARRAY FOR \"" + stat.execute("CREATE ALIAS GET_ARRAY FOR \"" +
getClass().getName() + ".getArray\""); getClass().getName() + ".getArray\"");
rs = stat.executeQuery("CALL ARRAY()"); rs = stat.executeQuery("CALL GET_ARRAY()");
assertEquals(1, rs.getMetaData().getColumnCount()); assertEquals(1, rs.getMetaData().getColumnCount());
rs.next(); rs.next();
Array a = rs.getArray(1); Array a = rs.getArray(1);
......
...@@ -463,7 +463,7 @@ public class TestMetaData extends TestDb { ...@@ -463,7 +463,7 @@ public class TestMetaData extends TestDb {
assertEquals("schema", meta.getSchemaTerm()); assertEquals("schema", meta.getSchemaTerm());
assertEquals("\\", meta.getSearchStringEscape()); assertEquals("\\", meta.getSearchStringEscape());
assertEquals("INTERSECTS,LIMIT,MINUS,OFFSET,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP", assertEquals("IF,INTERSECTS,LIMIT,MINUS,OFFSET,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP",
meta.getSQLKeywords()); meta.getSQLKeywords());
assertTrue(meta.getURL().startsWith("jdbc:h2:")); assertTrue(meta.getURL().startsWith("jdbc:h2:"));
......
...@@ -1860,7 +1860,7 @@ public class TestResultSet extends TestDb { ...@@ -1860,7 +1860,7 @@ public class TestResultSet extends TestDb {
assertEquals(Types.NULL, array.getBaseType()); assertEquals(Types.NULL, array.getBaseType());
assertEquals("NULL", array.getBaseTypeName()); assertEquals("NULL", array.getBaseTypeName());
assertTrue(array.toString().endsWith(": (11, 12)")); assertTrue(array.toString().endsWith(": [11, 12]"));
// free // free
array.free(); array.free();
......
...@@ -144,7 +144,7 @@ public class TestScript extends TestDb { ...@@ -144,7 +144,7 @@ public class TestScript extends TestDb {
for (String s : new String[] { "array", "bigint", "binary", "blob", for (String s : new String[] { "array", "bigint", "binary", "blob",
"boolean", "char", "clob", "date", "decimal", decimal2, "double", "enum", "boolean", "char", "clob", "date", "decimal", decimal2, "double", "enum",
"geometry", "identity", "int", "interval", "other", "real", "smallint", "geometry", "identity", "int", "interval", "other", "real", "row", "smallint",
"time", "timestamp-with-timezone", "timestamp", "tinyint", "time", "timestamp-with-timezone", "timestamp", "tinyint",
"uuid", "varchar", "varchar-ignorecase" }) { "uuid", "varchar", "varchar-ignorecase" }) {
testScript("datatypes/" + s + ".sql"); testScript("datatypes/" + s + ".sql");
......
...@@ -16,10 +16,10 @@ SELECT (10, 20, 30)[4]; ...@@ -16,10 +16,10 @@ SELECT (10, 20, 30)[4];
>> null >> null
SELECT ARRAY[]; SELECT ARRAY[];
>> () >> []
SELECT ARRAY[10]; SELECT ARRAY[10];
>> (10) >> [10]
SELECT ARRAY[10, 20, 30]; SELECT ARRAY[10, 20, 30];
>> (10, 20, 30) >> [10, 20, 30]
-- 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
--
SELECT ROW (10);
>> ROW (10)
SELECT (10, 20, 30);
>> ROW (10, 20, 30)
...@@ -6,9 +6,15 @@ ...@@ -6,9 +6,15 @@
CREATE TABLE test (id INT NOT NULL, name VARCHAR); CREATE TABLE test (id INT NOT NULL, name VARCHAR);
> ok > ok
select * from test where id = (1, 2); select * from test where id = ARRAY [1, 2];
> exception COMPARING_ARRAY_TO_SCALAR > exception COMPARING_ARRAY_TO_SCALAR
insert into test values (1, 't');
> update count: 1
select * from test where id = (1, 2);
> exception COLUMN_COUNT_DOES_NOT_MATCH
drop table test; drop table test;
> ok > ok
......
...@@ -6,18 +6,18 @@ ...@@ -6,18 +6,18 @@
SELECT HISTOGRAM(X), HISTOGRAM(DISTINCT X) FROM VALUES (1), (2), (3), (1), (2), (NULL), (5) T(X); SELECT HISTOGRAM(X), HISTOGRAM(DISTINCT X) FROM VALUES (1), (2), (3), (1), (2), (NULL), (5) T(X);
> HISTOGRAM(C1) HISTOGRAM(DISTINCT C1) > HISTOGRAM(C1) HISTOGRAM(DISTINCT C1)
> ------------------------------------------- ------------------------------------------- > ------------------------------------------- -------------------------------------------
> ((null, 1), (1, 2), (2, 2), (3, 1), (5, 1)) ((null, 1), (1, 1), (2, 1), (3, 1), (5, 1)) > [[null, 1], [1, 2], [2, 2], [3, 1], [5, 1]] [[null, 1], [1, 1], [2, 1], [3, 1], [5, 1]]
> rows: 1 > rows: 1
SELECT HISTOGRAM(X) FILTER (WHERE X > 1), HISTOGRAM(DISTINCT X) FILTER (WHERE X > 1) SELECT HISTOGRAM(X) FILTER (WHERE X > 1), HISTOGRAM(DISTINCT X) FILTER (WHERE X > 1)
FROM VALUES (1), (2), (3), (1), (2), (NULL), (5) T(X); FROM VALUES (1), (2), (3), (1), (2), (NULL), (5) T(X);
> HISTOGRAM(C1) FILTER (WHERE (C1 > 1)) HISTOGRAM(DISTINCT C1) FILTER (WHERE (C1 > 1)) > HISTOGRAM(C1) FILTER (WHERE (C1 > 1)) HISTOGRAM(DISTINCT C1) FILTER (WHERE (C1 > 1))
> ------------------------------------- ---------------------------------------------- > ------------------------------------- ----------------------------------------------
> ((2, 2), (3, 1), (5, 1)) ((2, 1), (3, 1), (5, 1)) > [[2, 2], [3, 1], [5, 1]] [[2, 1], [3, 1], [5, 1]]
> rows: 1 > rows: 1
SELECT HISTOGRAM(X) FILTER (WHERE X > 0), HISTOGRAM(DISTINCT X) FILTER (WHERE X > 0) FROM VALUES (0) T(X); SELECT HISTOGRAM(X) FILTER (WHERE X > 0), HISTOGRAM(DISTINCT X) FILTER (WHERE X > 0) FROM VALUES (0) T(X);
> HISTOGRAM(C1) FILTER (WHERE (C1 > 0)) HISTOGRAM(DISTINCT C1) FILTER (WHERE (C1 > 0)) > HISTOGRAM(C1) FILTER (WHERE (C1 > 0)) HISTOGRAM(DISTINCT C1) FILTER (WHERE (C1 > 0))
> ------------------------------------- ---------------------------------------------- > ------------------------------------- ----------------------------------------------
> () () > [] []
> rows: 1 > rows: 1
...@@ -32,7 +32,7 @@ drop table test; ...@@ -32,7 +32,7 @@ drop table test;
> ok > ok
explain select * from table(id int = (1, 2), name varchar=('Hello', 'World')); explain select * from table(id int = (1, 2), name varchar=('Hello', 'World'));
>> SELECT TABLE.ID, TABLE.NAME FROM TABLE(ID INT=(1, 2), NAME VARCHAR=('Hello', 'World')) /* function */ >> SELECT TABLE.ID, TABLE.NAME FROM TABLE(ID INT=ROW (1, 2), NAME VARCHAR=ROW ('Hello', 'World')) /* function */
select * from table(id int=(1, 2), name varchar=('Hello', 'World')) x order by id; select * from table(id int=(1, 2), name varchar=('Hello', 'World')) x order by id;
> ID NAME > ID NAME
......
...@@ -36,10 +36,10 @@ SELECT * FROM UNNEST(ARRAY[1], ARRAY[2, 3, 4], ARRAY[5, 6]) WITH ORDINALITY; ...@@ -36,10 +36,10 @@ SELECT * FROM UNNEST(ARRAY[1], ARRAY[2, 3, 4], ARRAY[5, 6]) WITH ORDINALITY;
> rows: 3 > rows: 3
EXPLAIN SELECT * FROM UNNEST(ARRAY[1]); EXPLAIN SELECT * FROM UNNEST(ARRAY[1]);
>> SELECT UNNEST.C1 FROM UNNEST((1,)) /* function */ >> SELECT UNNEST.C1 FROM UNNEST(ARRAY [1]) /* function */
EXPLAIN SELECT * FROM UNNEST(ARRAY[1]) WITH ORDINALITY; EXPLAIN SELECT * FROM UNNEST(ARRAY[1]) WITH ORDINALITY;
>> SELECT UNNEST.C1, UNNEST.NORD FROM UNNEST((1,)) WITH ORDINALITY /* function */ >> SELECT UNNEST.C1, UNNEST.NORD FROM UNNEST(ARRAY [1]) WITH ORDINALITY /* function */
SELECT 1 IN(UNNEST(ARRAY[1, 2, 3])); SELECT 1 IN(UNNEST(ARRAY[1, 2, 3]));
>> TRUE >> TRUE
...@@ -80,8 +80,8 @@ INSERT INTO TEST VALUES (2, ARRAY[2, 4]), (3, ARRAY[2, 5]); ...@@ -80,8 +80,8 @@ INSERT INTO TEST VALUES (2, ARRAY[2, 4]), (3, ARRAY[2, 5]);
SELECT A, B, A IN(UNNEST(B)) FROM TEST; SELECT A, B, A IN(UNNEST(B)) FROM TEST;
> A B A IN(UNNEST(B)) > A B A IN(UNNEST(B))
> - ------ --------------- > - ------ ---------------
> 2 (2, 4) TRUE > 2 [2, 4] TRUE
> 3 (2, 5) FALSE > 3 [2, 5] FALSE
> rows: 2 > rows: 2
DROP TABLE TEST; DROP TABLE TEST;
......
...@@ -366,14 +366,14 @@ explain select -cast(0 as real), -cast(0 as double); ...@@ -366,14 +366,14 @@ explain select -cast(0 as real), -cast(0 as double);
select () empty; select () empty;
> EMPTY > EMPTY
> ----- > ------
> () > ROW ()
> rows: 1 > rows: 1
select (1,) one_element; select (1,) one_element;
> ONE_ELEMENT > ONE_ELEMENT
> ----------- > -----------
> (1) > ROW (1)
> rows: 1 > rows: 1
select (1) one; select (1) one;
...@@ -1401,9 +1401,9 @@ insert into test values(1, (1, 1)), (2, (1, 2)), (3, (1, 1, 1)); ...@@ -1401,9 +1401,9 @@ insert into test values(1, (1, 1)), (2, (1, 2)), (3, (1, 1, 1));
select * from test order by data; select * from test order by data;
> ID DATA > ID DATA
> -- --------- > -- ---------
> 1 (1, 1) > 1 [1, 1]
> 3 (1, 1, 1) > 3 [1, 1, 1]
> 2 (1, 2) > 2 [1, 2]
> rows (ordered): 3 > rows (ordered): 3
drop table test; drop table test;
...@@ -2003,9 +2003,9 @@ drop table people, cars; ...@@ -2003,9 +2003,9 @@ drop table people, cars;
> ok > ok
select (1, 2); select (1, 2);
> 1, 2 > ROW (1, 2)
> ------ > ----------
> (1, 2) > ROW (1, 2)
> rows: 1 > rows: 1
create table array_test(x array); create table array_test(x array);
...@@ -2017,7 +2017,7 @@ insert into array_test values((1, 2, 3)), ((2, 3, 4)); ...@@ -2017,7 +2017,7 @@ insert into array_test values((1, 2, 3)), ((2, 3, 4));
select * from array_test where x = (1, 2, 3); select * from array_test where x = (1, 2, 3);
> X > X
> --------- > ---------
> (1, 2, 3) > [1, 2, 3]
> rows: 1 > rows: 1
drop table array_test; drop table array_test;
...@@ -3177,7 +3177,7 @@ drop table test; ...@@ -3177,7 +3177,7 @@ drop table test;
call select 1.0/3.0*3.0, 100.0/2.0, -25.0/100.0, 0.0/3.0, 6.9/2.0, 0.72179425150347250912311550800000 / 5314251955.21; call select 1.0/3.0*3.0, 100.0/2.0, -25.0/100.0, 0.0/3.0, 6.9/2.0, 0.72179425150347250912311550800000 / 5314251955.21;
> SELECT 0.999999999999999999999999990, 50, -0.25, 0, 3.45, 1.35822361752313607260107721120531135706133161972E-10 FROM SYSTEM_RANGE(1, 1) /* PUBLIC.RANGE_INDEX */ /* scanCount: 2 */ > SELECT 0.999999999999999999999999990, 50, -0.25, 0, 3.45, 1.35822361752313607260107721120531135706133161972E-10 FROM SYSTEM_RANGE(1, 1) /* PUBLIC.RANGE_INDEX */ /* scanCount: 2 */
> ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- > -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> (0.999999999999999999999999990, 50, -0.25, 0, 3.45, 1.35822361752313607260107721120531135706133161972E-10) > ROW (0.999999999999999999999999990, 50, -0.25, 0, 3.45, 1.35822361752313607260107721120531135706133161972E-10)
> rows: 1 > rows: 1
call (select x from dual where x is null); call (select x from dual where x is null);
...@@ -3254,9 +3254,12 @@ select count(*) from test where id in ((select id from test)); ...@@ -3254,9 +3254,12 @@ select count(*) from test where id in ((select id from test));
select count(*) from test where id = ((select id from test)); select count(*) from test where id = ((select id from test));
> exception SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW > exception SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW
select count(*) from test where id = ((select id from test), 1); select count(*) from test where id = ARRAY [(select id from test), 1];
> exception COMPARING_ARRAY_TO_SCALAR > exception COMPARING_ARRAY_TO_SCALAR
select count(*) from test where id = ((select id from test fetch first row only), 1);
> exception COLUMN_COUNT_DOES_NOT_MATCH
select (select id from test where 1=0) from test; select (select id from test where 1=0) from test;
> SELECT ID FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan: FALSE */ WHERE FALSE > SELECT ID FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan: FALSE */ WHERE FALSE
> ------------------------------------------------------------------------- > -------------------------------------------------------------------------
...@@ -4315,8 +4318,8 @@ update test set (id, name)=(select id+1, name || 'Ho' from test t1 where test.id ...@@ -4315,8 +4318,8 @@ update test set (id, name)=(select id+1, name || 'Ho' from test t1 where test.id
> update count: 2 > update count: 2
explain update test set (id, name)=(id+1, name || 'Hi'); explain update test set (id, name)=(id+1, name || 'Hi');
#+mvStore#>> UPDATE PUBLIC.TEST /* PUBLIC.TEST.tableScan */ SET ID = ARRAY_GET(((ID + 1), (NAME || 'Hi')), 1), NAME = ARRAY_GET(((ID + 1), (NAME || 'Hi')), 2) #+mvStore#>> UPDATE PUBLIC.TEST /* PUBLIC.TEST.tableScan */ SET ID = ARRAY_GET(ROW ((ID + 1), (NAME || 'Hi')), 1), NAME = ARRAY_GET(ROW ((ID + 1), (NAME || 'Hi')), 2)
#-mvStore#>> UPDATE PUBLIC.TEST /* PUBLIC.PRIMARY_KEY_2 */ SET ID = ARRAY_GET(((ID + 1), (NAME || 'Hi')), 1), NAME = ARRAY_GET(((ID + 1), (NAME || 'Hi')), 2) #-mvStore#>> UPDATE PUBLIC.TEST /* PUBLIC.PRIMARY_KEY_2 */ SET ID = ARRAY_GET(ROW ((ID + 1), (NAME || 'Hi')), 1), NAME = ARRAY_GET(ROW ((ID + 1), (NAME || 'Hi')), 2)
explain update test set (id, name)=(select id+1, name || 'Ho' from test t1 where test.id=t1.id); explain update test set (id, name)=(select id+1, name || 'Ho' from test t1 where test.id=t1.id);
#+mvStore#>> UPDATE PUBLIC.TEST /* PUBLIC.TEST.tableScan */ SET ID = ARRAY_GET((SELECT (ID + 1), (NAME || 'Ho') FROM PUBLIC.TEST T1 /* PUBLIC.PRIMARY_KEY_2: ID = TEST.ID */ WHERE TEST.ID = T1.ID), 1), NAME = ARRAY_GET((SELECT (ID + 1), (NAME || 'Ho') FROM PUBLIC.TEST T1 /* PUBLIC.PRIMARY_KEY_2: ID = TEST.ID */ WHERE TEST.ID = T1.ID), 2) #+mvStore#>> UPDATE PUBLIC.TEST /* PUBLIC.TEST.tableScan */ SET ID = ARRAY_GET((SELECT (ID + 1), (NAME || 'Ho') FROM PUBLIC.TEST T1 /* PUBLIC.PRIMARY_KEY_2: ID = TEST.ID */ WHERE TEST.ID = T1.ID), 1), NAME = ARRAY_GET((SELECT (ID + 1), (NAME || 'Ho') FROM PUBLIC.TEST T1 /* PUBLIC.PRIMARY_KEY_2: ID = TEST.ID */ WHERE TEST.ID = T1.ID), 2)
......
/*
* 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.test.unit;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashSet;
import org.h2.command.Parser;
import org.h2.test.TestBase;
import org.h2.util.ParserUtil;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* Tests keywords.
*/
public class TestKeywords extends TestBase {
/**
* Run just this test.
*
* @param a
* ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
final HashSet<String> set = new HashSet<>();
ClassReader r = new ClassReader(Parser.class.getResourceAsStream("Parser.class"));
r.accept(new ClassVisitor(Opcodes.ASM6) {
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
add(set, value);
return null;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
return new MethodVisitor(Opcodes.ASM6) {
@Override
public void visitLdcInsn(Object value) {
add(set, value);
}
};
}
void add(HashSet<String> set, Object value) {
if (!(value instanceof String)) {
return;
}
String s = (String) value;
int l = s.length();
if (l == 0 || ParserUtil.getSaveTokenType(s, false, 0, l, true) != ParserUtil.IDENTIFIER) {
return;
}
for (int i = 0; i < l; i++) {
char ch = s.charAt(i);
if ((ch < 'A' || ch > 'Z') && ch != '_') {
return;
}
}
set.add(s);
}
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:keywords")) {
Statement stat = conn.createStatement();
for (String s : set) {
// _ROWID_ is a special virtual column
String column = s.equals("_ROWID_") ? "C" : s;
try {
stat.execute("CREATE TABLE " + s + '(' + column + " INT)");
stat.execute("INSERT INTO " + s + '(' + column + ") VALUES (10)");
try (ResultSet rs = stat.executeQuery("SELECT " + column + " FROM " + s)) {
assertTrue(rs.next());
assertEquals(10, rs.getInt(1));
assertFalse(rs.next());
}
} catch (Throwable t) {
throw new AssertionError(s + " cannot be used as identifier.", t);
}
}
}
}
}
...@@ -170,7 +170,7 @@ public class TestValue extends TestDb { ...@@ -170,7 +170,7 @@ public class TestValue extends TestDb {
assertEquals(5, v.convertPrecision(5, true).getPrecision()); assertEquals(5, v.convertPrecision(5, true).getPrecision());
v = ValueArray.get(new Value[]{ValueString.get(""), ValueString.get("")}); v = ValueArray.get(new Value[]{ValueString.get(""), ValueString.get("")});
assertEquals(0, v.getPrecision()); assertEquals(0, v.getPrecision());
assertEquals("('')", v.convertPrecision(1, true).toString()); assertEquals("['']", v.convertPrecision(1, true).toString());
v = ValueBytes.get(spaces.getBytes()); v = ValueBytes.get(spaces.getBytes());
assertEquals(100, v.getPrecision()); assertEquals(100, v.getPrecision());
......
...@@ -43,6 +43,7 @@ import org.h2.value.ValueJavaObject; ...@@ -43,6 +43,7 @@ import org.h2.value.ValueJavaObject;
import org.h2.value.ValueLong; import org.h2.value.ValueLong;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
import org.h2.value.ValueResultSet; import org.h2.value.ValueResultSet;
import org.h2.value.ValueRow;
import org.h2.value.ValueShort; import org.h2.value.ValueShort;
import org.h2.value.ValueString; import org.h2.value.ValueString;
import org.h2.value.ValueStringFixed; import org.h2.value.ValueStringFixed;
...@@ -208,14 +209,10 @@ public class TestValueMemory extends TestBase implements DataHandler { ...@@ -208,14 +209,10 @@ public class TestValueMemory extends TestBase implements DataHandler {
String s = randomString(len); String s = randomString(len);
return getLobStorage().createClob(new StringReader(s), len); return getLobStorage().createClob(new StringReader(s), len);
} }
case Value.ARRAY: { case Value.ARRAY:
int len = random.nextInt(20); return ValueArray.get(createArray());
Value[] list = new Value[len]; case Value.ROW:
for (int i = 0; i < list.length; i++) { return ValueRow.get(createArray());
list[i] = create(Value.STRING);
}
return ValueArray.get(list);
}
case Value.RESULT_SET: case Value.RESULT_SET:
return ValueResultSet.get(new SimpleResult()); return ValueResultSet.get(new SimpleResult());
case Value.JAVA_OBJECT: case Value.JAVA_OBJECT:
...@@ -254,6 +251,15 @@ public class TestValueMemory extends TestBase implements DataHandler { ...@@ -254,6 +251,15 @@ public class TestValueMemory extends TestBase implements DataHandler {
} }
} }
private Value[] createArray() throws SQLException {
int len = random.nextInt(20);
Value[] list = new Value[len];
for (int i = 0; i < list.length; i++) {
list[i] = create(Value.STRING);
}
return list;
}
private byte[] randomBytes(int len) { private byte[] randomBytes(int len) {
byte[] data = new byte[len]; byte[] data = new byte[len];
if (random.nextBoolean()) { if (random.nextBoolean()) {
......
...@@ -163,9 +163,6 @@ public class Build extends BuildBase { ...@@ -163,9 +163,6 @@ public class Build extends BuildBase {
downloadUsingMaven("ext/org.jacoco.report-0.8.0.jar", downloadUsingMaven("ext/org.jacoco.report-0.8.0.jar",
"org.jacoco", "org.jacoco.report", "0.8.0", "org.jacoco", "org.jacoco.report", "0.8.0",
"1bcab2a451f5a382bc674857c8f3f6d3fa52151d"); "1bcab2a451f5a382bc674857c8f3f6d3fa52151d");
downloadUsingMaven("ext/asm-6.1.jar",
"org.ow2.asm", "asm", "6.1",
"94a0d17ba8eb24833cd54253ace9b053786a9571");
downloadUsingMaven("ext/asm-commons-6.1.jar", downloadUsingMaven("ext/asm-commons-6.1.jar",
"org.ow2.asm", "asm-commons", "6.1", "org.ow2.asm", "asm-commons", "6.1",
"8a8d242d7ce00fc937a245fae5b65763d13f7cd1"); "8a8d242d7ce00fc937a245fae5b65763d13f7cd1");
...@@ -272,6 +269,7 @@ public class Build extends BuildBase { ...@@ -272,6 +269,7 @@ public class Build extends BuildBase {
File.pathSeparator + "ext/org.osgi.core-4.2.0.jar" + File.pathSeparator + "ext/org.osgi.core-4.2.0.jar" +
File.pathSeparator + "ext/org.osgi.enterprise-4.2.0.jar" + File.pathSeparator + "ext/org.osgi.enterprise-4.2.0.jar" +
File.pathSeparator + "ext/jts-core-1.15.0.jar" + File.pathSeparator + "ext/jts-core-1.15.0.jar" +
File.pathSeparator + "ext/asm-6.1.jar" +
File.pathSeparator + javaToolsJar; File.pathSeparator + javaToolsJar;
FileList files; FileList files;
if (clientOnly) { if (clientOnly) {
...@@ -386,6 +384,9 @@ public class Build extends BuildBase { ...@@ -386,6 +384,9 @@ public class Build extends BuildBase {
downloadOrVerify("ext/junit-4.12.jar", downloadOrVerify("ext/junit-4.12.jar",
"junit", "junit", "4.12", "junit", "junit", "4.12",
"2973d150c0dc1fefe998f834810d68f278ea58ec", offline); "2973d150c0dc1fefe998f834810d68f278ea58ec", offline);
downloadUsingMaven("ext/asm-6.1.jar",
"org.ow2.asm", "asm", "6.1",
"94a0d17ba8eb24833cd54253ace9b053786a9571");
} }
private void downloadOrVerify(String target, String group, String artifact, private void downloadOrVerify(String target, String group, String artifact,
...@@ -962,6 +963,7 @@ public class Build extends BuildBase { ...@@ -962,6 +963,7 @@ public class Build extends BuildBase {
File.pathSeparator + "ext/jts-core-1.15.0.jar" + File.pathSeparator + "ext/jts-core-1.15.0.jar" +
File.pathSeparator + "ext/slf4j-api-1.6.0.jar" + File.pathSeparator + "ext/slf4j-api-1.6.0.jar" +
File.pathSeparator + "ext/slf4j-nop-1.6.0.jar" + File.pathSeparator + "ext/slf4j-nop-1.6.0.jar" +
File.pathSeparator + "ext/asm-6.1.jar" +
File.pathSeparator + javaToolsJar; File.pathSeparator + javaToolsJar;
int version = getJavaVersion(); int version = getJavaVersion();
if (version >= 9) { if (version >= 9) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论