HHH-18016 change ArgumentTypesValidator to delegate to the JdbcType

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-06-13 15:38:18 +02:00
parent 27bbdfc839
commit 505e64b19a
3 changed files with 111 additions and 100 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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() );
}
}