提交 2a3118fd authored 作者: Noel Grandin's avatar Noel Grandin

Issue #569: ClassCastException when filtering on ENUM value in WHERE clause

required passing the Column down to Value#convertTo
Note that the test code in testscript.sql was pulled from maxenglander's
PR #572
上级 476dba18
......@@ -21,6 +21,8 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Issue #569: ClassCastException when filtering on ENUM value in WHERE clause
</li>
<li>Issue #535: Allow explicit paths on Windows without drive letter
</li>
<li>Issue #549: Removed UNION ALL requirements for CTE
......
......@@ -165,34 +165,13 @@ public class ErrorCode {
* but the value is not one of the values enumerated by the
* type.
*
* This error is best thrown in a context when the column name
* and it's enumerated values are known.
*
* Example:
* <pre>
* CREATE TABLE TEST(CASE ENUM('sensitive','insensitive'));
* INSERT INTO TEST VALUES('snake');
* </pre>
*/
public static final int ENUM_VALUE_NOT_PERMITTED_1 = 22030;
/**
* The error with code <code>22031</code> is typically thrown
* when a math operation is attempted on an ENUM-typed cell,
* but the value resulting from the operation is not one of
* values enumerated by the type.
*
* This error is best thrown in a context when the column name
* is not known, but the enumerated values of the type are known.
*
* Example:
* <pre>
* CREATE TABLE TEST(CASE ENUM('sensitive','insensitive'));
* INSERT INTO TEST VALUES('sensitive');
* UPDATE TEST SET CASE = CASE + 100;
* </pre>
*/
public static final int ENUM_VALUE_NOT_PERMITTED_2 = 22031;
public static final int ENUM_VALUE_NOT_PERMITTED = 22030;
/**
* The error with code <code>22032</code> is thrown when an
......
......@@ -203,7 +203,8 @@ public class Comparison extends Condition {
// once.
if (constType != resType) {
right = ValueExpression.get(r.convertTo(resType,
MathUtils.convertLongToInt(left.getPrecision()), session.getDatabase().getMode()));
MathUtils.convertLongToInt(left.getPrecision()),
session.getDatabase().getMode(), ((ExpressionColumn) left).getColumn()));
}
} else if (right instanceof Parameter) {
((Parameter) right).setColumn(
......
......@@ -189,7 +189,7 @@ public class ExpressionColumn extends Expression {
throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL());
}
if (column.getEnumerators() != null) {
return ValueEnum.get(column.getEnumerators(), value);
return ValueEnum.get(column.getEnumerators(), value.getInt());
}
return value;
}
......
......@@ -177,13 +177,13 @@ public class Column {
*/
public Value convert(Value v, Mode mode) {
try {
return v.convertTo(type, MathUtils.convertLongToInt(precision), mode);
return v.convertTo(type, MathUtils.convertLongToInt(precision), mode, this);
} catch (DbException e) {
if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) {
String target = (table == null ? "" : table.getName() + ": ") +
getCreateSQL();
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1,
ErrorCode.DATA_CONVERSION_ERROR_1, e,
v.getSQL() + " (" + target + ")");
}
throw e;
......@@ -389,11 +389,11 @@ public class Column {
if (s.length() > 127) {
s = s.substring(0, 128) + "...";
}
throw DbException.get(ErrorCode.ENUM_VALUE_NOT_PERMITTED_1,
throw DbException.get(ErrorCode.ENUM_VALUE_NOT_PERMITTED,
getCreateSQL(), s);
}
value = ValueEnum.get(enumerators, value);
value = ValueEnum.get(enumerators, value.getInt());
}
updateSequenceIfRequired(session, value);
return value;
......
......@@ -24,6 +24,7 @@ import org.h2.engine.Mode;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.store.DataHandler;
import org.h2.table.Column;
import org.h2.tools.SimpleResultSet;
import org.h2.util.DateTimeUtils;
import org.h2.util.JdbcUtils;
......@@ -551,12 +552,26 @@ public abstract class Value {
* Compare a value to the specified type.
*
* @param targetType the type of the returned value
* @param the precision of the column to convert this value to.
* @param precision the precision of the column to convert this value to.
* The special constant <code>-1</code> is used to indicate that
* the precision plays no role when converting the value
* @return the converted value
*/
public Value convertTo(int targetType, int precision, Mode mode) {
public final Value convertTo(int targetType, int precision, Mode mode) {
return convertTo(targetType, precision, mode, null);
}
/**
* Compare a value to the specified type.
*
* @param targetType the type of the returned value
* @param precision the precision of the column to convert this value to.
* The special constant <code>-1</code> is used to indicate that
* the precision plays no role when converting the value
* @param column the column that contains the ENUM datatype enumerators, for dealing with ENUM conversions
* @return the converted value
*/
public Value convertTo(int targetType, int precision, Mode mode, Column column) {
// converting NULL is done in ValueNull
// converting BLOB to CLOB and vice versa is done in ValueLob
if (getType() == targetType) {
......@@ -582,6 +597,7 @@ public abstract class Value {
case BYTES:
case JAVA_OBJECT:
case UUID:
case ENUM:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
}
......@@ -593,6 +609,7 @@ public abstract class Value {
return ValueByte.get(getBoolean().booleanValue() ? (byte) 1 : (byte) 0);
case SHORT:
return ValueByte.get(convertToByte(getShort()));
case ENUM:
case INT:
return ValueByte.get(convertToByte(getInt()));
case LONG:
......@@ -617,6 +634,7 @@ public abstract class Value {
return ValueShort.get(getBoolean().booleanValue() ? (short) 1 : (short) 0);
case BYTE:
return ValueShort.get(getByte());
case ENUM:
case INT:
return ValueShort.get(convertToShort(getInt()));
case LONG:
......@@ -669,6 +687,7 @@ public abstract class Value {
return ValueLong.get(getByte());
case SHORT:
return ValueLong.get(getShort());
case ENUM:
case INT:
return ValueLong.get(getInt());
case DECIMAL:
......@@ -700,6 +719,7 @@ public abstract class Value {
return ValueDecimal.get(BigDecimal.valueOf(getByte()));
case SHORT:
return ValueDecimal.get(BigDecimal.valueOf(getShort()));
case ENUM:
case INT:
return ValueDecimal.get(BigDecimal.valueOf(getInt()));
case LONG:
......@@ -743,6 +763,7 @@ public abstract class Value {
return ValueDouble.get(getBigDecimal().doubleValue());
case FLOAT:
return ValueDouble.get(getFloat());
case ENUM:
case TIMESTAMP_TZ:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
......@@ -765,6 +786,7 @@ public abstract class Value {
return ValueFloat.get(getBigDecimal().floatValue());
case DOUBLE:
return ValueFloat.get((float) getDouble());
case ENUM:
case TIMESTAMP_TZ:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
......@@ -784,6 +806,9 @@ public abstract class Value {
case TIMESTAMP_TZ:
return ValueDate.fromDateValue(
((ValueTimestampTimeZone) this).getDateValue());
case ENUM:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
}
break;
}
......@@ -799,6 +824,9 @@ public abstract class Value {
case TIMESTAMP_TZ:
return ValueTime.fromNanos(
((ValueTimestampTimeZone) this).getTimeNanos());
case ENUM:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
}
break;
}
......@@ -814,6 +842,9 @@ public abstract class Value {
return ValueTimestamp.fromDateValueAndNanos(
((ValueTimestampTimeZone) this).getDateValue(),
((ValueTimestampTimeZone) this).getTimeNanos());
case ENUM:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
}
break;
}
......@@ -856,6 +887,7 @@ public abstract class Value {
(byte) x
});
}
case ENUM:
case TIMESTAMP_TZ:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
......@@ -868,6 +900,7 @@ public abstract class Value {
case BLOB:
return ValueJavaObject.getNoCopy(
null, getBytesNoCopy(), getDataHandler());
case ENUM:
case TIMESTAMP_TZ:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
......@@ -876,9 +909,19 @@ public abstract class Value {
}
case ENUM: {
switch (getType()) {
case BYTE:
case SHORT:
case INT:
case LONG:
case DECIMAL:
return ValueEnum.get(column.getEnumerators(), getInt());
case STRING:
return this;
case STRING_IGNORECASE:
case STRING_FIXED:
return ValueEnum.get(column.getEnumerators(), getString());
default:
throw DbException.get(
ErrorCode.DATA_CONVERSION_ERROR_1, getString());
}
}
case BLOB: {
......@@ -972,7 +1015,6 @@ public abstract class Value {
case JAVA_OBJECT:
return ValueJavaObject.getNoCopy(null,
StringUtils.convertHexToBytes(s.trim()), getDataHandler());
case ENUM:
case STRING:
return ValueString.get(s);
case STRING_IGNORECASE:
......
......@@ -49,19 +49,15 @@ public class ValueEnum extends ValueEnumBase {
private static final void check(final String[] enumerators, final Value value) {
check(enumerators);
switch (validate(enumerators, value)) {
case VALID:
return;
default:
throw DbException.get(ErrorCode.ENUM_VALUE_NOT_PERMITTED_2,
if (validate(enumerators, value) != Validation.VALID) {
throw DbException.get(ErrorCode.ENUM_VALUE_NOT_PERMITTED,
toString(enumerators), value.toString());
}
}
@Override
protected int compareSecure(final Value v, final CompareMode mode) {
final ValueEnum ev = ValueEnum.get(enumerators, v);
return MathUtils.compareInt(getInt(), ev.getInt());
return MathUtils.compareInt(getInt(), v.getInt());
}
/**
......@@ -72,11 +68,15 @@ public class ValueEnum extends ValueEnumBase {
* @param value a value
* @return the ENUM value
*/
public static ValueEnum get(final String[] enumerators, final Value value) {
check(enumerators, value);
public static ValueEnum get(final String[] enumerators, int value) {
check(enumerators, ValueInt.get(value));
return new ValueEnum(enumerators, value);
}
if (DataType.isStringType(value.getType())) {
final String cleanLabel = sanitize(value.getString());
public static ValueEnum get(final String[] enumerators, String value) {
check(enumerators, ValueString.get(value));
final String cleanLabel = sanitize(value);
for (int i = 0; i < enumerators.length; i++) {
if (cleanLabel.equals(sanitize(enumerators[i])))
......@@ -84,9 +84,6 @@ public class ValueEnum extends ValueEnumBase {
}
throw DbException.get(ErrorCode.GENERAL_ERROR_1, "Unexpected error");
} else {
return new ValueEnum(enumerators, value.getInt());
}
}
public String[] getEnumerators() {
......
......@@ -8,17 +8,16 @@ package org.h2.value;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.PrecisionModel;
import org.h2.engine.Mode;
import org.h2.message.DbException;
import org.h2.table.Column;
import org.h2.util.StringUtils;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.CoordinateSequenceFilter;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKBWriter;
......@@ -274,11 +273,11 @@ public class ValueGeometry extends Value {
}
@Override
public Value convertTo(int targetType, int precision, Mode mode) {
public Value convertTo(int targetType, int precision, Mode mode, Column column) {
if (targetType == Value.JAVA_OBJECT) {
return this;
}
return super.convertTo(targetType, precision, mode);
return super.convertTo(targetType, precision, mode, column);
}
/**
......
......@@ -23,6 +23,7 @@ import org.h2.store.FileStore;
import org.h2.store.FileStoreInputStream;
import org.h2.store.FileStoreOutputStream;
import org.h2.store.fs.FileUtils;
import org.h2.table.Column;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
import org.h2.util.SmallLRUCache;
......@@ -444,7 +445,7 @@ public class ValueLob extends Value {
* @return the converted value
*/
@Override
public Value convertTo(int t, int precision, Mode mode) {
public Value convertTo(int t, int precision, Mode mode, Column column) {
if (t == type) {
return this;
} else if (t == Value.CLOB) {
......@@ -454,7 +455,7 @@ public class ValueLob extends Value {
ValueLob copy = ValueLob.createBlob(getInputStream(), -1, handler);
return copy;
}
return super.convertTo(t, precision, mode);
return super.convertTo(t, precision, mode, column);
}
@Override
......
......@@ -25,6 +25,7 @@ import org.h2.store.FileStoreOutputStream;
import org.h2.store.LobStorageFrontend;
import org.h2.store.LobStorageInterface;
import org.h2.store.fs.FileUtils;
import org.h2.table.Column;
import org.h2.util.IOUtils;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
......@@ -185,7 +186,7 @@ public class ValueLobDb extends Value implements Value.ValueClob,
* @return the converted value
*/
@Override
public Value convertTo(int t, int precision, Mode mode) {
public Value convertTo(int t, int precision, Mode mode, Column column) {
if (t == type) {
return this;
} else if (t == Value.CLOB) {
......@@ -205,7 +206,7 @@ public class ValueLobDb extends Value implements Value.ValueClob,
return ValueLobDb.createSmallLob(t, small);
}
}
return super.convertTo(t, precision, mode);
return super.convertTo(t, precision, mode, column);
}
@Override
......
......@@ -13,9 +13,9 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import org.h2.engine.Mode;
import org.h2.message.DbException;
import org.h2.table.Column;
/**
* Implementation of NULL. NULL is not a regular data type.
......@@ -133,7 +133,7 @@ public class ValueNull extends Value {
}
@Override
public Value convertTo(int type, int precision, Mode mode) {
public Value convertTo(int type, int precision, Mode mode, Column column) {
return this;
}
......
......@@ -454,7 +454,7 @@ public class TestPreparedStatement extends TestBase {
PreparedStatement prep = conn.prepareStatement(
"INSERT INTO test_enum VALUES(?)");
prep.setObject(1, badSizes[i]);
assertThrows(ErrorCode.ENUM_VALUE_NOT_PERMITTED_1, prep).execute();
assertThrows(ErrorCode.ENUM_VALUE_NOT_PERMITTED, prep).execute();
}
String[] goodSizes = new String[]{"small", "medium", "large"};
......
......@@ -10684,3 +10684,58 @@ drop table card;
drop type CARD_SUIT;
> ok
--- ENUM in primary key with another column
create type CARD_SUIT as enum('hearts', 'clubs', 'spades', 'diamonds');
> ok
create table card (rank int, suit CARD_SUIT, primary key(rank, suit));
> ok
insert into card (rank, suit) values (0, 'clubs'), (3, 'hearts'), (1, 'clubs');
> update count: 3
insert into card (rank, suit) values (0, 'clubs');
> exception
select rank from card where suit = 'clubs';
> RANK
> ----
> 0
> 1
drop table card;
> ok
drop type CARD_SUIT;
> ok
--- ENUM with index
create type CARD_SUIT as enum('hearts', 'clubs', 'spades', 'diamonds');
> ok
create table card (rank int, suit CARD_SUIT, primary key(rank, suit));
> ok
insert into card (rank, suit) values (0, 'clubs'), (3, 'hearts'), (1, 'clubs');
> update count: 3
create index idx_card_suite on card(`suit`);
select rank from card where suit = 'clubs';
> RANK
> ----
> 0
> 1
select rank from card where suit in ('clubs');
> RANK
> ----
> 0
> 1
drop table card;
> ok
drop type CARD_SUIT;
> ok
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论