提交 0f38abf2 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Handle null elements in array and row values in comparison operations properly

上级 d5d927d2
......@@ -412,8 +412,8 @@ public class Database implements DataHandler {
}
/**
* Compare two values with the current comparison mode. The values may not
* be of the same type.
* Compare two values with the current comparison mode. The values may have
* different data types including NULL.
*
* @param a the first value
* @param b the second value
......@@ -424,6 +424,24 @@ public class Database implements DataHandler {
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
* of the same type.
......
......@@ -283,22 +283,15 @@ public class Comparison extends Condition {
}
return ValueBoolean.get(result);
}
if (l == ValueNull.INSTANCE) {
if ((compareType & NULL_SAFE) == 0) {
return ValueNull.INSTANCE;
}
}
Value r = right.getValue(session);
if (r == ValueNull.INSTANCE) {
if ((compareType & NULL_SAFE) == 0) {
return ValueNull.INSTANCE;
}
// Optimization: do not evaluate right if not necessary
if (l == ValueNull.INSTANCE && (compareType & NULL_SAFE) == 0) {
return ValueNull.INSTANCE;
}
return 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 l the first value
......@@ -306,39 +299,91 @@ public class Comparison extends Condition {
* @param compareType the compare type
* @return result of comparison, either TRUE, FALSE, or NULL
*/
static Value compareNotNull(Database database, Value l, Value r, int compareType) {
boolean result;
static Value compare(Database database, Value l, Value r, int compareType) {
Value result;
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:
result = database.areEqual(l, r);
result = ValueBoolean.get(database.areEqual(l, r));
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:
result = !database.areEqual(l, r);
result = ValueBoolean.get(!database.areEqual(l, r));
break;
case BIGGER_EQUAL:
result = database.compare(l, r) >= 0;
case BIGGER_EQUAL: {
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;
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;
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;
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;
}
case SPATIAL_INTERSECTS: {
ValueGeometry lg = (ValueGeometry) l.convertTo(Value.GEOMETRY);
ValueGeometry rg = (ValueGeometry) r.convertTo(Value.GEOMETRY);
result = lg.intersectsBoundingBox(rg);
if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) {
result = ValueNull.INSTANCE;
} else {
ValueGeometry lg = (ValueGeometry) l.convertTo(Value.GEOMETRY);
ValueGeometry rg = (ValueGeometry) r.convertTo(Value.GEOMETRY);
result = ValueBoolean.get(lg.intersectsBoundingBox(rg));
}
break;
}
default:
throw DbException.throwInternalError("type=" + compareType);
}
return ValueBoolean.get(result);
return result;
}
private int getReversedCompareType(int type) {
......
......@@ -49,8 +49,8 @@ public class ConditionIn extends Condition {
@Override
public Value getValue(Session session) {
Value l = left.getValue(session);
if (l == ValueNull.INSTANCE) {
return l;
if (l.containsNull()) {
return ValueNull.INSTANCE;
}
int size = valueList.size();
if (size == 1) {
......@@ -63,9 +63,8 @@ public class ConditionIn extends Condition {
for (int i = 0; i < size; i++) {
Expression e = valueList.get(i);
Value r = e.getValue(session);
Value cmp;
if (r == ValueNull.INSTANCE
|| (cmp = Comparison.compareNotNull(database, l, r, Comparison.EQUAL)) == ValueNull.INSTANCE) {
Value cmp = Comparison.compare(database, l, r, Comparison.EQUAL);
if (cmp == ValueNull.INSTANCE) {
hasNull = true;
} else if (cmp == ValueBoolean.TRUE) {
return cmp;
......@@ -112,7 +111,7 @@ public class ConditionIn extends Condition {
ArrayList<Expression> list = new ArrayList<>(ri.getRowCount());
while (ri.next()) {
Value v = ri.currentRow()[0];
if (v != ValueNull.INSTANCE) {
if (!v.containsNull()) {
allValuesNull = false;
}
list.add(ValueExpression.get(v));
......@@ -127,7 +126,7 @@ public class ConditionIn extends Condition {
for (int i = 0; i < size; i++) {
Expression e = valueList.get(i);
e = e.optimize(session);
if (e.isConstant() && e.getValue(session) != ValueNull.INSTANCE) {
if (e.isConstant() && !e.getValue(session).containsNull()) {
allValuesNull = false;
}
if (allValuesConstant && !e.isConstant()) {
......
......@@ -35,6 +35,7 @@ public class ConditionInConstantSet extends Condition {
private Expression left;
private final ArrayList<Expression> valueList;
private final TreeSet<Value> valueSet;
private boolean hasNull;
private final int type;
private ExtTypeInfo extTypeInfo;
......@@ -58,27 +59,32 @@ public class ConditionInConstantSet extends Condition {
if (type == Value.ENUM) {
extTypeInfo = ((ExpressionColumn) left).getColumn().getExtTypeInfo();
for (Expression expression : valueList) {
valueSet.add(extTypeInfo.cast(expression.getValue(session)));
add(extTypeInfo.cast(expression.getValue(session)));
}
} else {
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
public Value getValue(Session session) {
Value x = left.getValue(session);
if (x == ValueNull.INSTANCE) {
if (x.containsNull()) {
return x;
}
boolean result = valueSet.contains(x);
if (!result) {
boolean setHasNull = valueSet.contains(ValueNull.INSTANCE);
if (setHasNull) {
return ValueNull.INSTANCE;
}
if (!result && hasNull) {
return ValueNull.INSTANCE;
}
return ValueBoolean.get(result);
}
......@@ -168,9 +174,9 @@ public class ConditionInConstantSet extends Condition {
if (add.isConstant()) {
valueList.add(add);
if (type == Value.ENUM) {
valueSet.add(add.getValue(session).convertToEnum(extTypeInfo));
add(add.getValue(session).convertToEnum(extTypeInfo));
} else {
valueSet.add(add.getValue(session).convertTo(type, session.getDatabase().getMode()));
add(add.getValue(session).convertTo(type, session.getDatabase().getMode()));
}
return this;
}
......
......@@ -67,14 +67,13 @@ public class ConditionInParameter extends Condition {
static Value getValue(Database database, Value l, Value value) {
boolean hasNull = false;
if (value == ValueNull.INSTANCE) {
if (value.containsNull()) {
hasNull = true;
} else if (value.getType() == Value.RESULT_SET) {
for (ResultInterface ri = value.getResult(); ri.next();) {
Value r = ri.currentRow()[0];
Value cmp;
if (r == ValueNull.INSTANCE
|| (cmp = Comparison.compareNotNull(database, l, r, Comparison.EQUAL)) == ValueNull.INSTANCE) {
Value cmp = Comparison.compare(database, l, r, Comparison.EQUAL);
if (cmp == ValueNull.INSTANCE) {
hasNull = true;
} else if (cmp == ValueBoolean.TRUE) {
return cmp;
......@@ -82,9 +81,8 @@ public class ConditionInParameter extends Condition {
}
} else {
for (Value r : ((ValueArray) value.convertTo(Value.ARRAY)).getList()) {
Value cmp;
if (r == ValueNull.INSTANCE
|| (cmp = Comparison.compareNotNull(database, l, r, Comparison.EQUAL)) == ValueNull.INSTANCE) {
Value cmp = Comparison.compare(database, l, r, Comparison.EQUAL);
if (cmp == ValueNull.INSTANCE) {
hasNull = true;
} else if (cmp == ValueBoolean.TRUE) {
return cmp;
......
......@@ -18,7 +18,6 @@ import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
import org.h2.util.StringUtils;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueBoolean;
import org.h2.value.ValueNull;
import org.h2.value.ValueRow;
......@@ -53,8 +52,8 @@ public class ConditionInSelect extends Condition {
Value l = left.getValue(session);
if (!rows.hasNext()) {
return ValueBoolean.get(all);
} else if (l == ValueNull.INSTANCE) {
return l;
} else if (l.containsNull()) {
return ValueNull.INSTANCE;
}
if (!database.getSettings().optimizeInSelect) {
return getValueSlow(rows, l);
......@@ -65,8 +64,8 @@ public class ConditionInSelect extends Condition {
}
int columnCount = query.getColumnCount();
if (columnCount != 1) {
l = l.convertTo(Value.ARRAY);
Value[] leftValue = ((ValueArray) l).getList();
l = l.convertTo(Value.ROW);
Value[] leftValue = ((ValueRow) l).getList();
if (columnCount == leftValue.length && rows.containsDistinct(leftValue)) {
return ValueBoolean.TRUE;
}
......@@ -79,9 +78,9 @@ public class ConditionInSelect extends Condition {
if (rows.containsDistinct(new Value[] { l })) {
return ValueBoolean.TRUE;
}
if (rows.containsDistinct(new Value[] { ValueNull.INSTANCE })) {
return ValueNull.INSTANCE;
}
}
if (rows.containsNull()) {
return ValueNull.INSTANCE;
}
return ValueBoolean.FALSE;
}
......@@ -93,9 +92,8 @@ public class ConditionInSelect extends Condition {
while (rows.next()) {
Value[] currentRow = rows.currentRow();
Value r = query.getColumnCount() == 1 ? currentRow[0] : ValueRow.get(currentRow);
Value cmp;
if (r == ValueNull.INSTANCE
|| (cmp = Comparison.compareNotNull(database, l, r, compareType)) == ValueNull.INSTANCE) {
Value cmp = Comparison.compare(database, l, r, compareType);
if (cmp == ValueNull.INSTANCE) {
return ValueNull.INSTANCE;
} else if (cmp == ValueBoolean.FALSE) {
return cmp;
......@@ -107,9 +105,8 @@ public class ConditionInSelect extends Condition {
while (rows.next()) {
Value[] currentRow = rows.currentRow();
Value r = query.getColumnCount() == 1 ? currentRow[0] : ValueRow.get(currentRow);
Value cmp;
if (r == ValueNull.INSTANCE
|| (cmp = Comparison.compareNotNull(database, l, r, compareType)) == ValueNull.INSTANCE) {
Value cmp = Comparison.compare(database, l, r, compareType);
if (cmp == ValueNull.INSTANCE) {
hasNull = true;
} else if (cmp == ValueBoolean.TRUE) {
return cmp;
......
......@@ -49,6 +49,14 @@ public interface LocalResult extends ResultInterface, ResultTarget {
*/
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.
*
......
......@@ -45,6 +45,7 @@ public class LocalResultImpl implements LocalResult {
private int[] distinctIndexes;
private boolean closed;
private boolean containsLobs;
private Boolean containsNull;
/**
* Construct a local result object.
......@@ -212,6 +213,27 @@ public class LocalResultImpl implements LocalResult {
return distinctRows.get(array) != null;
}
@Override
public boolean containsNull() {
Boolean r = containsNull;
if (r == null) {
r = false;
reset();
while (next()) {
Value[] row = currentRow;
for (int i = 0; i < visibleColumnCount; i++) {
if (row[i].containsNull()) {
r = true;
break;
}
}
}
reset();
containsNull = r;
}
return r;
}
@Override
public void reset() {
rowId = -1;
......
......@@ -1347,7 +1347,7 @@ public abstract class Value {
return ValueResultSet.get(result);
}
private DbException getDataConversionError(int targetType) {
DbException getDataConversionError(int targetType) {
DataType from = DataType.getDataType(getType());
DataType to = DataType.getDataType(targetType);
throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, (from != null ? from.name : "type=" + getType())
......@@ -1372,7 +1372,7 @@ public abstract class Value {
* @param v the other value
* @param databaseMode the database 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
*/
public final int compareTo(Value v, Mode databaseMode, CompareMode compareMode) {
......@@ -1401,6 +1401,48 @@ public abstract class Value {
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() {
return 0;
}
......
......@@ -5,7 +5,10 @@
*/
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;
/**
......@@ -52,6 +55,65 @@ abstract class ValueCollectionBase extends Value {
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 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;
......
......@@ -143,6 +143,11 @@ public class ValueNull extends Value {
throw DbException.throwInternalError("compare null");
}
@Override
public boolean containsNull() {
return true;
}
@Override
public long getPrecision() {
return PRECISION;
......
......@@ -23,3 +23,57 @@ SELECT ARRAY[10];
SELECT ARRAY[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, NULL] IN (SELECT A FROM TEST);
>> null
DROP TABLE TEST;
> ok
......@@ -8,3 +8,66 @@ SELECT ROW (10);
SELECT (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
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论