make TimestampaddFunction understandable using Extract Method

This commit is contained in:
Gavin King 2022-02-07 14:51:23 +01:00
parent 52c7f61815
commit 3e97fe39a7
2 changed files with 79 additions and 77 deletions

View File

@ -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<? extends SqlAstNode> 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<SqlAstNode> castArguments = new ArrayList<>( 2 );
final List<SqlAstNode> 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<SqlAstNode> castArguments = new ArrayList<>( 2 );
final List<SqlAstNode> 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 );
}
}

View File

@ -4683,7 +4683,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> 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() );