HHH-17693 Fix typecheck assertions for converted properties

Also introduce a custom `DurationJdbcType`, mainly for validation purposes.
This commit is contained in:
Marco Belladelli 2024-02-01 10:17:16 +01:00
parent 658e9bc215
commit 6138c76c72
No known key found for this signature in database
GPG Key ID: D1D0C3030AE3AA35
10 changed files with 79 additions and 26 deletions

View File

@ -686,7 +686,7 @@ public class MetadataBuildingProcess {
); );
} }
else { else {
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.NUMERIC ); addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.DURATION );
} }
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY ); addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY );

View File

@ -776,6 +776,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements AggregateJdbcT
case SqlTypes.DOUBLE: case SqlTypes.DOUBLE:
case SqlTypes.DECIMAL: case SqlTypes.DECIMAL:
case SqlTypes.NUMERIC: case SqlTypes.NUMERIC:
case SqlTypes.DURATION:
jdbcJavaType.appendEncodedString( jdbcJavaType.appendEncodedString(
appender, appender,
jdbcJavaType.unwrap( jdbcJavaType.unwrap(

View File

@ -194,6 +194,7 @@ public class JsonHelper {
break; break;
case SqlTypes.DECIMAL: case SqlTypes.DECIMAL:
case SqlTypes.NUMERIC: case SqlTypes.NUMERIC:
case SqlTypes.DURATION:
case SqlTypes.UUID: case SqlTypes.UUID:
// These types need to be serialized as JSON string, but don't have a need for escaping // These types need to be serialized as JSON string, but don't have a need for escaping
appender.append( '"' ); appender.append( '"' );

View File

@ -564,6 +564,7 @@ public class XmlHelper {
case SqlTypes.DOUBLE: case SqlTypes.DOUBLE:
case SqlTypes.DECIMAL: case SqlTypes.DECIMAL:
case SqlTypes.NUMERIC: case SqlTypes.NUMERIC:
case SqlTypes.DURATION:
jdbcJavaType.appendEncodedString( jdbcJavaType.appendEncodedString(
appender, appender,
jdbcJavaType.unwrap( jdbcJavaType.unwrap(

View File

@ -546,7 +546,7 @@ public final class ConfigurationHelper {
return explicitSetting; return explicitSetting;
} }
return SqlTypes.NUMERIC; return SqlTypes.DURATION;
} }
@Incubating @Incubating

View File

@ -507,39 +507,24 @@ public class TypecheckUtil {
public static void assertString(SqmExpression<?> expression) { public static void assertString(SqmExpression<?> expression) {
final SqmExpressible<?> nodeType = expression.getNodeType(); final SqmExpressible<?> nodeType = expression.getNodeType();
if ( nodeType != null ) { if ( nodeType != null ) {
final Class<?> javaType = nodeType.getExpressibleJavaType().getJavaTypeClass(); final DomainType<?> domainType = nodeType.getSqmType();
if ( javaType != String.class && javaType != char[].class ) { if ( !( domainType instanceof JdbcMapping ) || !( (JdbcMapping) domainType ).getJdbcType().isStringLike() ) {
throw new SemanticException( throw new SemanticException(
"Operand of 'like' is of type '" + nodeType.getTypeName() + "Operand of 'like' is of type '" + nodeType.getTypeName() +
"' which is not a string (it is not an instance of 'java.lang.String' or 'char[]')" "' which is not a string (its JDBC type code is not string-like)"
); );
} }
} }
} }
// public static void assertNumeric(SqmExpression<?> expression, BinaryArithmeticOperator op) {
// final SqmExpressible<?> nodeType = expression.getNodeType();
// if ( nodeType != null ) {
// final Class<?> javaType = nodeType.getExpressibleJavaType().getJavaTypeClass();
// if ( !Number.class.isAssignableFrom( javaType )
// && !Temporal.class.isAssignableFrom( javaType )
// && !TemporalAmount.class.isAssignableFrom( javaType )
// && !java.util.Date.class.isAssignableFrom( javaType ) ) {
// throw new SemanticException( "Operand of " + op.getOperatorSqlText()
// + " is of type '" + nodeType.getTypeName() + "' which is not a numeric type"
// + " (it is not an instance of 'java.lang.Number', 'java.time.Temporal', or 'java.time.TemporalAmount')" );
// }
// }
// }
public static void assertDuration(SqmExpression<?> expression) { public static void assertDuration(SqmExpression<?> expression) {
final SqmExpressible<?> nodeType = expression.getNodeType(); final SqmExpressible<?> nodeType = expression.getNodeType();
if ( nodeType != null ) { if ( nodeType != null ) {
final Class<?> javaType = nodeType.getExpressibleJavaType().getJavaTypeClass(); final DomainType<?> domainType = nodeType.getSqmType();
if ( !TemporalAmount.class.isAssignableFrom( javaType ) ) { if ( !( domainType instanceof JdbcMapping ) || !( (JdbcMapping) domainType ).getJdbcType().isDuration() ) {
throw new SemanticException( throw new SemanticException(
"Operand of 'by' is of type '" + nodeType.getTypeName() + "Operand of 'by' is of type '" + nodeType.getTypeName() +
"' which is not a duration (it is not an instance of 'java.time.TemporalAmount')" "' which is not a duration (its JDBC type code is not duration-like)"
); );
} }
} }
@ -548,11 +533,11 @@ public class TypecheckUtil {
public static void assertNumeric(SqmExpression<?> expression, UnaryArithmeticOperator op) { public static void assertNumeric(SqmExpression<?> expression, UnaryArithmeticOperator op) {
final SqmExpressible<?> nodeType = expression.getNodeType(); final SqmExpressible<?> nodeType = expression.getNodeType();
if ( nodeType != null ) { if ( nodeType != null ) {
final Class<?> javaType = nodeType.getExpressibleJavaType().getJavaTypeClass(); final DomainType<?> domainType = nodeType.getSqmType();
if ( !Number.class.isAssignableFrom( javaType ) ) { if ( !( domainType instanceof JdbcMapping ) || !( (JdbcMapping) domainType ).getJdbcType().isNumber() ) {
throw new SemanticException( throw new SemanticException(
"Operand of " + op.getOperatorChar() + " is of type '" + nodeType.getTypeName() + "Operand of " + op.getOperatorChar() + " is of type '" + nodeType.getTypeName() +
"' which is not a numeric type (it is not an instance of 'java.lang.Number')" "' which is not a numeric type (its JDBC type code is not numeric)"
); );
} }
} }

View File

@ -498,6 +498,14 @@ public class SqlTypes {
*/ */
public static final int TIME_UTC = 3007; public static final int TIME_UTC = 3007;
/**
* A type code representing a "virtual mapping" of {@linkplain java.time.Duration}.
*
* @see Types#NUMERIC
* @see org.hibernate.type.descriptor.jdbc.DurationJdbcType
*/
public static final int DURATION = 3015;
// Interval types // Interval types
/** /**
@ -779,6 +787,13 @@ public class SqlTypes {
return typeCode == INTERVAL_SECOND; return typeCode == INTERVAL_SECOND;
} }
/**
* Does the given typecode represent a {@code duration} type?
*/
public static boolean isDurationType(int typeCode) {
return typeCode == DURATION;
}
/** /**
* Does the given typecode represent a SQL date or timestamp type? * Does the given typecode represent a SQL date or timestamp type?
* @param typeCode a JDBC type code from {@link Types} * @param typeCode a JDBC type code from {@link Types}

View File

@ -0,0 +1,42 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.type.descriptor.jdbc;
import java.sql.Types;
import java.time.Duration;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
/**
* Descriptor for {@link java.time.Duration}.
*
* @author Marco Belladelli
*/
public class DurationJdbcType extends NumericJdbcType {
public static final DurationJdbcType INSTANCE = new DurationJdbcType();
@Override
public int getDdlTypeCode() {
return Types.NUMERIC;
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.DURATION;
}
@Override
public String getFriendlyName() {
return "DURATION";
}
@Override
public String toString() {
return "DurationJdbcType";
}
}

View File

@ -287,6 +287,12 @@ public interface JdbcType extends Serializable {
return isIntervalType( getDdlTypeCode() ); return isIntervalType( getDdlTypeCode() );
} }
default boolean isDuration() {
final int ddlTypeCode = getDefaultSqlTypeCode();
return isDurationType( ddlTypeCode )
|| isIntervalType( ddlTypeCode );
}
default CastType getCastType() { default CastType getCastType() {
return getCastType( getDdlTypeCode() ); return getCastType( getDdlTypeCode() );
} }

View File

@ -18,6 +18,7 @@ import org.hibernate.type.descriptor.jdbc.ClobJdbcType;
import org.hibernate.type.descriptor.jdbc.DateJdbcType; import org.hibernate.type.descriptor.jdbc.DateJdbcType;
import org.hibernate.type.descriptor.jdbc.DecimalJdbcType; import org.hibernate.type.descriptor.jdbc.DecimalJdbcType;
import org.hibernate.type.descriptor.jdbc.DoubleJdbcType; import org.hibernate.type.descriptor.jdbc.DoubleJdbcType;
import org.hibernate.type.descriptor.jdbc.DurationJdbcType;
import org.hibernate.type.descriptor.jdbc.FloatJdbcType; import org.hibernate.type.descriptor.jdbc.FloatJdbcType;
import org.hibernate.type.descriptor.jdbc.IntegerJdbcType; import org.hibernate.type.descriptor.jdbc.IntegerJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
@ -66,6 +67,7 @@ public class JdbcTypeBaseline {
target.addDescriptor( TimestampWithTimeZoneJdbcType.INSTANCE ); target.addDescriptor( TimestampWithTimeZoneJdbcType.INSTANCE );
target.addDescriptor( TimeJdbcType.INSTANCE ); target.addDescriptor( TimeJdbcType.INSTANCE );
target.addDescriptor( TimeWithTimeZoneJdbcType.INSTANCE ); target.addDescriptor( TimeWithTimeZoneJdbcType.INSTANCE );
target.addDescriptor( DurationJdbcType.INSTANCE );
target.addDescriptor( BinaryJdbcType.INSTANCE ); target.addDescriptor( BinaryJdbcType.INSTANCE );
target.addDescriptor( VarbinaryJdbcType.INSTANCE ); target.addDescriptor( VarbinaryJdbcType.INSTANCE );