Skip to content

Commit

Permalink
HHH-17693 Fix typecheck assertions for converted properties
Browse files Browse the repository at this point in the history
Also introduce a custom `DurationJdbcType`, mainly for validation purposes.
  • Loading branch information
mbladel committed Feb 8, 2024
1 parent 8a993f4 commit 50e6cb6
Show file tree
Hide file tree
Showing 10 changed files with 78 additions and 26 deletions.
Expand Up @@ -686,7 +686,7 @@ public void contributeType(CompositeUserType<?> type) {
);
}
else {
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.NUMERIC );
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.DURATION );
}

addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY );
Expand Down
Expand Up @@ -776,6 +776,7 @@ private void serializeBasicTo(
case SqlTypes.DOUBLE:
case SqlTypes.DECIMAL:
case SqlTypes.NUMERIC:
case SqlTypes.DURATION:
jdbcJavaType.appendEncodedString(
appender,
jdbcJavaType.unwrap(
Expand Down
Expand Up @@ -194,6 +194,7 @@ else if ( mappedType instanceof BasicType<?> ) {
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( '"' );
Expand Down
Expand Up @@ -564,6 +564,7 @@ private static void serializeValueTo(XMLAppender appender, SelectableMapping sel
case SqlTypes.DOUBLE:
case SqlTypes.DECIMAL:
case SqlTypes.NUMERIC:
case SqlTypes.DURATION:
jdbcJavaType.appendEncodedString(
appender,
jdbcJavaType.unwrap(
Expand Down
Expand Up @@ -546,7 +546,7 @@ public static synchronized int getPreferredSqlTypeCodeForDuration(StandardServic
return explicitSetting;
}

return SqlTypes.NUMERIC;
return SqlTypes.DURATION;
}

@Incubating
Expand Down
Expand Up @@ -507,39 +507,24 @@ private static boolean isNumberArray(SqmExpressible<?> expressible) {
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)"
);
}
}
Expand All @@ -548,11 +533,11 @@ public static void assertDuration(SqmExpression<?> expression) {
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)"
);
}
}
Expand Down
14 changes: 14 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/type/SqlTypes.java
Expand Up @@ -571,6 +571,13 @@ public class SqlTypes {
*/
public static final int ZONED_DATE_TIME = 3014;

/**
* 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

Expand Down Expand Up @@ -854,6 +861,13 @@ public static boolean isIntervalType(int typeCode) {
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}
Expand Down
@@ -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";
}
}
Expand Up @@ -287,6 +287,12 @@ default boolean isInterval() {
return isIntervalType( getDdlTypeCode() );
}

default boolean isDuration() {
final int ddlTypeCode = getDefaultSqlTypeCode();
return isDurationType( ddlTypeCode )
|| isIntervalType( ddlTypeCode );
}

default CastType getCastType() {
return getCastType( getDdlTypeCode() );
}
Expand Down
Expand Up @@ -18,6 +18,7 @@
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.InstantJdbcType;
import org.hibernate.type.descriptor.jdbc.IntegerJdbcType;
Expand Down Expand Up @@ -81,6 +82,7 @@ public static void prime(BaselineTarget target) {
target.addDescriptor( TimestampWithTimeZoneJdbcType.INSTANCE );
target.addDescriptor( TimeJdbcType.INSTANCE );
target.addDescriptor( TimeWithTimeZoneJdbcType.INSTANCE );
target.addDescriptor( DurationJdbcType.INSTANCE );

target.addDescriptor( BinaryJdbcType.INSTANCE );
target.addDescriptor( VarbinaryJdbcType.INSTANCE );
Expand Down

0 comments on commit 50e6cb6

Please sign in to comment.