diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java index bd5bf2e268..d4a5e9ff15 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java @@ -9,6 +9,7 @@ package org.hibernate.dialect; import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.sql.Types; +import java.util.Locale; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; @@ -510,7 +511,11 @@ public class DerbyDialect extends Dialect { case NATIVE: return "{fn timestampadd(sql_tsi_frac_second,mod(bigint(?2),1000000000),{fn timestampadd(sql_tsi_second,bigint((?2)/1000000000),?3)})}"; default: - return "{fn timestampadd(sql_tsi_?1,bigint(?2),?3)}"; + final String addExpression = "{fn timestampadd(sql_tsi_?1,bigint(?2),?3)}"; + // Since timestampadd will always produce a TIMESTAMP, we have to cast back to the intended type + return temporalType == TemporalType.TIMESTAMP + ? addExpression + : "cast(" + addExpression + " as " + temporalType.name().toLowerCase( Locale.ROOT ) + ")" ; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 14e91aff50..c153860bdd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -6010,6 +6010,21 @@ public abstract class BaseSqmToSqlAstConverter extends Base ); } + if ( sqmExpression instanceof SqmBinaryArithmetic ) { + final SqmBinaryArithmetic binaryArithmetic = (SqmBinaryArithmetic) sqmExpression; + if ( binaryArithmetic.getNodeType() == null ) { + final MappingModelExpressible lhs = determineValueMapping( binaryArithmetic.getLeftHandOperand() ); + final MappingModelExpressible rhs = determineValueMapping( binaryArithmetic.getRightHandOperand() ); + // This cast should be fine since the result of this will be a BasicType + return (MappingModelExpressible) getTypeConfiguration().resolveArithmeticType( + // These casts should be safe, since the only JdbcMapping is BasicType + // which also implements SqmExpressible + lhs == null ? null : (SqmExpressible) lhs.getSingleJdbcMapping(), + rhs == null ? null : (SqmExpressible) rhs.getSingleJdbcMapping() + ); + } + } + log.debugf( "Determining mapping-model type for generalized SqmExpression : %s", sqmExpression ); final SqmExpressible nodeType = sqmExpression.getNodeType(); if ( nodeType == null ) { @@ -6536,11 +6551,20 @@ public abstract class BaseSqmToSqlAstConverter extends Base SqmExpression leftOperand = expression.getLeftHandOperand(); SqmExpression rightOperand = expression.getRightHandOperand(); - boolean durationToRight = isDuration( rightOperand.getNodeType() ); - TypeConfiguration typeConfiguration = getCreationContext().getMappingMetamodel().getTypeConfiguration(); - TemporalType temporalTypeToLeft = typeConfiguration.getSqlTemporalType( leftOperand.getNodeType() ); - TemporalType temporalTypeToRight = typeConfiguration.getSqlTemporalType( rightOperand.getNodeType() ); - boolean temporalTypeSomewhereToLeft = adjustedTimestamp != null || temporalTypeToLeft != null; + // Need to infer the operand types here first to decide how to transform the expression + final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); + inferrableTypeAccessStack.push( () -> determineValueMapping( rightOperand, fromClauseIndex ) ); + final MappingModelExpressible leftOperandType = determineValueMapping( leftOperand ); + inferrableTypeAccessStack.pop(); + inferrableTypeAccessStack.push( () -> determineValueMapping( leftOperand, fromClauseIndex ) ); + final MappingModelExpressible rightOperandType = determineValueMapping( rightOperand ); + inferrableTypeAccessStack.pop(); + + final boolean durationToRight = isDuration( rightOperand.getNodeType() ); + final TypeConfiguration typeConfiguration = getCreationContext().getMappingMetamodel().getTypeConfiguration(); + final TemporalType temporalTypeToLeft = typeConfiguration.getSqlTemporalType( leftOperandType ); + final TemporalType temporalTypeToRight = typeConfiguration.getSqlTemporalType( rightOperandType ); + final boolean temporalTypeSomewhereToLeft = adjustedTimestamp != null || temporalTypeToLeft != null; if ( temporalTypeToLeft != null && durationToRight ) { if ( adjustmentScale != null || negativeAdjustment ) { @@ -6557,7 +6581,6 @@ public abstract class BaseSqmToSqlAstConverter extends Base } else { // Infer one operand type through the other - final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent(); inferrableTypeAccessStack.push( () -> determineValueMapping( rightOperand, fromClauseIndex ) ); final Expression lhs = toSqlExpression( leftOperand.accept( this ) ); inferrableTypeAccessStack.pop(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java index 5d79fa24cf..7f9cb74cd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java @@ -19,6 +19,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; +import java.time.OffsetTime; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Date; @@ -49,6 +50,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.SessionFactoryRegistry; import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.mapping.MappingModelExpressible; @@ -821,6 +823,17 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable { if ( type instanceof BasicValuedMapping ) { return getSqlTemporalType( ( (BasicValuedMapping) type ).getJdbcMapping().getJdbcType() ); } + else if ( type instanceof EmbeddableValuedModelPart ) { + // Handle the special embeddables for emulated offset/timezone handling + final Class javaTypeClass = ( (EmbeddableValuedModelPart) type ).getJavaType().getJavaTypeClass(); + if ( javaTypeClass == OffsetDateTime.class + || javaTypeClass == ZonedDateTime.class ) { + return TemporalType.TIMESTAMP; + } + else if ( javaTypeClass == OffsetTime.class ) { + return TemporalType.TIME; + } + } return null; }