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

Merge pull request #1611 from katzyn/row

Handle null elements in array and row values in comparison operations properly
...@@ -412,8 +412,8 @@ public class Database implements DataHandler { ...@@ -412,8 +412,8 @@ public class Database implements DataHandler {
} }
/** /**
* Compare two values with the current comparison mode. The values may not * Compare two values with the current comparison mode. The values may have
* be of the same type. * different data types including NULL.
* *
* @param a the first value * @param a the first value
* @param b the second value * @param b the second value
...@@ -424,6 +424,24 @@ public class Database implements DataHandler { ...@@ -424,6 +424,24 @@ public class Database implements DataHandler {
return a.compareTo(b, mode, compareMode); return a.compareTo(b, mode, compareMode);
} }
/**
* Compare two values with the current comparison mode. The values may have
* different data types including NULL.
*
* @param v the other value
* @param databaseMode the database mode
* @param compareMode the compare mode
* @param a the first value
* @param b the second value
* @param forEquality perform only check for equality (= or <>)
* @return 0 if both values are equal, -1 if the first value is smaller, 1
* if the second value is larger, {@link Integer#MIN_VALUE} if order
* is not defined due to NULL comparison
*/
public int compareWithNull(Value a, Value b, boolean forEquality) {
return a.compareWithNull(b, forEquality, mode, compareMode);
}
/** /**
* Compare two values with the current comparison mode. The values must be * Compare two values with the current comparison mode. The values must be
* of the same type. * of the same type.
......
...@@ -283,58 +283,101 @@ public class Comparison extends Condition { ...@@ -283,58 +283,101 @@ public class Comparison extends Condition {
} }
return ValueBoolean.get(result); return ValueBoolean.get(result);
} }
if (l == ValueNull.INSTANCE) { // Optimization: do not evaluate right if not necessary
if ((compareType & NULL_SAFE) == 0) { if (l == ValueNull.INSTANCE && (compareType & NULL_SAFE) == 0) {
return ValueNull.INSTANCE; return ValueNull.INSTANCE;
}
}
Value r = right.getValue(session);
if (r == ValueNull.INSTANCE) {
if ((compareType & NULL_SAFE) == 0) {
return ValueNull.INSTANCE;
}
} }
return ValueBoolean.get(compareNotNull(database, l, r, compareType)); return compare(database, l, right.getValue(session), compareType);
} }
/** /**
* Compare two values, given the values are not NULL. * Compare two values.
* *
* @param database the database * @param database the database
* @param l the first value * @param l the first value
* @param r the second value * @param r the second value
* @param compareType the compare type * @param compareType the compare type
* @return true if the comparison indicated by the comparison type evaluates * @return result of comparison, either TRUE, FALSE, or NULL
* to true
*/ */
static boolean compareNotNull(Database database, Value l, Value r, static Value compare(Database database, Value l, Value r, int compareType) {
int compareType) { Value result;
boolean result;
switch (compareType) { switch (compareType) {
case EQUAL: case EQUAL: {
int cmp = database.compareWithNull(l, r, true);
if (cmp == 0) {
result = ValueBoolean.TRUE;
} else if (cmp == Integer.MIN_VALUE) {
result = ValueNull.INSTANCE;
} else {
result = ValueBoolean.FALSE;
}
break;
}
case EQUAL_NULL_SAFE: case EQUAL_NULL_SAFE:
result = database.areEqual(l, r); result = ValueBoolean.get(database.areEqual(l, r));
break; break;
case NOT_EQUAL: case NOT_EQUAL: {
int cmp = database.compareWithNull(l, r, true);
if (cmp == 0) {
result = ValueBoolean.FALSE;
} else if (cmp == Integer.MIN_VALUE) {
result = ValueNull.INSTANCE;
} else {
result = ValueBoolean.TRUE;
}
break;
}
case NOT_EQUAL_NULL_SAFE: case NOT_EQUAL_NULL_SAFE:
result = !database.areEqual(l, r); result = ValueBoolean.get(!database.areEqual(l, r));
break; break;
case BIGGER_EQUAL: case BIGGER_EQUAL: {
result = database.compare(l, r) >= 0; int cmp = database.compareWithNull(l, r, false);
if (cmp >= 0) {
result = ValueBoolean.TRUE;
} else if (cmp == Integer.MIN_VALUE) {
result = ValueNull.INSTANCE;
} else {
result = ValueBoolean.FALSE;
}
break; break;
case BIGGER: }
result = database.compare(l, r) > 0; case BIGGER: {
int cmp = database.compareWithNull(l, r, false);
if (cmp > 0) {
result = ValueBoolean.TRUE;
} else if (cmp == Integer.MIN_VALUE) {
result = ValueNull.INSTANCE;
} else {
result = ValueBoolean.FALSE;
}
break; break;
case SMALLER_EQUAL: }
result = database.compare(l, r) <= 0; case SMALLER_EQUAL: {
int cmp = database.compareWithNull(l, r, false);
if (cmp == Integer.MIN_VALUE) {
result = ValueNull.INSTANCE;
} else {
result = ValueBoolean.get(cmp <= 0);
}
break; break;
case SMALLER: }
result = database.compare(l, r) < 0; case SMALLER: {
int cmp = database.compareWithNull(l, r, false);
if (cmp == Integer.MIN_VALUE) {
result = ValueNull.INSTANCE;
} else {
result = ValueBoolean.get(cmp < 0);
}
break; break;
}
case SPATIAL_INTERSECTS: { case SPATIAL_INTERSECTS: {
ValueGeometry lg = (ValueGeometry) l.convertTo(Value.GEOMETRY); if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) {
ValueGeometry rg = (ValueGeometry) r.convertTo(Value.GEOMETRY); result = ValueNull.INSTANCE;
result = lg.intersectsBoundingBox(rg); } else {
ValueGeometry lg = (ValueGeometry) l.convertTo(Value.GEOMETRY);
ValueGeometry rg = (ValueGeometry) r.convertTo(Value.GEOMETRY);
result = ValueBoolean.get(lg.intersectsBoundingBox(rg));
}
break; break;
} }
default: default:
......
...@@ -49,8 +49,8 @@ public class ConditionIn extends Condition { ...@@ -49,8 +49,8 @@ public class ConditionIn extends Condition {
@Override @Override
public Value getValue(Session session) { public Value getValue(Session session) {
Value l = left.getValue(session); Value l = left.getValue(session);
if (l == ValueNull.INSTANCE) { if (l.containsNull()) {
return l; return ValueNull.INSTANCE;
} }
int size = valueList.size(); int size = valueList.size();
if (size == 1) { if (size == 1) {
...@@ -59,24 +59,21 @@ public class ConditionIn extends Condition { ...@@ -59,24 +59,21 @@ public class ConditionIn extends Condition {
return ConditionInParameter.getValue(database, l, e.getValue(session)); return ConditionInParameter.getValue(database, l, e.getValue(session));
} }
} }
boolean result = false;
boolean hasNull = false; boolean hasNull = false;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
Expression e = valueList.get(i); Expression e = valueList.get(i);
Value r = e.getValue(session); Value r = e.getValue(session);
if (r == ValueNull.INSTANCE) { Value cmp = Comparison.compare(database, l, r, Comparison.EQUAL);
if (cmp == ValueNull.INSTANCE) {
hasNull = true; hasNull = true;
} else { } else if (cmp == ValueBoolean.TRUE) {
result = Comparison.compareNotNull(database, l, r, Comparison.EQUAL); return cmp;
if (result) {
break;
}
} }
} }
if (!result && hasNull) { if (hasNull) {
return ValueNull.INSTANCE; return ValueNull.INSTANCE;
} }
return ValueBoolean.get(result); return ValueBoolean.FALSE;
} }
@Override @Override
...@@ -114,7 +111,7 @@ public class ConditionIn extends Condition { ...@@ -114,7 +111,7 @@ public class ConditionIn extends Condition {
ArrayList<Expression> list = new ArrayList<>(ri.getRowCount()); ArrayList<Expression> list = new ArrayList<>(ri.getRowCount());
while (ri.next()) { while (ri.next()) {
Value v = ri.currentRow()[0]; Value v = ri.currentRow()[0];
if (v != ValueNull.INSTANCE) { if (!v.containsNull()) {
allValuesNull = false; allValuesNull = false;
} }
list.add(ValueExpression.get(v)); list.add(ValueExpression.get(v));
...@@ -129,7 +126,7 @@ public class ConditionIn extends Condition { ...@@ -129,7 +126,7 @@ public class ConditionIn extends Condition {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
Expression e = valueList.get(i); Expression e = valueList.get(i);
e = e.optimize(session); e = e.optimize(session);
if (e.isConstant() && e.getValue(session) != ValueNull.INSTANCE) { if (e.isConstant() && !e.getValue(session).containsNull()) {
allValuesNull = false; allValuesNull = false;
} }
if (allValuesConstant && !e.isConstant()) { if (allValuesConstant && !e.isConstant()) {
......
...@@ -35,6 +35,7 @@ public class ConditionInConstantSet extends Condition { ...@@ -35,6 +35,7 @@ public class ConditionInConstantSet extends Condition {
private Expression left; private Expression left;
private final ArrayList<Expression> valueList; private final ArrayList<Expression> valueList;
private final TreeSet<Value> valueSet; private final TreeSet<Value> valueSet;
private boolean hasNull;
private final int type; private final int type;
private ExtTypeInfo extTypeInfo; private ExtTypeInfo extTypeInfo;
...@@ -58,27 +59,32 @@ public class ConditionInConstantSet extends Condition { ...@@ -58,27 +59,32 @@ public class ConditionInConstantSet extends Condition {
if (type == Value.ENUM) { if (type == Value.ENUM) {
extTypeInfo = ((ExpressionColumn) left).getColumn().getExtTypeInfo(); extTypeInfo = ((ExpressionColumn) left).getColumn().getExtTypeInfo();
for (Expression expression : valueList) { for (Expression expression : valueList) {
valueSet.add(extTypeInfo.cast(expression.getValue(session))); add(extTypeInfo.cast(expression.getValue(session)));
} }
} else { } else {
for (Expression expression : valueList) { for (Expression expression : valueList) {
valueSet.add(expression.getValue(session).convertTo(type, mode)); add(expression.getValue(session).convertTo(type, mode));
} }
} }
} }
private void add(Value v) {
if (v.containsNull()) {
hasNull = true;
} else {
valueSet.add(v);
}
}
@Override @Override
public Value getValue(Session session) { public Value getValue(Session session) {
Value x = left.getValue(session); Value x = left.getValue(session);
if (x == ValueNull.INSTANCE) { if (x.containsNull()) {
return x; return x;
} }
boolean result = valueSet.contains(x); boolean result = valueSet.contains(x);
if (!result) { if (!result && hasNull) {
boolean setHasNull = valueSet.contains(ValueNull.INSTANCE); return ValueNull.INSTANCE;
if (setHasNull) {
return ValueNull.INSTANCE;
}
} }
return ValueBoolean.get(result); return ValueBoolean.get(result);
} }
...@@ -168,9 +174,9 @@ public class ConditionInConstantSet extends Condition { ...@@ -168,9 +174,9 @@ public class ConditionInConstantSet extends Condition {
if (add.isConstant()) { if (add.isConstant()) {
valueList.add(add); valueList.add(add);
if (type == Value.ENUM) { if (type == Value.ENUM) {
valueSet.add(add.getValue(session).convertToEnum(extTypeInfo)); add(add.getValue(session).convertToEnum(extTypeInfo));
} else { } else {
valueSet.add(add.getValue(session).convertTo(type, session.getDatabase().getMode())); add(add.getValue(session).convertTo(type, session.getDatabase().getMode()));
} }
return this; return this;
} }
......
...@@ -66,38 +66,33 @@ public class ConditionInParameter extends Condition { ...@@ -66,38 +66,33 @@ public class ConditionInParameter extends Condition {
private final Parameter parameter; private final Parameter parameter;
static Value getValue(Database database, Value l, Value value) { static Value getValue(Database database, Value l, Value value) {
boolean result = false;
boolean hasNull = false; boolean hasNull = false;
if (value == ValueNull.INSTANCE) { if (value.containsNull()) {
hasNull = true; hasNull = true;
} else if (value.getType() == Value.RESULT_SET) { } else if (value.getType() == Value.RESULT_SET) {
for (ResultInterface ri = value.getResult(); ri.next();) { for (ResultInterface ri = value.getResult(); ri.next();) {
Value r = ri.currentRow()[0]; Value r = ri.currentRow()[0];
if (r == ValueNull.INSTANCE) { Value cmp = Comparison.compare(database, l, r, Comparison.EQUAL);
if (cmp == ValueNull.INSTANCE) {
hasNull = true; hasNull = true;
} else { } else if (cmp == ValueBoolean.TRUE) {
result = Comparison.compareNotNull(database, l, r, Comparison.EQUAL); return cmp;
if (result) {
break;
}
} }
} }
} else { } else {
for (Value r : ((ValueArray) value.convertTo(Value.ARRAY)).getList()) { for (Value r : ((ValueArray) value.convertTo(Value.ARRAY)).getList()) {
if (r == ValueNull.INSTANCE) { Value cmp = Comparison.compare(database, l, r, Comparison.EQUAL);
if (cmp == ValueNull.INSTANCE) {
hasNull = true; hasNull = true;
} else { } else if (cmp == ValueBoolean.TRUE) {
result = Comparison.compareNotNull(database, l, r, Comparison.EQUAL); return cmp;
if (result) {
break;
}
} }
} }
} }
if (!result && hasNull) { if (hasNull) {
return ValueNull.INSTANCE; return ValueNull.INSTANCE;
} }
return ValueBoolean.get(result); return ValueBoolean.FALSE;
} }
/** /**
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
*/ */
package org.h2.expression.condition; package org.h2.expression.condition;
import org.h2.api.ErrorCode;
import org.h2.command.dml.Query; import org.h2.command.dml.Query;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.Session; import org.h2.engine.Session;
...@@ -12,15 +13,16 @@ import org.h2.expression.Expression; ...@@ -12,15 +13,16 @@ import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn; import org.h2.expression.ExpressionColumn;
import org.h2.expression.ExpressionVisitor; import org.h2.expression.ExpressionVisitor;
import org.h2.index.IndexCondition; import org.h2.index.IndexCondition;
import org.h2.message.DbException;
import org.h2.result.LocalResult; import org.h2.result.LocalResult;
import org.h2.result.ResultInterface; 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.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueBoolean; import org.h2.value.ValueBoolean;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
import org.h2.value.ValueRow;
/** /**
* An 'in' condition with a subquery, as in WHERE ID IN(SELECT ...) * An 'in' condition with a subquery, as in WHERE ID IN(SELECT ...)
...@@ -52,8 +54,8 @@ public class ConditionInSelect extends Condition { ...@@ -52,8 +54,8 @@ public class ConditionInSelect extends Condition {
Value l = left.getValue(session); Value l = left.getValue(session);
if (!rows.hasNext()) { if (!rows.hasNext()) {
return ValueBoolean.get(all); return ValueBoolean.get(all);
} else if (l == ValueNull.INSTANCE) { } else if (l.containsNull()) {
return l; return ValueNull.INSTANCE;
} }
if (!database.getSettings().optimizeInSelect) { if (!database.getSettings().optimizeInSelect) {
return getValueSlow(rows, l); return getValueSlow(rows, l);
...@@ -64,8 +66,8 @@ public class ConditionInSelect extends Condition { ...@@ -64,8 +66,8 @@ public class ConditionInSelect extends Condition {
} }
int columnCount = query.getColumnCount(); int columnCount = query.getColumnCount();
if (columnCount != 1) { if (columnCount != 1) {
l = l.convertTo(Value.ARRAY); l = l.convertTo(Value.ROW);
Value[] leftValue = ((ValueArray) l).getList(); Value[] leftValue = ((ValueRow) l).getList();
if (columnCount == leftValue.length && rows.containsDistinct(leftValue)) { if (columnCount == leftValue.length && rows.containsDistinct(leftValue)) {
return ValueBoolean.TRUE; return ValueBoolean.TRUE;
} }
...@@ -74,13 +76,20 @@ public class ConditionInSelect extends Condition { ...@@ -74,13 +76,20 @@ public class ConditionInSelect extends Condition {
if (dataType == Value.NULL) { if (dataType == Value.NULL) {
return ValueBoolean.FALSE; return ValueBoolean.FALSE;
} }
if (l.getType() == Value.ROW) {
Value[] leftList = ((ValueRow) l).getList();
if (leftList.length != 1) {
throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
}
l = leftList[0];
}
l = l.convertTo(dataType, database.getMode()); l = l.convertTo(dataType, database.getMode());
if (rows.containsDistinct(new Value[] { l })) { if (rows.containsDistinct(new Value[] { l })) {
return ValueBoolean.TRUE; return ValueBoolean.TRUE;
} }
if (rows.containsDistinct(new Value[] { ValueNull.INSTANCE })) { }
return ValueNull.INSTANCE; if (rows.containsNull()) {
} return ValueNull.INSTANCE;
} }
return ValueBoolean.FALSE; return ValueBoolean.FALSE;
} }
...@@ -89,29 +98,35 @@ public class ConditionInSelect extends Condition { ...@@ -89,29 +98,35 @@ public class ConditionInSelect extends Condition {
// this only returns the correct result if the result has at least one // this only returns the correct result if the result has at least one
// row, and if l is not null // row, and if l is not null
boolean hasNull = false; boolean hasNull = false;
boolean result = all; if (all) {
while (rows.next()) { while (rows.next()) {
boolean value; Value cmp = compare(l, rows);
Value[] currentRow = rows.currentRow(); if (cmp == ValueNull.INSTANCE) {
Value r = query.getColumnCount() == 1 ? currentRow[0] : org.h2.value.ValueArray.get(currentRow); hasNull = true;
if (r == ValueNull.INSTANCE) { } else if (cmp == ValueBoolean.FALSE) {
value = false; return cmp;
hasNull = true; }
} else {
value = Comparison.compareNotNull(database, l, r, compareType);
} }
if (!value && all) { } else {
result = false; while (rows.next()) {
break; Value cmp = compare(l, rows);
} else if (value && !all) { if (cmp == ValueNull.INSTANCE) {
result = true; hasNull = true;
break; } else if (cmp == ValueBoolean.TRUE) {
return cmp;
}
} }
} }
if (!result && hasNull) { if (hasNull) {
return ValueNull.INSTANCE; return ValueNull.INSTANCE;
} }
return ValueBoolean.get(result); return ValueBoolean.get(all);
}
private Value compare(Value l, ResultInterface rows) {
Value[] currentRow = rows.currentRow();
Value r = l.getType() != Value.ROW && query.getColumnCount() == 1 ? currentRow[0] : ValueRow.get(currentRow);
return Comparison.compare(database, l, r, compareType);
} }
@Override @Override
......
...@@ -49,6 +49,14 @@ public interface LocalResult extends ResultInterface, ResultTarget { ...@@ -49,6 +49,14 @@ public interface LocalResult extends ResultInterface, ResultTarget {
*/ */
boolean containsDistinct(Value[] values); boolean containsDistinct(Value[] values);
/**
* Check if this result set contains a NULL value. This method may reset
* this result.
*
* @return true if there is a NULL value
*/
boolean containsNull();
/** /**
* Remove the row from the result set if it exists. * Remove the row from the result set if it exists.
* *
......
...@@ -45,6 +45,7 @@ public class LocalResultImpl implements LocalResult { ...@@ -45,6 +45,7 @@ public class LocalResultImpl implements LocalResult {
private int[] distinctIndexes; private int[] distinctIndexes;
private boolean closed; private boolean closed;
private boolean containsLobs; private boolean containsLobs;
private Boolean containsNull;
/** /**
* Construct a local result object. * Construct a local result object.
...@@ -127,6 +128,7 @@ public class LocalResultImpl implements LocalResult { ...@@ -127,6 +128,7 @@ public class LocalResultImpl implements LocalResult {
copy.offset = 0; copy.offset = 0;
copy.limit = -1; copy.limit = -1;
copy.external = e2; copy.external = e2;
copy.containsNull = containsNull;
return copy; return copy;
} }
...@@ -212,6 +214,27 @@ public class LocalResultImpl implements LocalResult { ...@@ -212,6 +214,27 @@ public class LocalResultImpl implements LocalResult {
return distinctRows.get(array) != null; return distinctRows.get(array) != null;
} }
@Override
public boolean containsNull() {
Boolean r = containsNull;
if (r == null) {
r = false;
reset();
loop: while (next()) {
Value[] row = currentRow;
for (int i = 0; i < visibleColumnCount; i++) {
if (row[i].containsNull()) {
r = true;
break loop;
}
}
}
reset();
containsNull = r;
}
return r;
}
@Override @Override
public void reset() { public void reset() {
rowId = -1; rowId = -1;
......
...@@ -1347,7 +1347,7 @@ public abstract class Value { ...@@ -1347,7 +1347,7 @@ public abstract class Value {
return ValueResultSet.get(result); return ValueResultSet.get(result);
} }
private DbException getDataConversionError(int targetType) { DbException getDataConversionError(int targetType) {
DataType from = DataType.getDataType(getType()); DataType from = DataType.getDataType(getType());
DataType to = DataType.getDataType(targetType); DataType to = DataType.getDataType(targetType);
throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, (from != null ? from.name : "type=" + getType()) throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, (from != null ? from.name : "type=" + getType())
...@@ -1372,7 +1372,7 @@ public abstract class Value { ...@@ -1372,7 +1372,7 @@ public abstract class Value {
* @param v the other value * @param v the other value
* @param databaseMode the database mode * @param databaseMode the database mode
* @param compareMode the compare mode * @param compareMode the compare mode
* @return 0 if both values are equal, -1 if the other value is smaller, and * @return 0 if both values are equal, -1 if this value is smaller, and
* 1 otherwise * 1 otherwise
*/ */
public final int compareTo(Value v, Mode databaseMode, CompareMode compareMode) { public final int compareTo(Value v, Mode databaseMode, CompareMode compareMode) {
...@@ -1401,6 +1401,48 @@ public abstract class Value { ...@@ -1401,6 +1401,48 @@ public abstract class Value {
return l.compareTypeSafe(v, compareMode); return l.compareTypeSafe(v, compareMode);
} }
/**
* Compare this value against another value using the specified compare
* mode.
*
* @param v the other value
* @param forEquality perform only check for equality
* @param databaseMode the database mode
* @param compareMode the compare mode
* @return 0 if both values are equal, -1 if this value is smaller, 1
* if other value is larger, {@link Integer#MIN_VALUE} if order is
* not defined due to NULL comparison
*/
public int compareWithNull(Value v, boolean forEquality, Mode databaseMode, CompareMode compareMode) {
if (this == ValueNull.INSTANCE || v == ValueNull.INSTANCE) {
return Integer.MIN_VALUE;
}
Value l = this;
int leftType = l.getType();
int rightType = v.getType();
if (leftType != rightType || leftType == Value.ENUM) {
int dataType = Value.getHigherOrder(leftType, rightType);
if (dataType == Value.ENUM) {
ExtTypeInfoEnum enumerators = ExtTypeInfoEnum.getEnumeratorsForBinaryOperation(l, v);
l = l.convertToEnum(enumerators);
v = v.convertToEnum(enumerators);
} else {
l = l.convertTo(dataType, databaseMode);
v = v.convertTo(dataType, databaseMode);
}
}
return l.compareTypeSafe(v, compareMode);
}
/**
* Returns true if this value is NULL or contains NULL value.
*
* @return true if this value is NULL or contains NULL value
*/
public boolean containsNull() {
return false;
}
public int getScale() { public int getScale() {
return 0; return 0;
} }
......
...@@ -10,14 +10,12 @@ import java.sql.PreparedStatement; ...@@ -10,14 +10,12 @@ import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays; import java.util.Arrays;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.util.MathUtils;
/** /**
* Implementation of the ARRAY data type. * Implementation of the ARRAY data type.
*/ */
public class ValueArray extends Value { public class ValueArray extends ValueCollectionBase {
/** /**
* Empty array. * Empty array.
...@@ -25,12 +23,10 @@ public class ValueArray extends Value { ...@@ -25,12 +23,10 @@ public class ValueArray extends Value {
private static final Object EMPTY = get(new Value[0]); private static final Object EMPTY = get(new Value[0]);
private final Class<?> componentType; private final Class<?> componentType;
private final Value[] values;
private int hash;
private ValueArray(Class<?> componentType, Value[] list) { private ValueArray(Class<?> componentType, Value[] list) {
super(list);
this.componentType = componentType; this.componentType = componentType;
this.values = list;
} }
/** /**
...@@ -65,19 +61,6 @@ public class ValueArray extends Value { ...@@ -65,19 +61,6 @@ public class ValueArray extends Value {
return (ValueArray) EMPTY; return (ValueArray) 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() { public Value[] getList() {
return values; return values;
} }
...@@ -91,15 +74,6 @@ public class ValueArray extends Value { ...@@ -91,15 +74,6 @@ public class ValueArray extends Value {
return componentType; return componentType;
} }
@Override
public long getPrecision() {
long p = 0;
for (Value v : values) {
p += v.getPrecision();
}
return p;
}
@Override @Override
public String getString() { public String getString() {
StringBuilder builder = new StringBuilder().append('['); StringBuilder builder = new StringBuilder().append('[');
...@@ -181,15 +155,6 @@ public class ValueArray extends Value { ...@@ -181,15 +155,6 @@ public class ValueArray extends Value {
return builder.append(']').toString(); return builder.append(']').toString();
} }
@Override
public int getDisplaySize() {
long size = 0;
for (Value v : values) {
size += v.getDisplaySize();
}
return MathUtils.convertLongToInt(size);
}
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (!(other instanceof ValueArray)) { if (!(other instanceof ValueArray)) {
...@@ -211,15 +176,6 @@ public class ValueArray extends Value { ...@@ -211,15 +176,6 @@ public class ValueArray extends Value {
return true; return true;
} }
@Override
public int getMemory() {
int memory = 32;
for (Value v : values) {
memory += v.getMemory() + Constants.MEMORY_POINTER;
}
return memory;
}
@Override @Override
public Value convertPrecision(long precision, boolean force) { public Value convertPrecision(long precision, boolean force) {
if (!force) { if (!force) {
......
/*
* 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 org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.message.DbException;
import org.h2.util.MathUtils;
/**
* Base class for ARRAY and ROW values.
*/
abstract class ValueCollectionBase extends Value {
final Value[] values;
private int hash;
ValueCollectionBase(Value[] values) {
this.values = values;
}
@Override
public int hashCode() {
if (hash != 0) {
return hash;
}
int h = getType();
for (Value v : values) {
h = h * 31 + v.hashCode();
}
hash = h;
return h;
}
@Override
public long getPrecision() {
long p = 0;
for (Value v : values) {
p += v.getPrecision();
}
return p;
}
@Override
public int getDisplaySize() {
long size = 0;
for (Value v : values) {
size += v.getDisplaySize();
}
return MathUtils.convertLongToInt(size);
}
@Override
public int compareWithNull(Value v, boolean forEquality, Mode databaseMode, CompareMode compareMode) {
if (v == ValueNull.INSTANCE) {
return Integer.MIN_VALUE;
}
ValueCollectionBase l = this;
int leftType = l.getType();
int rightType = v.getType();
if (rightType != ARRAY && rightType != ROW) {
throw v.getDataConversionError(leftType);
}
ValueCollectionBase r = (ValueCollectionBase) v;
Value[] leftArray = l.values, rightArray = r.values;
int leftLength = leftArray.length, rightLength = rightArray.length;
if (leftLength != rightLength) {
if (leftType == ROW || rightType == ROW) {
throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
}
if (forEquality) {
return 1;
}
}
if (forEquality) {
boolean hasNull = false;
for (int i = 0; i < leftLength; i++) {
Value v1 = leftArray[i];
Value v2 = rightArray[i];
int comp = v1.compareWithNull(v2, forEquality, databaseMode, compareMode);
if (comp != 0) {
if (comp != Integer.MIN_VALUE) {
return comp;
}
hasNull = true;
}
}
return hasNull ? Integer.MIN_VALUE : 0;
}
int len = Math.min(leftLength, rightLength);
for (int i = 0; i < len; i++) {
Value v1 = leftArray[i];
Value v2 = rightArray[i];
int comp = v1.compareWithNull(v2, forEquality, databaseMode, compareMode);
if (comp != 0) {
return comp;
}
}
return Integer.compare(leftLength, rightLength);
}
@Override
public boolean containsNull() {
for (Value v : values) {
if (v.containsNull()) {
return true;
}
}
return false;
}
@Override
public int getMemory() {
int memory = 32;
for (Value v : values) {
memory += v.getMemory() + Constants.MEMORY_POINTER;
}
return memory;
}
}
...@@ -143,6 +143,11 @@ public class ValueNull extends Value { ...@@ -143,6 +143,11 @@ public class ValueNull extends Value {
throw DbException.throwInternalError("compare null"); throw DbException.throwInternalError("compare null");
} }
@Override
public boolean containsNull() {
return true;
}
@Override @Override
public long getPrecision() { public long getPrecision() {
return PRECISION; return PRECISION;
......
...@@ -10,26 +10,21 @@ import java.sql.SQLException; ...@@ -10,26 +10,21 @@ import java.sql.SQLException;
import java.util.Arrays; import java.util.Arrays;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.util.MathUtils;
/** /**
* Row value. * Row value.
*/ */
public class ValueRow extends Value { public class ValueRow extends ValueCollectionBase {
/** /**
* Empty row. * Empty row.
*/ */
private static final Object EMPTY = get(new Value[0]); private static final Object EMPTY = get(new Value[0]);
private final Value[] values;
private int hash;
private ValueRow(Value[] list) { private ValueRow(Value[] list) {
this.values = list; super(list);
} }
/** /**
...@@ -52,19 +47,6 @@ public class ValueRow extends Value { ...@@ -52,19 +47,6 @@ public class ValueRow extends Value {
return (ValueRow) EMPTY; 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() { public Value[] getList() {
return values; return values;
} }
...@@ -74,15 +56,6 @@ public class ValueRow extends Value { ...@@ -74,15 +56,6 @@ public class ValueRow extends Value {
return Value.ROW; return Value.ROW;
} }
@Override
public long getPrecision() {
long p = 0;
for (Value v : values) {
p += v.getPrecision();
}
return p;
}
@Override @Override
public String getString() { public String getString() {
StringBuilder builder = new StringBuilder("ROW ("); StringBuilder builder = new StringBuilder("ROW (");
...@@ -165,15 +138,6 @@ public class ValueRow extends Value { ...@@ -165,15 +138,6 @@ public class ValueRow extends Value {
return builder.append(')').toString(); return builder.append(')').toString();
} }
@Override
public int getDisplaySize() {
long size = 0;
for (Value v : values) {
size += v.getDisplaySize();
}
return MathUtils.convertLongToInt(size);
}
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (!(other instanceof ValueRow)) { if (!(other instanceof ValueRow)) {
...@@ -195,15 +159,6 @@ public class ValueRow extends Value { ...@@ -195,15 +159,6 @@ public class ValueRow extends Value {
return true; return true;
} }
@Override
public int getMemory() {
int memory = 32;
for (Value v : values) {
memory += v.getMemory() + Constants.MEMORY_POINTER;
}
return memory;
}
@Override @Override
public Value convertPrecision(long precision, boolean force) { public Value convertPrecision(long precision, boolean force) {
if (!force) { if (!force) {
......
...@@ -74,6 +74,7 @@ import org.h2.test.db.TestSetCollation; ...@@ -74,6 +74,7 @@ import org.h2.test.db.TestSetCollation;
import org.h2.test.db.TestSpaceReuse; import org.h2.test.db.TestSpaceReuse;
import org.h2.test.db.TestSpatial; import org.h2.test.db.TestSpatial;
import org.h2.test.db.TestSpeed; import org.h2.test.db.TestSpeed;
import org.h2.test.db.TestSubqueryPerformanceOnLazyExecutionMode;
import org.h2.test.db.TestSynonymForTable; import org.h2.test.db.TestSynonymForTable;
import org.h2.test.db.TestTableEngines; import org.h2.test.db.TestTableEngines;
import org.h2.test.db.TestTempTables; import org.h2.test.db.TestTempTables;
...@@ -223,7 +224,6 @@ import org.h2.test.unit.TestSort; ...@@ -223,7 +224,6 @@ import org.h2.test.unit.TestSort;
import org.h2.test.unit.TestStreams; import org.h2.test.unit.TestStreams;
import org.h2.test.unit.TestStringCache; import org.h2.test.unit.TestStringCache;
import org.h2.test.unit.TestStringUtils; import org.h2.test.unit.TestStringUtils;
import org.h2.test.unit.TestSubqueryPerformanceOnLazyExecutionMode;
import org.h2.test.unit.TestTimeStampWithTimeZone; import org.h2.test.unit.TestTimeStampWithTimeZone;
import org.h2.test.unit.TestTools; import org.h2.test.unit.TestTools;
import org.h2.test.unit.TestTraceSystem; import org.h2.test.unit.TestTraceSystem;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* and the EPL 1.0 (http://h2database.com/html/license.html). * and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group * Initial Developer: H2 Group
*/ */
package org.h2.test.unit; package org.h2.test.db;
import org.h2.command.dml.SetTypes; import org.h2.command.dml.SetTypes;
import org.h2.test.TestBase; import org.h2.test.TestBase;
...@@ -33,6 +33,11 @@ public class TestSubqueryPerformanceOnLazyExecutionMode extends TestDb { ...@@ -33,6 +33,11 @@ public class TestSubqueryPerformanceOnLazyExecutionMode extends TestDb {
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
@Override
public boolean isEnabled() {
return !config.travis;
}
@Override @Override
public void test() throws Exception { public void test() throws Exception {
deleteDb("lazySubq"); deleteDb("lazySubq");
......
...@@ -23,3 +23,66 @@ SELECT ARRAY[10]; ...@@ -23,3 +23,66 @@ SELECT ARRAY[10];
SELECT ARRAY[10, 20, 30]; SELECT ARRAY[10, 20, 30];
>> [10, 20, 30] >> [10, 20, 30]
SELECT ARRAY[1, NULL] IS NOT DISTINCT FROM ARRAY[1, NULL];
>> TRUE
SELECT ARRAY[1, NULL] IS DISTINCT FROM ARRAY[1, NULL];
>> FALSE
SELECT ARRAY[1, NULL] = ARRAY[1, NULL];
>> null
SELECT ARRAY[1, NULL] <> ARRAY[1, NULL];
>> null
SELECT ARRAY[NULL] = ARRAY[NULL, NULL];
>> FALSE
select ARRAY[1, NULL, 2] = ARRAY[1, NULL, 1];
>> FALSE
select ARRAY[1, NULL, 2] <> ARRAY[1, NULL, 1];
>> TRUE
SELECT ARRAY[1, NULL] > ARRAY[1, NULL];
>> null
SELECT ARRAY[1, 2] > ARRAY[1, NULL];
>> null
SELECT ARRAY[1, 2, NULL] > ARRAY[1, 1, NULL];
>> TRUE
SELECT ARRAY[1, 1, NULL] > ARRAY[1, 2, NULL];
>> FALSE
SELECT ARRAY[1, 2, NULL] < ARRAY[1, 1, NULL];
>> FALSE
SELECT ARRAY[1, 1, NULL] <= ARRAY[1, 1, NULL];
>> null
SELECT ARRAY[1, NULL] IN (ARRAY[1, NULL]);
>> null
CREATE TABLE TEST(A ARRAY);
> ok
INSERT INTO TEST VALUES (ARRAY[1, NULL]), (ARRAY[1, 2]);
> update count: 2
SELECT ARRAY[1, 2] IN (SELECT A FROM TEST);
>> TRUE
SELECT ROW (ARRAY[1, 2]) IN (SELECT A FROM TEST);
>> TRUE
SELECT ARRAY[1, NULL] IN (SELECT A FROM TEST);
>> null
SELECT ROW (ARRAY[1, NULL]) IN (SELECT A FROM TEST);
>> null
DROP TABLE TEST;
> ok
...@@ -8,3 +8,66 @@ SELECT ROW (10); ...@@ -8,3 +8,66 @@ SELECT ROW (10);
SELECT (10, 20, 30); SELECT (10, 20, 30);
>> ROW (10, 20, 30) >> ROW (10, 20, 30)
SELECT (1, NULL) IS NOT DISTINCT FROM (1, NULL);
>> TRUE
SELECT (1, NULL) IS DISTINCT FROM ROW (1, NULL);
>> FALSE
SELECT (1, NULL) = (1, NULL);
>> null
SELECT (1, NULL) <> (1, NULL);
>> null
SELECT ROW (NULL) = (NULL, NULL);
> exception COLUMN_COUNT_DOES_NOT_MATCH
select (1, NULL, 2) = (1, NULL, 1);
>> FALSE
select (1, NULL, 2) <> (1, NULL, 1);
>> TRUE
SELECT (1, NULL) > (1, NULL);
>> null
SELECT (1, 2) > (1, NULL);
>> null
SELECT (1, 2, NULL) > (1, 1, NULL);
>> TRUE
SELECT (1, 1, NULL) > (1, 2, NULL);
>> FALSE
SELECT (1, 2, NULL) < (1, 1, NULL);
>> FALSE
SELECT (1, 1, NULL) <= (1, 1, NULL);
>> null
SELECT (1, 2) IN (SELECT 1, 2);
>> TRUE
SELECT (1, 2) IN (SELECT * FROM VALUES (1, 2), (1, NULL));
>> TRUE
SELECT (1, 2) IN (SELECT * FROM VALUES (1, 1), (1, NULL));
>> null
SELECT (1, 2) IN (SELECT * FROM VALUES (1, 1), (1, 3));
>> FALSE
SELECT (1, NULL) IN (SELECT 1, NULL);
>> null
SELECT (1, ARRAY[1]) IN (SELECT 1, ARRAY[1]);
>> TRUE
SELECT (1, ARRAY[1]) IN (SELECT 1, ARRAY[2]);
>> FALSE
SELECT (1, ARRAY[NULL]) IN (SELECT 1, ARRAY[NULL]);
>> null
...@@ -244,11 +244,11 @@ CREATE TABLE TEST(A INT, B INT); ...@@ -244,11 +244,11 @@ CREATE TABLE TEST(A INT, B INT);
INSERT INTO TEST VALUES (1, 1), (1, 2), (2, 1), (2, 2), (2, 3); INSERT INTO TEST VALUES (1, 1), (1, 2), (2, 1), (2, 2), (2, 3);
> update count: 5 > update count: 5
SELECT A, COUNT(B) FROM TEST GROUP BY A OFFSET 1; SELECT A, COUNT(B) FROM TEST GROUP BY A ORDER BY A OFFSET 1;
> A COUNT(B) > A COUNT(B)
> - -------- > - --------
> 2 3 > 2 3
> rows: 1 > rows (ordered): 1
DROP TABLE TEST; DROP TABLE TEST;
> ok > ok
...@@ -463,3 +463,6 @@ SELECT (3, 4) > ALL (SELECT ID, V FROM TEST); ...@@ -463,3 +463,6 @@ SELECT (3, 4) > ALL (SELECT ID, V FROM TEST);
DROP TABLE TEST; DROP TABLE TEST;
> ok > ok
SELECT 1 = ALL (SELECT * FROM VALUES (NULL), (1), (2), (NULL) ORDER BY 1);
>> FALSE
...@@ -386,15 +386,17 @@ public class TestNestedJoins extends TestDb { ...@@ -386,15 +386,17 @@ public class TestNestedJoins extends TestDb {
rs = stat.executeQuery("select distinct t1.a, t2.a, t3.a from t1 " + rs = stat.executeQuery("select distinct t1.a, t2.a, t3.a from t1 " +
"right outer join t3 on t1.b=t3.a " + "right outer join t3 on t1.b=t3.a " +
"right outer join t2 on t2.b=t1.a"); "right outer join t2 on t2.b=t1.a");
// expected: 1 1 1; null 2 null // expected:
assertTrue(rs.next()); // null 2 null
assertEquals("1", rs.getString(1)); // 1 1 1
assertEquals("1", rs.getString(2));
assertEquals("1", rs.getString(3));
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(null, rs.getString(1)); assertEquals(null, rs.getString(1));
assertEquals("2", rs.getString(2)); assertEquals("2", rs.getString(2));
assertEquals(null, rs.getString(3)); assertEquals(null, rs.getString(3));
assertTrue(rs.next());
assertEquals("1", rs.getString(1));
assertEquals("1", rs.getString(2));
assertEquals("1", rs.getString(3));
assertFalse(rs.next()); assertFalse(rs.next());
stat.execute("drop table t1, t2, t3, t4"); stat.execute("drop table t1, t2, t3, t4");
......
...@@ -339,15 +339,17 @@ public class TestOuterJoins extends TestDb { ...@@ -339,15 +339,17 @@ public class TestOuterJoins extends TestDb {
"ON T2.B = T1.A", sql); "ON T2.B = T1.A", sql);
rs = stat.executeQuery("select distinct t1.a, t2.a, t3.a from t1 " + rs = stat.executeQuery("select distinct t1.a, t2.a, t3.a from t1 " +
"right outer join t3 on t1.b=t3.a right outer join t2 on t2.b=t1.a"); "right outer join t3 on t1.b=t3.a right outer join t2 on t2.b=t1.a");
// expected: 1 1 1; null 2 null // expected:
assertTrue(rs.next()); // null 2 null
assertEquals("1", rs.getString(1)); // 1 1 1
assertEquals("1", rs.getString(2));
assertEquals("1", rs.getString(3));
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(null, rs.getString(1)); assertEquals(null, rs.getString(1));
assertEquals("2", rs.getString(2)); assertEquals("2", rs.getString(2));
assertEquals(null, rs.getString(3)); assertEquals(null, rs.getString(3));
assertTrue(rs.next());
assertEquals("1", rs.getString(1));
assertEquals("1", rs.getString(2));
assertEquals("1", rs.getString(3));
assertFalse(rs.next()); assertFalse(rs.next());
stat.execute("drop table t1, t2, t3, t4"); stat.execute("drop table t1, t2, t3, t4");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论