diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/TimestampaddFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/TimestampaddFunction.java index d53cb2f44b..789860ab59 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/TimestampaddFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/TimestampaddFunction.java @@ -6,11 +6,12 @@ */ package org.hibernate.dialect.function; +import jakarta.persistence.TemporalType; import org.hibernate.dialect.Dialect; import org.hibernate.metamodel.mapping.BasicValuedMapping; -import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.query.ReturnableType; import org.hibernate.query.sqm.BinaryArithmeticOperator; +import org.hibernate.query.sqm.IntervalType; import org.hibernate.query.sqm.TemporalUnit; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression; @@ -28,10 +29,9 @@ import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.type.BasicType; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.TypeConfiguration; -import java.sql.Types; -import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -39,6 +39,8 @@ import static java.util.Arrays.asList; import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER; import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL; import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL_UNIT; +import static org.hibernate.type.spi.TypeConfiguration.getSqlIntervalType; +import static org.hibernate.type.spi.TypeConfiguration.getSqlTemporalType; /** * @author Gavin King @@ -73,89 +75,89 @@ public class TimestampaddFunction SqlAstTranslator walker) { final DurationUnit field = (DurationUnit) arguments.get( 0 ); + final Expression magnitude = (Expression) arguments.get(1); final Expression to = (Expression) arguments.get( 2 ); - final TemporalUnit unit; - if ( dialect.supportsFractionalTimestampArithmetic() ) { - unit = field.getUnit(); + + final TemporalUnit unit = bestTemporalUnit( magnitude, field ); + if ( unit != field.getUnit() ) { + renderWithUnitConversion( sqlAppender, arguments, walker, field, unit ); } else { - final Expression magnitude = (Expression) arguments.get( 1 ); - final JdbcMapping magnitudeJdbcMapping = magnitude.getExpressionType().getJdbcMappings().get( 0 ); - switch ( magnitudeJdbcMapping.getJdbcType().getJdbcTypeCode() ) { - case Types.INTEGER: - case Types.TINYINT: - case Types.SMALLINT: - case Types.BIGINT: - unit = field.getUnit(); - break; - default: - if ( magnitudeJdbcMapping.getMappedJavaType().getJavaTypeClass() == Duration.class ) { - // Don't scale durations - unit = field.getUnit(); - } - else { - // We need to multiply the magnitude by the conversion factor and cast to int - // Use second by default and nanosecond if we encounter fractional seconds - unit = field.getUnit() == TemporalUnit.SECOND - ? TemporalUnit.NANOSECOND - : TemporalUnit.SECOND; - } - break; - } + patternRenderer( to, magnitude, unit ).render( sqlAppender, arguments, walker ); } + } - final String pattern = dialect.timestampaddPattern( - unit, - TypeConfiguration.getSqlTemporalType( to.getExpressionType() ), - TypeConfiguration.getSqlIntervalType( - ( (Expression) arguments.get( 1 ) ).getExpressionType().getJdbcMappings().get( 0 ) + private PatternRenderer patternRenderer(Expression to, Expression interval, TemporalUnit unit) { + TemporalType temporalType = getSqlTemporalType( to.getExpressionType() ); + IntervalType intervalType = getSqlIntervalType( interval.getExpressionType().getJdbcMappings().get(0) ); + return new PatternRenderer( dialect.timestampaddPattern( unit, temporalType, intervalType ) ); + } + + private void renderWithUnitConversion( + SqlAppender sqlAppender, + List arguments, + SqlAstTranslator walker, + DurationUnit field, + TemporalUnit unit) { + final Expression magnitude = (Expression) arguments.get( 1 ); + final Expression to = (Expression) arguments.get( 2 ); + final Expression interval = (Expression) arguments.get(1); + + final List castArguments = new ArrayList<>( 2 ); + final List newArguments = new ArrayList<>( arguments ); + + castArguments.add( convertedArgument( field, unit, magnitude ) ); + castArguments.add( new CastTarget( integerType ) ); + newArguments.set( 0, new DurationUnit( unit, field.getExpressionType() ) ); + newArguments.set( + 1, + new SelfRenderingFunctionSqlAstExpression( + "cast", + castFunction, + castArguments, + integerType, + integerType ) - ); - final PatternRenderer renderer = new PatternRenderer( pattern ); - if ( unit != field.getUnit() ) { - final List castArguments = new ArrayList<>( 2 ); - final List newArguments = new ArrayList<>( arguments ); - final Expression magnitude = (Expression) arguments.get( 1 ); - final BasicValuedMapping expressionType = (BasicValuedMapping) magnitude.getExpressionType(); - final String conversionFactor = field.getUnit().conversionFactor( unit, dialect ); - if ( conversionFactor.isEmpty() ) { - castArguments.add( magnitude ); + ); + patternRenderer( to, interval, unit ).render( sqlAppender, newArguments, walker ); + } + + private Expression convertedArgument(DurationUnit field, TemporalUnit unit, Expression magnitude) { + final BasicValuedMapping expressionType = (BasicValuedMapping) magnitude.getExpressionType(); + final String conversionFactor = field.getUnit().conversionFactor( unit, dialect ); + return conversionFactor.isEmpty() + ? magnitude + : new BinaryArithmeticExpression( + magnitude, + conversionFactor.charAt(0) == '*' + ? BinaryArithmeticOperator.MULTIPLY + : BinaryArithmeticOperator.DIVIDE, + new QueryLiteral<>( + expressionType.getExpressibleJavaType().fromString( conversionFactor.substring(1) ), + expressionType + ), + expressionType + ); + } + + private TemporalUnit bestTemporalUnit(Expression magnitude, DurationUnit field) { + if ( dialect.supportsFractionalTimestampArithmetic() ) { + return field.getUnit(); + } + else { + final JdbcType jdbcType = magnitude.getExpressionType().getJdbcMappings().get( 0 ).getJdbcType(); + if ( jdbcType.isFloat() ) { + // Some databases don't support fractional seconds + // We need to multiply the magnitude by the conversion factor and cast to int + // Use second by default and nanosecond if we encounter fractional seconds + return field.getUnit() == TemporalUnit.SECOND + ? TemporalUnit.NANOSECOND + : TemporalUnit.SECOND; } else { - castArguments.add( - new BinaryArithmeticExpression( - magnitude, - conversionFactor.charAt( 0 ) == '*' - ? BinaryArithmeticOperator.MULTIPLY - : BinaryArithmeticOperator.DIVIDE, - new QueryLiteral<>( - expressionType.getExpressibleJavaType() - .fromString( conversionFactor.substring( 1 ) ), - expressionType - ), - expressionType - ) - ); + return field.getUnit(); } - - castArguments.add( new CastTarget( integerType ) ); - newArguments.set( 0, new DurationUnit( unit, field.getExpressionType() ) ); - newArguments.set( - 1, - new SelfRenderingFunctionSqlAstExpression( - "cast", - castFunction, - castArguments, - integerType, - integerType - ) - - ); - renderer.render( sqlAppender, newArguments, walker ); - } - else { - renderer.render( sqlAppender, arguments, walker ); } } 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 e4fd9689af..53e4d838a4 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 @@ -4683,7 +4683,7 @@ public abstract class BaseSqmToSqlAstConverter extends Base SqmExpression leftOperand = expression.getLeftHandOperand(); SqmExpression rightOperand = expression.getRightHandOperand(); - boolean durationToRight = TypeConfiguration.isDuration( rightOperand.getNodeType() ); + boolean durationToRight = isDuration( rightOperand.getNodeType() ); TypeConfiguration typeConfiguration = getCreationContext().getMappingMetamodel().getTypeConfiguration(); TemporalType temporalTypeToLeft = typeConfiguration.getSqlTemporalType( leftOperand.getNodeType() ); TemporalType temporalTypeToRight = typeConfiguration.getSqlTemporalType( rightOperand.getNodeType() );