From 6138c76c7277a396eb4fe9558dda52925df36615 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 1 Feb 2024 10:17:16 +0100 Subject: [PATCH] HHH-17693 Fix typecheck assertions for converted properties Also introduce a custom `DurationJdbcType`, mainly for validation purposes. --- .../process/spi/MetadataBuildingProcess.java | 2 +- .../AbstractPostgreSQLStructJdbcType.java | 1 + .../org/hibernate/dialect/JsonHelper.java | 1 + .../java/org/hibernate/dialect/XmlHelper.java | 1 + .../util/config/ConfigurationHelper.java | 2 +- .../query/sqm/internal/TypecheckUtil.java | 33 ++++----------- .../java/org/hibernate/type/SqlTypes.java | 15 +++++++ .../descriptor/jdbc/DurationJdbcType.java | 42 +++++++++++++++++++ .../type/descriptor/jdbc/JdbcType.java | 6 +++ .../jdbc/internal/JdbcTypeBaseline.java | 2 + 10 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/DurationJdbcType.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java index a5353f12c0..d18696b988 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java @@ -686,7 +686,7 @@ public class MetadataBuildingProcess { ); } else { - addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.NUMERIC ); + addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.DURATION ); } addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java index 49bd79e360..ea433d40a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractPostgreSQLStructJdbcType.java @@ -776,6 +776,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements AggregateJdbcT case SqlTypes.DOUBLE: case SqlTypes.DECIMAL: case SqlTypes.NUMERIC: + case SqlTypes.DURATION: jdbcJavaType.appendEncodedString( appender, jdbcJavaType.unwrap( diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java index 91b5b564d4..fa6712b55f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java @@ -194,6 +194,7 @@ public class JsonHelper { break; case SqlTypes.DECIMAL: case SqlTypes.NUMERIC: + case SqlTypes.DURATION: case SqlTypes.UUID: // These types need to be serialized as JSON string, but don't have a need for escaping appender.append( '"' ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java index 69e8e9fec2..cd68779d7e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/XmlHelper.java @@ -564,6 +564,7 @@ public class XmlHelper { case SqlTypes.DOUBLE: case SqlTypes.DECIMAL: case SqlTypes.NUMERIC: + case SqlTypes.DURATION: jdbcJavaType.appendEncodedString( appender, jdbcJavaType.unwrap( diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java index 979a52271c..cd287a3cd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java @@ -546,7 +546,7 @@ public final class ConfigurationHelper { return explicitSetting; } - return SqlTypes.NUMERIC; + return SqlTypes.DURATION; } @Incubating diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java index dd84d30823..359701042b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/TypecheckUtil.java @@ -507,39 +507,24 @@ public class TypecheckUtil { public static void assertString(SqmExpression expression) { final SqmExpressible nodeType = expression.getNodeType(); if ( nodeType != null ) { - final Class javaType = nodeType.getExpressibleJavaType().getJavaTypeClass(); - if ( javaType != String.class && javaType != char[].class ) { + final DomainType domainType = nodeType.getSqmType(); + if ( !( domainType instanceof JdbcMapping ) || !( (JdbcMapping) domainType ).getJdbcType().isStringLike() ) { throw new SemanticException( "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) { final SqmExpressible nodeType = expression.getNodeType(); if ( nodeType != null ) { - final Class javaType = nodeType.getExpressibleJavaType().getJavaTypeClass(); - if ( !TemporalAmount.class.isAssignableFrom( javaType ) ) { + final DomainType domainType = nodeType.getSqmType(); + if ( !( domainType instanceof JdbcMapping ) || !( (JdbcMapping) domainType ).getJdbcType().isDuration() ) { throw new SemanticException( "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) { final SqmExpressible nodeType = expression.getNodeType(); if ( nodeType != null ) { - final Class javaType = nodeType.getExpressibleJavaType().getJavaTypeClass(); - if ( !Number.class.isAssignableFrom( javaType ) ) { + final DomainType domainType = nodeType.getSqmType(); + if ( !( domainType instanceof JdbcMapping ) || !( (JdbcMapping) domainType ).getJdbcType().isNumber() ) { throw new SemanticException( "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)" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java b/hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java index 6eaff7dc81..6ca2212e55 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java +++ b/hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java @@ -498,6 +498,14 @@ public class SqlTypes { */ 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 /** @@ -779,6 +787,13 @@ public class SqlTypes { 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? * @param typeCode a JDBC type code from {@link Types} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/DurationJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/DurationJdbcType.java new file mode 100644 index 0000000000..9342b9b528 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/DurationJdbcType.java @@ -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"; + } +} 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 dd03a423ac..e2b26b3aba 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 @@ -287,6 +287,12 @@ public interface JdbcType extends Serializable { return isIntervalType( getDdlTypeCode() ); } + default boolean isDuration() { + final int ddlTypeCode = getDefaultSqlTypeCode(); + return isDurationType( ddlTypeCode ) + || isIntervalType( ddlTypeCode ); + } + default CastType getCastType() { return getCastType( getDdlTypeCode() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/internal/JdbcTypeBaseline.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/internal/JdbcTypeBaseline.java index 2a276c66e7..b606122dd1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/internal/JdbcTypeBaseline.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/internal/JdbcTypeBaseline.java @@ -18,6 +18,7 @@ import org.hibernate.type.descriptor.jdbc.ClobJdbcType; import org.hibernate.type.descriptor.jdbc.DateJdbcType; import org.hibernate.type.descriptor.jdbc.DecimalJdbcType; 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.IntegerJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; @@ -66,6 +67,7 @@ public class JdbcTypeBaseline { target.addDescriptor( TimestampWithTimeZoneJdbcType.INSTANCE ); target.addDescriptor( TimeJdbcType.INSTANCE ); target.addDescriptor( TimeWithTimeZoneJdbcType.INSTANCE ); + target.addDescriptor( DurationJdbcType.INSTANCE ); target.addDescriptor( BinaryJdbcType.INSTANCE ); target.addDescriptor( VarbinaryJdbcType.INSTANCE );