提交 fc102859 authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #451 from maxenglander/enum-support

Enum support
...@@ -1939,7 +1939,7 @@ CALL CSVWRITE('test2.csv', 'SELECT * FROM TEST', 'charset=UTF-8 fieldSeparator=| ...@@ -1939,7 +1939,7 @@ CALL CSVWRITE('test2.csv', 'SELECT * FROM TEST', 'charset=UTF-8 fieldSeparator=|
intType | booleanType | tinyintType | smallintType | bigintType | identityType intType | booleanType | tinyintType | smallintType | bigintType | identityType
| decimalType | doubleType | realType | dateType | timeType | timestampType | decimalType | doubleType | realType | dateType | timeType | timestampType
| binaryType | otherType | varcharType | varcharIgnorecaseType | charType | binaryType | otherType | varcharType | varcharIgnorecaseType | charType
| blobType | clobType | uuidType | arrayType | blobType | clobType | uuidType | arrayType | enumType
"," ","
A data type definition. A data type definition.
"," ","
...@@ -2527,6 +2527,20 @@ and ""ResultSet.getObject(..)"" or ""ResultSet.getArray(..)"" to retrieve the va ...@@ -2527,6 +2527,20 @@ and ""ResultSet.getObject(..)"" or ""ResultSet.getArray(..)"" to retrieve the va
ARRAY ARRAY
" "
"Data Types","ENUM Type","
{ ENUM (string [, ... ]) }
","
A type with enumerated values.
Mapped to ""java.lang.Integer"".
The first provided value is mapped to 0, the
second mapped to 1, and so on.
Duplicate and empty values are not permitted.
","
ENUM('clubs', 'diamonds', 'hearts', 'spades')
"
"Data Types","GEOMETRY Type"," "Data Types","GEOMETRY Type","
GEOMETRY GEOMETRY
"," ","
......
...@@ -25,6 +25,8 @@ Change Log ...@@ -25,6 +25,8 @@ Change Log
</li> </li>
<li>Added support for invisible columns. <li>Added support for invisible columns.
</li> </li>
<li>Added an ENUM data type, with syntax similar to that of MySQL and Oracle.
</li>
</ul> </ul>
<h2>Version 1.4.194 (2017-03-10)</h2> <h2>Version 1.4.194 (2017-03-10)</h2>
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
22012=Division by zero: {0} 22012=Division by zero: {0}
22018=Data conversion error converting {0} 22018=Data conversion error converting {0}
22025=Error in LIKE ESCAPE: {0} 22025=Error in LIKE ESCAPE: {0}
22030=Value not permitted for column {0}: {1}
22031=Value not a member of enumerators {0}: {1}
22032=Empty enums are not allowed
22033=Duplicate enumerators are not allowed for enum types: {0}
23502=NULL not allowed for column {0} 23502=NULL not allowed for column {0}
23503=Referential integrity constraint violation: {0} 23503=Referential integrity constraint violation: {0}
23505=Unique index or primary key violation: {0} 23505=Unique index or primary key violation: {0}
......
...@@ -159,6 +159,65 @@ public class ErrorCode { ...@@ -159,6 +159,65 @@ public class ErrorCode {
*/ */
public static final int LIKE_ESCAPE_ERROR_1 = 22025; public static final int LIKE_ESCAPE_ERROR_1 = 22025;
/**
* The error with code <code>22030</code> is thrown when
* an attempt is made to INSERT or UPDATE an ENUM-typed cell,
* 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;
/**
* The error with code <code>22032</code> is thrown when an
* attempt is made to add or modify an ENUM-typed column so
* that one or more of its enumerators would be empty.
*
* Example:
* <pre>
* CREATE TABLE TEST(CASE ENUM(' '));
* </pre>
*/
public static final int ENUM_EMPTY = 22032;
/**
* The error with code <code>22033</code> is thrown when an
* attempt is made to add or modify an ENUM-typed column so
* that it would have duplicate values.
*
* Example:
* <pre>
* CREATE TABLE TEST(CASE ENUM('sensitive', 'sensitive'));
* </pre>
*/
public static final int ENUM_DUPLICATE = 22033;
// 23: constraint violation // 23: constraint violation
/** /**
......
...@@ -146,6 +146,7 @@ import org.h2.value.ValueBoolean; ...@@ -146,6 +146,7 @@ import org.h2.value.ValueBoolean;
import org.h2.value.ValueBytes; import org.h2.value.ValueBytes;
import org.h2.value.ValueDate; import org.h2.value.ValueDate;
import org.h2.value.ValueDecimal; import org.h2.value.ValueDecimal;
import org.h2.value.ValueEnum;
import org.h2.value.ValueInt; 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;
...@@ -4127,6 +4128,7 @@ public class Parser { ...@@ -4127,6 +4128,7 @@ public class Parser {
} }
long precision = -1; long precision = -1;
int displaySize = -1; int displaySize = -1;
String[] enumerators = null;
int scale = -1; int scale = -1;
String comment = null; String comment = null;
Column templateColumn = null; Column templateColumn = null;
...@@ -4200,6 +4202,28 @@ public class Parser { ...@@ -4200,6 +4202,28 @@ public class Parser {
} }
read(")"); read(")");
} }
} else if (dataType.enumerated) {
if (readIf("(")) {
java.util.List<String> enumeratorList = new ArrayList<String>();
original += '(';
String enumerator0 = readString();
enumeratorList.add(enumerator0);
original += "'" + enumerator0 + "'";
while(readIf(",")) {
original += ',';
String enumeratorN = readString();
original += "'" + enumeratorN + "'";
enumeratorList.add(enumeratorN);
}
read(")");
original += ')';
enumerators = enumeratorList.toArray(new String[enumeratorList.size()]);
}
try {
ValueEnum.check(enumerators);
} catch(DbException e) {
throw e.addSQL(original);
}
} else if (readIf("(")) { } else if (readIf("(")) {
// Support for MySQL: INT(11), MEDIUMINT(8) and so on. // Support for MySQL: INT(11), MEDIUMINT(8) and so on.
// Just ignore the precision. // Just ignore the precision.
...@@ -4220,8 +4244,10 @@ public class Parser { ...@@ -4220,8 +4244,10 @@ public class Parser {
throw DbException.get(ErrorCode.INVALID_VALUE_SCALE_PRECISION, throw DbException.get(ErrorCode.INVALID_VALUE_SCALE_PRECISION,
Integer.toString(scale), Long.toString(precision)); Integer.toString(scale), Long.toString(precision));
} }
Column column = new Column(columnName, type, precision, scale, Column column = new Column(columnName, type, precision, scale,
displaySize); displaySize, enumerators);
if (templateColumn != null) { if (templateColumn != null) {
column.setNullable(templateColumn.isNullable()); column.setNullable(templateColumn.isNullable());
column.setDefaultExpression(session, column.setDefaultExpression(session,
......
...@@ -16,6 +16,7 @@ import org.h2.engine.Database; ...@@ -16,6 +16,7 @@ import org.h2.engine.Database;
import org.h2.engine.DbObject; import org.h2.engine.DbObject;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.schema.Schema; import org.h2.schema.Schema;
import org.h2.schema.Sequence; import org.h2.schema.Sequence;
...@@ -248,7 +249,18 @@ public class CreateTable extends SchemaCommand { ...@@ -248,7 +249,18 @@ public class CreateTable extends SchemaCommand {
if (scale > precision) { if (scale > precision) {
precision = scale; precision = scale;
} }
Column col = new Column(name, type, precision, scale, displaySize); String[] enumerators = null;
if (dt.enumerated) {
/**
* Only columns of tables may be enumerated.
*/
if(!(expr instanceof ExpressionColumn)) {
throw DbException.get(ErrorCode.GENERAL_ERROR_1,
"Unable to resolve enumerators of expression");
}
enumerators = ((ExpressionColumn)expr).getColumn().getEnumerators();
}
Column col = new Column(name, type, precision, scale, displaySize, enumerators);
addColumn(col); addColumn(col);
} }
} }
......
...@@ -22,6 +22,7 @@ import org.h2.table.Table; ...@@ -22,6 +22,7 @@ import org.h2.table.Table;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueBoolean; import org.h2.value.ValueBoolean;
import org.h2.value.ValueEnum;
/** /**
* A expression that represents a column of a table or view. * A expression that represents a column of a table or view.
...@@ -187,6 +188,9 @@ public class ExpressionColumn extends Expression { ...@@ -187,6 +188,9 @@ public class ExpressionColumn extends Expression {
columnResolver.getValue(column); columnResolver.getValue(column);
throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL()); throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL());
} }
if (column.getEnumerators() != null) {
return ValueEnum.get(column.getEnumerators(), value);
}
return value; return value;
} }
......
...@@ -201,6 +201,7 @@ public class ValueDataType implements DataType { ...@@ -201,6 +201,7 @@ public class ValueDataType implements DataType {
case Value.SHORT: case Value.SHORT:
buff.put((byte) type).putShort(v.getShort()); buff.put((byte) type).putShort(v.getShort());
break; break;
case Value.ENUM:
case Value.INT: { case Value.INT: {
int x = v.getInt(); int x = v.getInt();
if (x < 0) { if (x < 0) {
......
...@@ -9,6 +9,10 @@ ...@@ -9,6 +9,10 @@
22012=Division by zero: {0} 22012=Division by zero: {0}
22018=Data conversion error converting {0} 22018=Data conversion error converting {0}
22025=Error in LIKE ESCAPE: {0} 22025=Error in LIKE ESCAPE: {0}
22030=Value not permitted for column {0}: {1}
22031=Value not a member of enumerators {0}: {1}
22032=Empty enums are not allowed
22033=Duplicate enumerators are not allowed for enum types: {0}
23502=NULL not allowed for column {0} 23502=NULL not allowed for column {0}
23503=Referential integrity constraint violation: {0} 23503=Referential integrity constraint violation: {0}
23505=Unique index or primary key violation: {0} 23505=Unique index or primary key violation: {0}
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
22012=División por cero: {0} 22012=División por cero: {0}
22018=Conversión de datos fallida, convirtiendo {0} 22018=Conversión de datos fallida, convirtiendo {0}
22025=Error en LIKE ESCAPE: {0} 22025=Error en LIKE ESCAPE: {0}
22030=Valor no permitido para la columna {0}: {1}
23502=La columna {0} no permite valores nulos (NULL) 23502=La columna {0} no permite valores nulos (NULL)
23503=Violación de una restricción de Integridad Referencial: {0} 23503=Violación de una restricción de Integridad Referencial: {0}
23505=Violación de indice de Unicidad ó Clave primaria: {0} 23505=Violación de indice de Unicidad ó Clave primaria: {0}
......
...@@ -606,7 +606,7 @@ Optional parameters for CSVREAD and CSVWRITE." ...@@ -606,7 +606,7 @@ Optional parameters for CSVREAD and CSVWRITE."
intType | booleanType | tinyintType | smallintType | bigintType | identityType intType | booleanType | tinyintType | smallintType | bigintType | identityType
| decimalType | doubleType | realType | dateType | timeType | timestampType | decimalType | doubleType | realType | dateType | timeType | timestampType
| binaryType | otherType | varcharType | varcharIgnorecaseType | charType | binaryType | otherType | varcharType | varcharIgnorecaseType | charType
| blobType | clobType | uuidType | arrayType | blobType | clobType | uuidType | arrayType | enumType
"," ","
A data type definition." A data type definition."
"Other Grammar","Date"," "Other Grammar","Date","
...@@ -834,6 +834,10 @@ Universally unique identifier." ...@@ -834,6 +834,10 @@ Universally unique identifier."
ARRAY ARRAY
"," ","
An array of values." An array of values."
"Data Types","ENUM Type","
{ ENUM (string [, ... ]) }
","
A type with enumerated values."
"Data Types","GEOMETRY Type"," "Data Types","GEOMETRY Type","
GEOMETRY GEOMETRY
"," ","
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
package org.h2.table; package org.h2.table;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.util.Arrays;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Parser; import org.h2.command.Parser;
import org.h2.engine.Constants; import org.h2.engine.Constants;
...@@ -26,6 +27,7 @@ import org.h2.util.StringUtils; ...@@ -26,6 +27,7 @@ import org.h2.util.StringUtils;
import org.h2.value.DataType; import org.h2.value.DataType;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueDate; import org.h2.value.ValueDate;
import org.h2.value.ValueEnum;
import org.h2.value.ValueInt; 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;
...@@ -66,6 +68,7 @@ public class Column { ...@@ -66,6 +68,7 @@ public class Column {
private final int type; private final int type;
private long precision; private long precision;
private int scale; private int scale;
private String[] enumerators;
private int displaySize; private int displaySize;
private Table table; private Table table;
private String name; private String name;
...@@ -89,11 +92,20 @@ public class Column { ...@@ -89,11 +92,20 @@ public class Column {
private boolean visible = true; private boolean visible = true;
public Column(String name, int type) { public Column(String name, int type) {
this(name, type, -1, -1, -1); this(name, type, -1, -1, -1, null);
}
public Column(String name, int type, String[] enumerators) {
this(name, type, -1, -1, -1, enumerators);
} }
public Column(String name, int type, long precision, int scale, public Column(String name, int type, long precision, int scale,
int displaySize) { int displaySize) {
this(name, type, precision, scale, displaySize, null);
}
public Column(String name, int type, long precision, int scale,
int displaySize, String[] enumerators) {
this.name = name; this.name = name;
this.type = type; this.type = type;
if (precision == -1 && scale == -1 && displaySize == -1 && type != Value.UNKNOWN) { if (precision == -1 && scale == -1 && displaySize == -1 && type != Value.UNKNOWN) {
...@@ -105,6 +117,7 @@ public class Column { ...@@ -105,6 +117,7 @@ public class Column {
this.precision = precision; this.precision = precision;
this.scale = scale; this.scale = scale;
this.displaySize = displaySize; this.displaySize = displaySize;
this.enumerators = enumerators;
} }
@Override @Override
...@@ -133,8 +146,12 @@ public class Column { ...@@ -133,8 +146,12 @@ public class Column {
return table.getId() ^ name.hashCode(); return table.getId() ^ name.hashCode();
} }
public boolean isEnumerated() {
return type == Value.ENUM;
}
public Column getClone() { public Column getClone() {
Column newColumn = new Column(name, type, precision, scale, displaySize); Column newColumn = new Column(name, type, precision, scale, displaySize, enumerators);
newColumn.copy(this); newColumn.copy(this);
return newColumn; return newColumn;
} }
...@@ -258,6 +275,14 @@ public class Column { ...@@ -258,6 +275,14 @@ public class Column {
nullable = b; nullable = b;
} }
public String[] getEnumerators() {
return enumerators;
}
public void setEnumerators(String[] enumerators) {
this.enumerators = enumerators;
}
public boolean getVisible() { public boolean getVisible() {
return visible; return visible;
} }
...@@ -345,6 +370,18 @@ public class Column { ...@@ -345,6 +370,18 @@ public class Column {
getCreateSQL(), s + " (" + value.getPrecision() + ")"); getCreateSQL(), s + " (" + value.getPrecision() + ")");
} }
} }
if (isEnumerated()) {
if (!ValueEnum.isValid(enumerators, value)) {
String s = value.getTraceSQL();
if (s.length() > 127) {
s = s.substring(0, 128) + "...";
}
throw DbException.get(ErrorCode.ENUM_VALUE_NOT_PERMITTED_1,
getCreateSQL(), s);
}
value = ValueEnum.get(enumerators, value);
}
updateSequenceIfRequired(session, value); updateSequenceIfRequired(session, value);
return value; return value;
} }
...@@ -434,6 +471,15 @@ public class Column { ...@@ -434,6 +471,15 @@ public class Column {
case Value.DECIMAL: case Value.DECIMAL:
buff.append('(').append(precision).append(", ").append(scale).append(')'); buff.append('(').append(precision).append(", ").append(scale).append(')');
break; break;
case Value.ENUM:
buff.append('(');
for (int i = 0; i < enumerators.length; i++) {
buff.append('\'').append(enumerators[i]).append('\'');
if(i < enumerators.length - 1) {
buff.append(',');
}
}
buff.append(')');
case Value.BYTES: case Value.BYTES:
case Value.STRING: case Value.STRING:
case Value.STRING_IGNORECASE: case Value.STRING_IGNORECASE:
...@@ -748,6 +794,8 @@ public class Column { ...@@ -748,6 +794,8 @@ public class Column {
displaySize = source.displaySize; displaySize = source.displaySize;
name = source.name; name = source.name;
precision = source.precision; precision = source.precision;
enumerators = source.enumerators == null ? null :
Arrays.copyOf(source.enumerators, source.enumerators.length);
scale = source.scale; scale = source.scale;
// table is not set // table is not set
// columnId is not set // columnId is not set
......
...@@ -140,6 +140,11 @@ public class DataType { ...@@ -140,6 +140,11 @@ public class DataType {
*/ */
public boolean caseSensitive; public boolean caseSensitive;
/**
* If enumerated values are supported.
*/
public boolean enumerated;
/** /**
* If the precision parameter is supported. * If the precision parameter is supported.
*/ */
...@@ -385,6 +390,11 @@ public class DataType { ...@@ -385,6 +390,11 @@ public class DataType {
new String[]{"RESULT_SET"}, new String[]{"RESULT_SET"},
400 400
); );
add(Value.ENUM, Types.OTHER, "Enum",
createEnum(),
new String[]{"ENUM"},
48
);
for (Integer i : TYPES_BY_VALUE_TYPE.keySet()) { for (Integer i : TYPES_BY_VALUE_TYPE.keySet()) {
Value.getOrder(i); Value.getOrder(i);
} }
...@@ -406,6 +416,7 @@ public class DataType { ...@@ -406,6 +416,7 @@ public class DataType {
dt.params = dataType.params; dt.params = dataType.params;
dt.prefix = dataType.prefix; dt.prefix = dataType.prefix;
dt.suffix = dataType.suffix; dt.suffix = dataType.suffix;
dt.enumerated = dataType.enumerated;
dt.supportsPrecision = dataType.supportsPrecision; dt.supportsPrecision = dataType.supportsPrecision;
dt.supportsScale = dataType.supportsScale; dt.supportsScale = dataType.supportsScale;
dt.defaultPrecision = dataType.defaultPrecision; dt.defaultPrecision = dataType.defaultPrecision;
...@@ -459,6 +470,13 @@ public class DataType { ...@@ -459,6 +470,13 @@ public class DataType {
return dataType; return dataType;
} }
private static DataType createEnum() {
DataType dataType = createString(false);
dataType.enumerated = true;
dataType.supportsPrecision = false;
dataType.supportsScale = false;
return dataType;
}
private static DataType createString(boolean caseSensitive) { private static DataType createString(boolean caseSensitive) {
DataType dataType = new DataType(); DataType dataType = new DataType();
dataType.prefix = "'"; dataType.prefix = "'";
...@@ -667,6 +685,12 @@ public class DataType { ...@@ -667,6 +685,12 @@ public class DataType {
v = ValueArray.get(values); v = ValueArray.get(values);
break; break;
} }
case Value.ENUM: {
int value = rs.getInt(columnIndex);
v = rs.wasNull() ? (Value) ValueNull.INSTANCE :
ValueInt.get(value);
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) {
......
...@@ -494,6 +494,11 @@ public class Transfer { ...@@ -494,6 +494,11 @@ public class Transfer {
} }
break; break;
} }
case Value.ENUM: {
writeInt(v.getInt());
writeString(v.getString());
break;
}
case Value.RESULT_SET: { case Value.RESULT_SET: {
try { try {
ResultSet rs = ((ValueResultSet) v).getResultSet(); ResultSet rs = ((ValueResultSet) v).getResultSet();
...@@ -594,6 +599,11 @@ public class Transfer { ...@@ -594,6 +599,11 @@ public class Transfer {
return ValueDouble.get(readDouble()); return ValueDouble.get(readDouble());
case Value.FLOAT: case Value.FLOAT:
return ValueFloat.get(readFloat()); return ValueFloat.get(readFloat());
case Value.ENUM: {
final int ordinal = readInt();
final String label = readString();
return ValueEnumBase.get(label, ordinal);
}
case Value.INT: case Value.INT:
return ValueInt.get(readInt()); return ValueInt.get(readInt());
case Value.LONG: case Value.LONG:
......
...@@ -168,10 +168,15 @@ public abstract class Value { ...@@ -168,10 +168,15 @@ public abstract class Value {
*/ */
public static final int TIMESTAMP_TZ = 24; public static final int TIMESTAMP_TZ = 24;
/**
* The value type for ENUM values.
*/
public static final int ENUM = 25;
/** /**
* The number of value types. * The number of value types.
*/ */
public static final int TYPE_COUNT = TIMESTAMP_TZ; public static final int TYPE_COUNT = ENUM;
private static SoftReference<Value[]> softCache = private static SoftReference<Value[]> softCache =
new SoftReference<Value[]>(null); new SoftReference<Value[]>(null);
...@@ -323,6 +328,8 @@ public abstract class Value { ...@@ -323,6 +328,8 @@ public abstract class Value {
return 50_000; return 50_000;
case RESULT_SET: case RESULT_SET:
return 51_000; return 51_000;
case ENUM:
return 52_000;
default: default:
if (JdbcUtils.customDataTypesHandler != null) { if (JdbcUtils.customDataTypesHandler != null) {
return JdbcUtils.customDataTypesHandler.getDataTypeOrder(type); return JdbcUtils.customDataTypesHandler.getDataTypeOrder(type);
...@@ -618,6 +625,8 @@ public abstract class Value { ...@@ -618,6 +625,8 @@ public abstract class Value {
return ValueInt.get(getBoolean().booleanValue() ? 1 : 0); return ValueInt.get(getBoolean().booleanValue() ? 1 : 0);
case BYTE: case BYTE:
return ValueInt.get(getByte()); return ValueInt.get(getByte());
case ENUM:
return ValueInt.get(getInt());
case SHORT: case SHORT:
return ValueInt.get(getShort()); return ValueInt.get(getShort());
case LONG: case LONG:
...@@ -849,6 +858,13 @@ public abstract class Value { ...@@ -849,6 +858,13 @@ public abstract class Value {
} }
break; break;
} }
case ENUM: {
switch (getType()) {
case INT:
case STRING:
return this;
}
}
case BLOB: { case BLOB: {
switch (getType()) { switch (getType()) {
case BYTES: case BYTES:
...@@ -940,6 +956,7 @@ public abstract class Value { ...@@ -940,6 +956,7 @@ public abstract class Value {
case JAVA_OBJECT: case JAVA_OBJECT:
return ValueJavaObject.getNoCopy(null, return ValueJavaObject.getNoCopy(null,
StringUtils.convertHexToBytes(s.trim()), getDataHandler()); StringUtils.convertHexToBytes(s.trim()), getDataHandler());
case ENUM:
case STRING: case STRING:
return ValueString.get(s); return ValueString.get(s);
case STRING_IGNORECASE: case STRING_IGNORECASE:
......
package org.h2.value;
import java.util.Locale;
import org.h2.api.ErrorCode;
import org.h2.message.DbException;
import org.h2.util.MathUtils;
import org.h2.value.DataType;
public class ValueEnum extends ValueEnumBase {
private static enum Validation {
DUPLICATE,
EMPTY,
INVALID,
VALID
}
private final String[] enumerators;
private ValueEnum(final String[] enumerators, final int ordinal) {
super(enumerators[ordinal], ordinal);
this.enumerators = enumerators;
}
/**
* Check for any violations, such as empty
* values, duplicate values.
*
* @param enumerators the enumerators
*/
public static final void check(final String[] enumerators) {
switch (validate(enumerators)) {
case VALID:
return;
case EMPTY:
throw DbException.get(ErrorCode.ENUM_EMPTY);
case DUPLICATE:
throw DbException.get(ErrorCode.ENUM_DUPLICATE,
toString(enumerators));
default:
throw DbException.get(ErrorCode.INVALID_VALUE_2,
toString(enumerators));
}
}
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,
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());
}
/**
* Create an ENUM value from the provided enumerators
* and value.
*
* @param enumerators the enumerators
* @param value a value
* @return the ENUM value
*/
public static ValueEnum get(final String[] enumerators, final Value value) {
check(enumerators, value);
if (DataType.isStringType(value.getType())) {
final String cleanLabel = sanitize(value.getString());
for (int i = 0; i < enumerators.length; i++) {
if (cleanLabel.equals(sanitize(enumerators[i])))
return new ValueEnum(enumerators, i);
}
throw DbException.get(ErrorCode.GENERAL_ERROR_1, "Unexpected error");
} else {
return new ValueEnum(enumerators, value.getInt());
}
}
public String[] getEnumerators() {
return enumerators;
}
/**
* Evaluates whether a valid ENUM can be constructed
* from the provided enumerators and value.
*
* @param enumerators the enumerators
* @param value the value
* @return whether a valid ENUM can be constructed from the provided values
*/
public static boolean isValid(final String enumerators[], final Value value) {
return validate(enumerators, value).equals(Validation.VALID);
}
private static String sanitize(final String label) {
return label == null ? null : label.trim().toUpperCase(Locale.ENGLISH);
}
private static String[] sanitize(final String[] enumerators) {
if (enumerators == null || enumerators.length == 0) return null;
final String[] clean = new String[enumerators.length];
for (int i = 0; i < enumerators.length; i++) {
clean[i] = sanitize(enumerators[i]);
}
return clean;
}
private static String toString(final String[] enumerators) {
String result = "(";
for (int i = 0; i < enumerators.length; i++) {
result += "'" + enumerators[i] + "'";
if (i < enumerators.length - 1) {
result += ", ";
}
}
result += ")";
return result;
}
private static Validation validate(final String[] enumerators) {
final String[] cleaned = sanitize(enumerators);
if (cleaned == null || cleaned.length == 0) {
return Validation.EMPTY;
}
for (int i = 0; i < cleaned.length; i++) {
if (cleaned[i] == null || cleaned[i].equals("")) {
return Validation.EMPTY;
}
if (i < cleaned.length - 1) {
for (int j = i + 1; j < cleaned.length; j++) {
if (cleaned[i].equals(cleaned[j])) {
return Validation.DUPLICATE;
}
}
}
}
return Validation.VALID;
}
private static Validation validate(final String[] enumerators, final Value value) {
final Validation validation = validate(enumerators);
if (!validation.equals(Validation.VALID)) {
return validation;
}
if (DataType.isStringType(value.getType())) {
final String cleanLabel = sanitize(value.getString());
for (int i = 0; i < enumerators.length; i++) {
if (cleanLabel.equals(sanitize(enumerators[i]))) {
return Validation.VALID;
}
}
return Validation.INVALID;
} else {
final int ordinal = value.getInt();
if (ordinal < 0 || ordinal >= enumerators.length) {
return Validation.INVALID;
}
return Validation.VALID;
}
}
}
package org.h2.value;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Locale;
import org.h2.message.DbException;
import org.h2.util.MathUtils;
/**
* Base implementation of the ENUM data type.
*
* Currently, this class is used primarily for
* client-server communication.
*/
public class ValueEnumBase extends Value {
private static final int PRECISION = 10;
private static final int DISPLAY_SIZE = 11;
private final String label;
private final int ordinal;
protected ValueEnumBase(final String label, final int ordinal) {
this.label = label;
this.ordinal = ordinal;
}
@Override
public Value add(final Value v) {
final Value iv = v.convertTo(Value.INT);
return convertTo(Value.INT).add(iv);
}
@Override
protected int compareSecure(final Value v, final CompareMode mode) {
return MathUtils.compareInt(getInt(), v.getInt());
}
@Override
public Value divide(final Value v) {
final Value iv = v.convertTo(Value.INT);
return convertTo(Value.INT).divide(iv);
}
@Override
public boolean equals(final Object other) {
return other instanceof ValueEnumBase &&
getInt() == ((ValueEnumBase) other).getInt();
}
/**
* Get or create an enum value with the given label and ordinal.
*
* @param label the label
* @param ordinal the ordinal
* @return the value
*/
public static ValueEnumBase get(final String label, final int ordinal) {
return new ValueEnumBase(label, ordinal);
}
@Override
public int getDisplaySize() {
return DISPLAY_SIZE;
}
@Override
public int getInt() {
return ordinal;
}
@Override
public long getLong() {
return ordinal;
}
@Override
public Object getObject() {
return ordinal;
}
@Override
public long getPrecision() {
return PRECISION;
}
@Override
public int getSignum() {
return Integer.signum(ordinal);
}
@Override
public String getSQL() {
return getString();
}
@Override
public String getString() {
return label;
}
@Override
public int getType() {
return Value.ENUM;
}
@Override
public int hashCode() {
int results = 31;
results += getString().hashCode();
results += getInt();
return results;
}
@Override
public Value modulus(final Value v) {
final Value iv = v.convertTo(Value.INT);
return convertTo(Value.INT).modulus(iv);
}
@Override
public Value multiply(final Value v) {
final Value iv = v.convertTo(Value.INT);
return convertTo(Value.INT).multiply(iv);
}
@Override
public void set(final PreparedStatement prep, final int parameterIndex)
throws SQLException {
prep.setInt(parameterIndex, ordinal);
}
@Override
public Value subtract(final Value v) {
final Value iv = v.convertTo(Value.INT);
return convertTo(Value.INT).subtract(iv);
}
}
...@@ -57,6 +57,7 @@ public class TestPreparedStatement extends TestBase { ...@@ -57,6 +57,7 @@ public class TestPreparedStatement extends TestBase {
testToString(conn); testToString(conn);
testExecuteUpdateCall(conn); testExecuteUpdateCall(conn);
testPrepareExecute(conn); testPrepareExecute(conn);
testEnum(conn);
testUUID(conn); testUUID(conn);
testUUIDAsJavaObject(conn); testUUIDAsJavaObject(conn);
testScopedGeneratedKey(conn); testScopedGeneratedKey(conn);
...@@ -443,6 +444,37 @@ public class TestPreparedStatement extends TestBase { ...@@ -443,6 +444,37 @@ public class TestPreparedStatement extends TestBase {
assertFalse(rs.next()); assertFalse(rs.next());
} }
private void testEnum(Connection conn) throws SQLException {
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE test_enum(size ENUM('small', 'medium', 'large'))");
String[] badSizes = new String[]{"green", "smol", "0"};
for (int i = 0; i < badSizes.length; i++) {
PreparedStatement prep = conn.prepareStatement(
"INSERT INTO test_enum VALUES(?)");
prep.setObject(1, badSizes[i]);
assertThrows(ErrorCode.ENUM_VALUE_NOT_PERMITTED_1, prep).execute();
}
String[] goodSizes = new String[]{"small", "medium", "large"};
for (int i = 0; i < goodSizes.length; i++) {
PreparedStatement prep = conn.prepareStatement(
"INSERT INTO test_enum VALUES(?)");
prep.setObject(1, goodSizes[i]);
prep.execute();
ResultSet rs = stat.executeQuery("SELECT * FROM test_enum");
for (int j = 0; j <= i; j++) {
rs.next();
}
assertEquals(goodSizes[i], rs.getString(1));
assertEquals(i, rs.getInt(1));
Object o = rs.getObject(1);
assertEquals(Integer.class, o.getClass());
}
stat.execute("DROP TABLE test_enum");
}
private void testUUID(Connection conn) throws SQLException { private void testUUID(Connection conn) throws SQLException {
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
stat.execute("create table test_uuid(id uuid primary key)"); stat.execute("create table test_uuid(id uuid primary key)");
......
...@@ -10589,3 +10589,75 @@ create table z.z (id int); ...@@ -10589,3 +10589,75 @@ create table z.z (id int);
drop schema z; drop schema z;
> ok > ok
----------------
--- ENUM support
----------------
--- ENUM basic operations
create table card (rank int, suit enum('hearts', 'clubs', 'spades'));
> ok
insert into card (rank, suit) values (0, 'clubs'), (3, 'hearts');
> update count: 2
alter table card alter column suit enum('hearts', 'clubs', 'spades', 'diamonds');
> ok
select * from card;
> RANK SUIT
> ---- ------
> 0 clubs
> 3 hearts
select * from card order by suit;
> RANK SUIT
> ---- ------
> 3 hearts
> 0 clubs
insert into card (rank, suit) values (8, 'diamonds'), (10, 'clubs'), (7, 'hearts');
> update count: 3
select suit, count(rank) from card group by suit order by suit, count(rank);
> SUIT COUNT(RANK)
> -------- -----------
> hearts 2
> clubs 2
> diamonds 1
select rank from card where suit = 'diamonds';
> RANK
> ----
> 8
--- ENUM integer-based operations
select rank from card where suit = 1;
> RANK
> ----
> 0
> 10
insert into card (rank, suit) values(5, 2);
> update count: 1
select * from card where rank = 5;
> RANK SUIT
> ---- ------
> 5 spades
--- ENUM edge cases
insert into card (rank, suit) values(6, ' ');
> exception
alter table card alter column suit enum('hearts', 'clubs', 'spades', 'diamonds', 'clubs');
> exception
alter table card alter column suit enum('hearts', 'clubs', 'spades', 'diamonds', '');
> exception
drop table card;
> ok
...@@ -76,7 +76,7 @@ calculation calculations calendar calendars call callable callback callbacks ...@@ -76,7 +76,7 @@ calculation calculations calendar calendars call callable callback callbacks
called caller calling calls cally caload came camel can cancel canceled canceling called caller calling calls cally caload came camel can cancel canceled canceling
cancellation cancelled cancels candidates cannot canonical cap capabilities cancellation cancelled cancels candidates cannot canonical cap capabilities
capability capacity capitalization capitalize capitalized capone caps capture capability capacity capitalization capitalize capitalized capone caps capture
captured car cardinal cardinality care careful carriage carrier cars cartesian captured car card cardinal cardinality care careful carriage carrier cars cartesian
cascade cascading case cases casesensitive casewhen cash casing casqueiro cast cascade cascading case cases casesensitive casewhen cash casing casqueiro cast
casting castore cat catalina catalog catalogs cataloguing catch catcher catches casting castore cat catalina catalog catalogs cataloguing catch catcher catches
catching category catlog caucho caught cause caused causes causing cavestro catching category catlog caucho caught cause caused causes causing cavestro
...@@ -181,8 +181,8 @@ detailed details detect detected detecting detection detector detects determine ...@@ -181,8 +181,8 @@ detailed details detect detected detecting detection detector detects determine
determining deterministic detrimental deusen deutsch dev develop developed determining deterministic detrimental deusen deutsch dev develop developed
developer developers developing development devenish deviation device devices developer developers developing development devenish deviation device devices
dfile dgenerate dgroup dhe dhis diabetes diagnostic diagnostics diagram diagrams dfile dgenerate dgroup dhe dhis diabetes diagnostic diagnostics diagram diagrams
dialect dialog diams dick dictionary did didn died dieguez diehard dies diff dialect dialog diamonds diams dick dictionary did didn died dieguez diehard dies
differ difference differences different differential differentiate differently diff differ difference differences different differential differentiate differently
differs dig digest digit digital digits diligence dim dimension dimensional differs dig digest digit digital digits diligence dim dimension dimensional
dimensions dimitrijs dinamica dining dip dips dir direct direction directly dimensions dimitrijs dinamica dining dip dips dir direct direction directly
directories directory directs dirname dirs dirty disable disabled directories directory directs dirname dirs dirty disable disabled
...@@ -218,14 +218,14 @@ encrypting encryption encrypts end ended enderbury endif ending endings endless ...@@ -218,14 +218,14 @@ encrypting encryption encrypts end ended enderbury endif ending endings endless
endlessly endorse ends enforce enforceability enforceable enforced engine engines endlessly endorse ends enforce enforceability enforceable enforced engine engines
english enhance enhanced enhancement enhancer enlarge enough enqueued ensp ensure english enhance enhanced enhancement enhancer enlarge enough enqueued ensp ensure
ensures ensuring enter entered entering enterprise entire entities entity entries ensures ensuring enter entered entering enterprise entire entities entity entries
entry enum enumerate enumeration env envelope environment environments enwiki eof entry enum enumerate enumerated enumerator enumerators enumeration env envelope
eol epl epoch epoll epsilon equal equality equally equals equipment equitable environment environments enwiki eof eol epl epoch epoll epsilon equal equality equally
equiv equivalent equivalents era erable eremainder eric erik err error errorlevel equals equipment equitable equiv equivalent equivalents era erable eremainder eric
errors erwan ery esc escape escaped escapes escaping escargots ese espa essential erik err error errorlevel errors erwan ery esc escape escaped escapes escaping
essentials established estimate estimated estimates estimating estimation escargots ese espa essential essentials established estimate estimated estimates
estoppel eta etc eth etl euml euro europe europeu euros eva eval evaluatable estimating estimation estoppel eta etc eth etl euml euro europe europeu euros eva eval
evaluate evaluated evaluates evaluating evaluation evdokimov even evenly event evaluatable evaluate evaluated evaluates evaluating evaluation evdokimov even evenly
events eventually ever every everybody everyone everything everywhere evict event events eventually ever every everybody everyone everything everywhere evict
evicted eviction evolving exact exactly example examples exceed exceeded exceeds evicted eviction evolving exact exactly example examples exceed exceeded exceeds
excel except exception exceptions exchange exclude excluded excludes excluding excel except exception exceptions exchange exclude excluded excludes excluding
exclusion exclusive exclusively exe exec executable executables execute executed exclusion exclusive exclusively exe exec executable executables execute executed
...@@ -277,7 +277,7 @@ google googlegroups got goto goubard governed governing government gpg grabbing ...@@ -277,7 +277,7 @@ google googlegroups got goto goubard governed governing government gpg grabbing
graceful graf grails grained grains grajcar grammar grammars grandin grandma graceful graf grails grained grains grajcar grammar grammars grandin grandma
grant grantable granted grantedrole grantee granteetype granting grantor grants grant grantable granted grantedrole grantee granteetype granting grantor grants
granularity graph graphic graphical graphics grass gray great greater greatest granularity graph graphic graphical graphics grass gray great greater greatest
greatly gredler greece greedy gregorian grep grew grid gridwidth gridx gridy greatly gredler greece greedy green gregorian grep grew grid gridwidth gridx gridy
groove groovy gross group grouped grouping groups groupware grover grow growing groove groovy gross group grouped grouping groups groupware grover grow growing
grows growth guarantee guaranteed guard guardian guess guesses guest gui guid grows growth guarantee guaranteed guard guardian guess guesses guest gui guid
guide guidelines guides gumbo gustav gutierrez gzip hack had haidinyak half hallo guide guidelines guides gumbo gustav gutierrez gzip hack had haidinyak half hallo
...@@ -485,9 +485,9 @@ predict predicted prediction prefer preferable preferdoslikelineends preferences ...@@ -485,9 +485,9 @@ predict predicted prediction prefer preferable preferdoslikelineends preferences
preferred prefix prefixes prefs premain premature prep prepare prepared prepares preferred prefix prefixes prefs premain premature prep prepare prepared prepares
preparing prepended prepending pres presence present presentation preserve preparing prepended prepending pres presence present presentation preserve
preserved preserving press pressed pretty prev prevent prevented prevention preserved preserving press pressed pretty prev prevent prevented prevention
prevents previous previously pri price prices primary prime primitive primitives prevents previous previously pri price prices primarily primary prime primitive
principal print printable printed printf printing println prints prio prior primitives principal print printable printed printf printing println prints prio
prioritize priority private privilege privileges pro prob probability probable prior prioritize priority private privilege privileges pro prob probability probable
probably problem problematic problems proc procedural procedure procedures probably problem problematic problems proc procedural procedure procedures
proceed process processed processes processing processor processors procurement proceed process processed processes processing processor processors procurement
prod produce produced produces product production products prof profile profiler prod produce produced produces product production products prof profile profiler
...@@ -558,7 +558,7 @@ routines row rowcount rowid rowlock rownum rows rowscn rowsize roy royalty rpad ...@@ -558,7 +558,7 @@ routines row rowcount rowid rowlock rownum rows rowscn rowsize roy royalty rpad
rsaquo rsquo rss rtree rtrim ruby ruebezahl rule rules run rund rundll runnable rsaquo rsquo rss rtree rtrim ruby ruebezahl rule rules run rund rundll runnable
runner runners running runs runscript runtime rwd rws sabine safari safe safely runner runners running runs runscript runtime rwd rws sabine safari safe safely
safes safety said sainsbury salary sale sales saload salt salz sam same safes safety said sainsbury salary sale sales saload salt salz sam same
sameorigin samp sample samples sanity sans sastore sat satisfy saturday sauce sameorigin samp sample samples sanitize sanity sans sastore sat satisfy saturday sauce
sauerkraut sava save saved savepoint savepoints saves saving savings say saying sauerkraut sava save saved savepoint savepoints saves saving savings say saying
says sbquo scala scalability scalable scalar scale scaled scales scan scanned says sbquo scala scalability scalable scalar scale scaled scales scan scanned
scanner scanners scanning scans scapegoat scc schedule scheduler schem schema scanner scanners scanning scans scapegoat scc schedule scheduler schem schema
...@@ -591,7 +591,7 @@ sites situation situations six sixty size sized sizeof sizes sizing skeletons sk ...@@ -591,7 +591,7 @@ sites situation situations six sixty size sized sizeof sizes sizing skeletons sk
skiing skill skip skipped skipping skips sky slash slashdot slashes slave sleep skiing skill skip skipped skipping skips sky slash slashdot slashes slave sleep
sleeping sleeps slept slice sliced slight slightly slist slot slots slovensky sleeping sleeps slept slice sliced slight slightly slist slot slots slovensky
slow slowed slower slowest slowing slowly slows small smalldatetime smaller slow slowed slower slowest slowing slowly slows small smalldatetime smaller
smallest smallint smart smith smtp smtps smuggled snapshot snapshots snipped smallest smallint smart smith smol smtp smtps smuggled snake snapshot snapshots snipped
snippet snippets soap social socket sockets soerensen soffice soft software sold snippet snippets soap social socket sockets soerensen soffice soft software sold
sole solely solid solo solution solutions solve solved solves solving some sole solely solid solo solution solutions solve solved solves solving some
somebody somehow someone something sometime sometimes somewhat somewhere song somebody somehow someone something sometime sometimes somewhat somewhere song
...@@ -621,8 +621,8 @@ subscribe subselect subsequent subsequently subset substance substitute ...@@ -621,8 +621,8 @@ subscribe subselect subsequent subsequently subset substance substitute
substituted substitution substr substring substrings substructure subsystem substituted substitution substr substring substrings substructure subsystem
subtract subtracted subtracting subtraction subversion succeed succeeded succeeds subtract subtracted subtracting subtraction subversion succeed succeeded succeeds
success successful successfully succession such suddenly sudo sue sufficient success successful successfully succession such suddenly sudo sue sufficient
sufficiently suffix sugar suggest suggested suggestion suggestions suitable suite sufficiently suffix sugar suggest suggested suggestion suggestions suit suitable
suites sullivan sum summand summary summer summertime sums sun sunday sup supe suite suites sullivan sum summand summary summer summertime sums sun sunday sup supe
super superclass superfluous superinterfaces superior superseded supertable super superclass superfluous superinterfaces superior superseded supertable
superuser supplemental supplied supplier supply support supported supporter superuser supplemental supplied supplier supply support supported supporter
supporters supporting supports supposed suppress sure surname surrogate supporters supporting supports supposed suppress sure surname surrogate
...@@ -666,7 +666,7 @@ trick tricky tried tries trig trigger triggered triggers trigonometric trim ...@@ -666,7 +666,7 @@ trick tricky tried tries trig trigger triggered triggers trigonometric trim
trimmed trims trip trivial trouble true trunc truncate truncated truncates trimmed trims trip trivial trouble true trunc truncate truncated truncates
truncating truncation trunk trust trusted trx try trying tsi tsmsys tsv tucc truncating truncation trunk trust trusted trx try trying tsi tsmsys tsv tucc
tucker tuesday tune tunes tuning turkel turkish turn turned turns tutorial tweak tucker tuesday tune tunes tuning turkel turkish turn turned turns tutorial tweak
tweaking tweet twelve twice twitter two txt tymczak type typeof types typesafe tweaking tweet twelve twice twitter two txt tymczak type typed typeof types typesafe
typical typically typing typlen typname typo typos typtypmod tzd tzh tzm tzr typical typically typing typlen typname typo typos typtypmod tzd tzh tzm tzr
uacute uarr ubuntu ucase ucb ucirc ucs udt udts uffff ugly ugrave uid uint ujint uacute uarr ubuntu ucase ucb ucirc ucs udt udts uffff ugly ugrave uid uint ujint
ujlong ulimit uml umlaut umr unable unaligned unary unavailability unbound ujlong ulimit uml umlaut umr unable unaligned unary unavailability unbound
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论