diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java index 1e219bff12..37ac90a56b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/ArgumentTypesValidator.java @@ -7,9 +7,9 @@ package org.hibernate.query.sqm.produce.function; import java.lang.reflect.Type; -import java.sql.Types; import java.util.List; +import org.hibernate.Internal; import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMappingContainer; @@ -26,25 +26,10 @@ import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.type.BasicType; import org.hibernate.type.JavaObjectType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException; import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.spi.TypeConfiguration; -import static org.hibernate.type.SqlTypes.BIT; -import static org.hibernate.type.SqlTypes.BOOLEAN; -import static org.hibernate.type.SqlTypes.SMALLINT; -import static org.hibernate.type.SqlTypes.TINYINT; -import static org.hibernate.type.SqlTypes.UUID; -import static org.hibernate.type.SqlTypes.hasDatePart; -import static org.hibernate.type.SqlTypes.hasTimePart; -import static org.hibernate.type.SqlTypes.isCharacterOrClobType; -import static org.hibernate.type.SqlTypes.isCharacterType; -import static org.hibernate.type.SqlTypes.isEnumType; -import static org.hibernate.type.SqlTypes.isIntegral; -import static org.hibernate.type.SqlTypes.isNumericType; -import static org.hibernate.type.SqlTypes.isSpatialType; -import static org.hibernate.type.SqlTypes.isTemporalType; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.COMPARABLE; import static org.hibernate.type.descriptor.java.JavaTypeHelper.isUnknown; @@ -66,7 +51,7 @@ import static org.hibernate.type.descriptor.java.JavaTypeHelper.isUnknown; public class ArgumentTypesValidator implements ArgumentsValidator { // a JDBC type code of an enum when we don't know if it's mapped STRING or ORDINAL // this number has to be distinct from every code in SqlTypes! - private static final int ENUM_UNKNOWN_JDBC_TYPE = -101977; +// private static final int ENUM_UNKNOWN_JDBC_TYPE = -101977; final ArgumentsValidator delegate; private final FunctionParameterType[] types; @@ -93,13 +78,13 @@ public class ArgumentTypesValidator implements ArgumentsValidator { delegate.validate( arguments, functionName, typeConfiguration); int count = 0; for (SqmTypedNode argument : arguments) { - JdbcTypeIndicators indicators = typeConfiguration.getCurrentBaseSqlTypeIndicators(); +// JdbcTypeIndicators indicators = typeConfiguration.getCurrentBaseSqlTypeIndicators(); SqmExpressible nodeType = argument.getNodeType(); FunctionParameterType type = count < types.length ? types[count++] : types[types.length - 1]; if ( nodeType != null && type != FunctionParameterType.ANY ) { JavaType javaType = nodeType.getRelationalJavaType(); if (javaType != null) { - checkArgumentType( functionName, count, argument, indicators, type, javaType ); + checkArgumentType( functionName, count, argument, type, javaType ); } switch (type) { case TEMPORAL_UNIT: @@ -144,46 +129,44 @@ public class ArgumentTypesValidator implements ArgumentsValidator { String functionName, int count, SqmTypedNode argument, - JdbcTypeIndicators indicators, FunctionParameterType type, JavaType javaType) { if ( !isUnknown( javaType ) ) { DomainType domainType = argument.getExpressible().getSqmType(); if ( domainType instanceof JdbcMapping ) { - JdbcType jdbcType = ((JdbcMapping) domainType).getJdbcType(); + JdbcMapping jdbcMapping = (JdbcMapping) domainType; checkArgumentType( count, functionName, type, - jdbcType.getDefaultSqlTypeCode(), - jdbcType.getFriendlyName(), + jdbcMapping.getJdbcType(), javaType.getJavaTypeClass() ); } - else { - //TODO: this branch is now probably obsolete and can be deleted! - try { - checkArgumentType( - count, functionName, type, - getJdbcType( indicators, javaType ), - null, - javaType.getJavaTypeClass() - ); - } - catch (JdbcTypeRecommendationException e) { - // it's a converter or something like that, and we will check it later - } - } +// else { +// //TODO: this branch is now probably obsolete and can be deleted! +// try { +// checkArgumentType( +// count, functionName, type, +// getJdbcType( indicators, javaType ), +// null, +// javaType.getJavaTypeClass() +// ); +// } +// catch (JdbcTypeRecommendationException e) { +// // it's a converter or something like that, and we will check it later +// } +// } } } - private int getJdbcType(JdbcTypeIndicators indicators, JavaType javaType) { - if ( javaType.getJavaTypeClass().isEnum() ) { - // we can't tell if the enum is mapped STRING or ORDINAL - return ENUM_UNKNOWN_JDBC_TYPE; - } - else { - return javaType.getRecommendedJdbcType( indicators ).getDefaultSqlTypeCode(); - } - } +// private int getJdbcType(JdbcTypeIndicators indicators, JavaType javaType) { +// if ( javaType.getJavaTypeClass().isEnum() ) { +// // we can't tell if the enum is mapped STRING or ORDINAL +// return ENUM_UNKNOWN_JDBC_TYPE; +// } +// else { +// return javaType.getRecommendedJdbcType( indicators ).getDefaultSqlTypeCode(); +// } +// } /** * This is the final validation phase with the fully-typed SQL nodes. Note that these @@ -232,8 +215,7 @@ public class ArgumentTypesValidator implements ArgumentsValidator { paramNumber, functionName, type, - mapping.getJdbcType().getDefaultSqlTypeCode(), - mapping.getJdbcType().getFriendlyName(), + mapping.getJdbcType(), mapping.getJavaTypeDescriptor().getJavaType() ); } @@ -242,65 +224,49 @@ public class ArgumentTypesValidator implements ArgumentsValidator { } private static void checkArgumentType( - int paramNumber, String functionName, FunctionParameterType type, int code, String sqlType, Type javaType) { - switch (type) { + int paramNumber, String functionName, FunctionParameterType type, JdbcType jdbcType, Type javaType) { + if ( !isCompatible( type, jdbcType ) + // as a special case, we consider a binary column + // comparable when it is mapped by a Java UUID + && !( type == COMPARABLE && isBinaryUuid( jdbcType, javaType ) ) ) { + throwError( type, javaType, jdbcType.getFriendlyName(), functionName, paramNumber ); + } + } + + private static boolean isBinaryUuid(JdbcType jdbcType, Type javaType) { + return javaType == java.util.UUID.class + && jdbcType.isBinary(); + } + + @Internal + private static boolean isCompatible(FunctionParameterType type, JdbcType jdbcType) { + switch ( type ) { case COMPARABLE: - if ( !isCharacterType(code) && !isTemporalType(code) && !isNumericType(code) && !isEnumType( code ) - // both Java and the database consider UUIDs - // comparable, so go ahead and accept them - && code != UUID - // as a special case, we consider a binary column - // comparable when it is mapped by a Java UUID - && !( javaType == java.util.UUID.class && code == Types.BINARY ) ) { - throwError(type, javaType, sqlType, functionName, paramNumber); - } - break; + return jdbcType.isComparable(); case STRING: - if ( !isCharacterType(code) && !isEnumType(code) ) { - throwError(type, javaType, sqlType, functionName, paramNumber); - } - break; + return jdbcType.isStringLikeExcludingClob(); case STRING_OR_CLOB: - if ( !isCharacterOrClobType(code) ) { - throwError(type, javaType, sqlType, functionName, paramNumber); - } - break; + return jdbcType.isString(); // should it be isStringLike() case NUMERIC: - if ( !isNumericType(code) ) { - throwError(type, javaType, sqlType, functionName, paramNumber); - } - break; + return jdbcType.isNumber(); case INTEGER: - if ( !isIntegral(code) ) { - throwError(type, javaType, sqlType, functionName, paramNumber); - } - break; + return jdbcType.isInteger(); case BOOLEAN: - // ugh, need to be careful here, need to accept all the - // JDBC type codes that a Dialect might use for BOOLEAN - if ( code != BOOLEAN && code != BIT && code != TINYINT && code != SMALLINT ) { - throwError(type, javaType, sqlType, functionName, paramNumber); - } - break; + return jdbcType.isBoolean() + // some Dialects map Boolean to SMALLINT or TINYINT + // TODO: check with Dialect.getPreferredSqlTypeCodeForBoolean + || jdbcType.isSmallInteger(); case TEMPORAL: - if ( !isTemporalType(code) ) { - throwError(type, javaType, sqlType, functionName, paramNumber); - } - break; + return jdbcType.isTemporal(); case DATE: - if ( !hasDatePart(code) ) { - throwError(type, javaType, sqlType, functionName, paramNumber); - } - break; + return jdbcType.hasDatePart(); case TIME: - if ( !hasTimePart(code) ) { - throwError(type, javaType, sqlType, functionName, paramNumber); - } - break; + return jdbcType.hasTimePart(); case SPATIAL: - if ( !isSpatialType( code ) ) { - throwError( type, javaType, sqlType, functionName, paramNumber ); - } + return jdbcType.isSpatial(); + default: + // TODO: should we throw here? + return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/BooleanJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/BooleanJdbcType.java index 5f1ddff958..d76e6f94a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/BooleanJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/BooleanJdbcType.java @@ -15,7 +15,6 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterBoolean; import org.hibernate.type.spi.TypeConfiguration; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java index 0a71511f37..540c143cea 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java @@ -290,7 +290,7 @@ public interface JdbcType extends Serializable { default boolean isDuration() { final int ddlTypeCode = getDefaultSqlTypeCode(); return isDurationType( ddlTypeCode ) - || isIntervalType( ddlTypeCode ); + || isIntervalType( ddlTypeCode ); } default boolean isArray() { @@ -402,4 +402,50 @@ public interface JdbcType extends Serializable { default String getExtraCreateTableInfo(JavaType javaType, String columnName, String tableName, Database database) { return ""; } + + @Incubating + default boolean isComparable() { + final int code = getDefaultSqlTypeCode(); + return isCharacterType( code ) + || isTemporalType( code ) + || isNumericType( code ) + || isEnumType( code ) + // both Java and the SQL database consider + // that false < true is a sensible thing + || isBoolean() + // both Java and the database consider UUIDs + // comparable, so go ahead and accept them + || code == UUID; + } + + @Incubating + default boolean hasDatePart() { + return SqlTypes.hasDatePart( getDefaultSqlTypeCode() ); + } + + @Incubating + default boolean hasTimePart() { + return SqlTypes.hasTimePart( getDefaultSqlTypeCode() ); + } + + @Incubating + default boolean isStringLikeExcludingClob() { + final int code = getDefaultSqlTypeCode(); + return isCharacterType( code ) || isEnumType( code ); + } + + @Incubating + default boolean isSpatial() { + return isSpatialType( getDefaultSqlTypeCode() ); + } + + @Incubating + default boolean isBoolean() { + return getDefaultSqlTypeCode() == BOOLEAN; + } + + @Incubating + default boolean isSmallInteger() { + return isSmallOrTinyInt( getDefaultSqlTypeCode() ); + } }