split out IntegralTimestampaddFunction

This commit is contained in:
Gavin King 2022-02-07 15:24:55 +01:00
parent 3e97fe39a7
commit addc3ea4c2
6 changed files with 188 additions and 109 deletions

View File

@ -14,6 +14,7 @@ import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.IntegralTimestampaddFunction;
import org.hibernate.dialect.identity.HANAIdentityColumnSupport;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.pagination.LimitHandler;
@ -283,6 +284,7 @@ public abstract class AbstractHANADialect extends Dialect {
).setArgumentListSignature("(pattern, string[, start])");
CommonFunctionFactory functionFactory = new CommonFunctionFactory(queryEngine);
functionFactory.ceiling_ceil();
functionFactory.concat_pipeOperator();
functionFactory.trim2();
@ -312,6 +314,9 @@ public abstract class AbstractHANADialect extends Dialect {
functionFactory.listagg_stringAgg( "varchar" );
functionFactory.inverseDistributionOrderedSetAggregates();
functionFactory.hypotheticalOrderedSetAggregates();
queryEngine.getSqmFunctionRegistry().register( "timestampadd",
new IntegralTimestampaddFunction( this, queryEngine.getTypeConfiguration() ) );
}
@Override
@ -1659,7 +1664,7 @@ public abstract class AbstractHANADialect extends Dialect {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
JdbcType descriptor = BlobJdbcType.BLOB_BINDING;
if ( byte[].class.isInstance( value ) ) {
if ( value instanceof byte[] ) {
// performance shortcut for binding BLOB data in byte[] format
descriptor = BlobJdbcType.PRIMITIVE_ARRAY_BINDING;
}

View File

@ -841,18 +841,21 @@ public abstract class Dialect implements ConversionContext {
//only some databases support the ANSI SQL-style position() function, so
//define it here as an alias for locate()
queryEngine.getSqmFunctionRegistry().register( "position", new LocatePositionEmulation( queryEngine.getTypeConfiguration() ) );
queryEngine.getSqmFunctionRegistry().register( "position",
new LocatePositionEmulation( queryEngine.getTypeConfiguration() ) );
//very few databases support ANSI-style overlay() function, so emulate
//it here in terms of either insert() or concat()/substring()
queryEngine.getSqmFunctionRegistry().register( "overlay", new InsertSubstringOverlayEmulation( queryEngine.getTypeConfiguration(), false ) );
queryEngine.getSqmFunctionRegistry().register( "overlay",
new InsertSubstringOverlayEmulation( queryEngine.getTypeConfiguration(), false ) );
//ANSI SQL trim() function is supported on almost all of the databases
//we care about, but on some it must be emulated using ltrim(), rtrim(),
//and replace()
queryEngine.getSqmFunctionRegistry().register( "trim", new TrimFunction( this, queryEngine.getTypeConfiguration() ) );
queryEngine.getSqmFunctionRegistry().register( "trim",
new TrimFunction( this, queryEngine.getTypeConfiguration() ) );
//ANSI SQL cast() function is supported on the databases we care most
//about but in certain cases it doesn't allow some useful typecasts,
@ -879,7 +882,8 @@ public abstract class Dialect implements ConversionContext {
//additional non-standard temporal field types, which must be emulated in
//a very dialect-specific way
queryEngine.getSqmFunctionRegistry().register( "extract", new ExtractFunction( this ) );
queryEngine.getSqmFunctionRegistry().register( "extract",
new ExtractFunction( this ) );
//comparison functions supported on every known database
@ -888,7 +892,8 @@ public abstract class Dialect implements ConversionContext {
//two-argument synonym for coalesce() supported on most but not every
//database, so define it here as an alias for coalesce(arg1,arg2)
queryEngine.getSqmFunctionRegistry().register( "ifnull", new CoalesceIfnullEmulation() );
queryEngine.getSqmFunctionRegistry().register( "ifnull",
new CoalesceIfnullEmulation() );
//rpad() and pad() are supported on almost every database, and emulated
//where not supported, but they're not considered "standard" ... instead
@ -898,12 +903,14 @@ public abstract class Dialect implements ConversionContext {
//pad() is a function we've designed to look like ANSI trim()
queryEngine.getSqmFunctionRegistry().register( "pad", new LpadRpadPadEmulation( queryEngine.getTypeConfiguration() ) );
queryEngine.getSqmFunctionRegistry().register( "pad",
new LpadRpadPadEmulation( queryEngine.getTypeConfiguration() ) );
//legacy Hibernate convenience function for casting to string, defined
//here as an alias for cast(arg as String)
queryEngine.getSqmFunctionRegistry().register( "str", new CastStrEmulation( queryEngine.getTypeConfiguration() ) );
queryEngine.getSqmFunctionRegistry().register( "str",
new CastStrEmulation( queryEngine.getTypeConfiguration() ) );
//format() function for datetimes, emulated on many databases using the
//Oracle-style to_char() function, and on others using their native
@ -914,8 +921,10 @@ public abstract class Dialect implements ConversionContext {
//timestampadd()/timestampdiff() delegated back to the Dialect itself
//since there is a great variety of different ways to emulate them
queryEngine.getSqmFunctionRegistry().register( "timestampadd", new TimestampaddFunction( this, queryEngine.getTypeConfiguration() ) );
queryEngine.getSqmFunctionRegistry().register( "timestampdiff", new TimestampdiffFunction( this, queryEngine.getTypeConfiguration() ) );
queryEngine.getSqmFunctionRegistry().register( "timestampadd",
new TimestampaddFunction( this ) );
queryEngine.getSqmFunctionRegistry().register( "timestampdiff",
new TimestampdiffFunction( this, queryEngine.getTypeConfiguration() ) );
queryEngine.getSqmFunctionRegistry().registerAlternateKey( "dateadd", "timestampadd" );
queryEngine.getSqmFunctionRegistry().registerAlternateKey( "datediff", "timestampdiff" );

View File

@ -9,6 +9,7 @@ package org.hibernate.dialect;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.IntegralTimestampaddFunction;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
@ -221,6 +222,9 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
functionFactory.replace_strReplace();
functionFactory.everyAny_sumCaseCase();
functionFactory.bitLength_pattern( "datalength(?1) * 8" );
queryEngine.getSqmFunctionRegistry().register( "timestampadd",
new IntegralTimestampaddFunction( this, queryEngine.getTypeConfiguration() ) );
}
@Override

View File

@ -0,0 +1,135 @@
/*
* 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.dialect.function;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.query.sqm.BinaryArithmeticOperator;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.DurationUnit;
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.util.Arrays;
import java.util.List;
/**
* Used in place of {@link TimestampaddFunction} for databases which don't
* support fractional seconds in the {@code timestampadd()} function.
*
* @author Christian Beikov
*/
public class IntegralTimestampaddFunction
extends TimestampaddFunction {
private final Dialect dialect;
private final CastFunction castFunction;
private final BasicType<Integer> integerType;
public IntegralTimestampaddFunction(Dialect dialect, TypeConfiguration typeConfiguration) {
super( dialect );
this.dialect = dialect;
this.integerType = typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.INTEGER );
//This is kinda wrong, we're supposed to use findFunctionDescriptor("cast"), not instantiate CastFunction
//However, since no Dialects currently override the cast() function, it's OK for now
this.castFunction = new CastFunction( dialect, dialect.getPreferredSqlTypeCodeForBoolean() );
}
@Override
public void render(
SqlAppender sqlAppender,
List<? extends SqlAstNode> arguments,
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 = bestTemporalUnit( magnitude, field );
if ( unit != field.getUnit() ) {
renderWithUnitConversion( sqlAppender, magnitude, to, walker, field, unit );
}
else {
super.render( sqlAppender, arguments, walker );
}
}
private void renderWithUnitConversion(
SqlAppender sqlAppender,
Expression magnitude,
Expression to,
SqlAstTranslator<?> walker,
DurationUnit field,
TemporalUnit unit) {
patternRenderer( unit, magnitude, to )
.render( sqlAppender, convertedArguments( field, unit, magnitude, to ), walker );
}
private List<SqlAstNode> convertedArguments(
DurationUnit field,
TemporalUnit unit,
Expression magnitude,
Expression to) {
return Arrays.asList(
new DurationUnit( unit, field.getExpressionType() ),
new SelfRenderingFunctionSqlAstExpression(
"cast",
castFunction,
Arrays.asList(
convertedArgument(field, unit, magnitude),
new CastTarget( integerType )
),
integerType,
integerType
),
to
);
}
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) {
final JdbcType jdbcType = magnitude.getExpressionType().getJdbcMappings().get( 0 ).getJdbcType();
if ( jdbcType.isFloat() ) {
// 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 {
return field.getUnit();
}
}
}

View File

@ -8,9 +8,7 @@ package org.hibernate.dialect.function;
import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
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;
@ -22,17 +20,9 @@ import org.hibernate.query.sqm.produce.function.internal.PatternRenderer;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
import org.hibernate.sql.ast.tree.expression.CastTarget;
import org.hibernate.sql.ast.tree.expression.DurationUnit;
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.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
@ -43,16 +33,20 @@ import static org.hibernate.type.spi.TypeConfiguration.getSqlIntervalType;
import static org.hibernate.type.spi.TypeConfiguration.getSqlTemporalType;
/**
* The {@code timestampadd()} or {@code dateadd()} function has a funny
* syntax which accepts a {@link TemporalUnit} as the first argument,
* and the actual set of accepted units varies widely. This class uses
* {@link Dialect#timestampaddPattern(TemporalUnit, TemporalType, IntervalType)}
* to abstract these differences.
*
* @author Gavin King
*/
public class TimestampaddFunction
extends AbstractSqmSelfRenderingFunctionDescriptor {
private final Dialect dialect;
private final CastFunction castFunction;
private final BasicType<Integer> integerType;
public TimestampaddFunction(Dialect dialect, TypeConfiguration typeConfiguration) {
public TimestampaddFunction(Dialect dialect) {
super(
"timestampadd",
new ArgumentTypesValidator(
@ -62,10 +56,6 @@ public class TimestampaddFunction
StandardFunctionReturnTypeResolvers.useArgType( 3 )
);
this.dialect = dialect;
this.integerType = typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.INTEGER );
//This is kinda wrong, we're supposed to use findFunctionDescriptor("cast"), not instantiate CastFunction
//However, since no Dialects currently override the cast() function, it's OK for now
this.castFunction = new CastFunction( dialect, dialect.getPreferredSqlTypeCodeForBoolean() );
}
@Override
@ -78,89 +68,15 @@ public class TimestampaddFunction
final Expression magnitude = (Expression) arguments.get(1);
final Expression to = (Expression) arguments.get( 2 );
final TemporalUnit unit = bestTemporalUnit( magnitude, field );
if ( unit != field.getUnit() ) {
renderWithUnitConversion( sqlAppender, arguments, walker, field, unit );
}
else {
patternRenderer( to, magnitude, unit ).render( sqlAppender, arguments, walker );
}
patternRenderer( field.getUnit(), magnitude, to ).render( sqlAppender, arguments, walker );
}
private PatternRenderer patternRenderer(Expression to, Expression interval, TemporalUnit unit) {
PatternRenderer patternRenderer(TemporalUnit unit, Expression interval, Expression to) {
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
)
);
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 {
return field.getUnit();
}
}
}
// @Override
// protected <T> SelfRenderingSqlFunctionExpression<T> generateSqmFunctionExpression(
// List<SqmTypedNode<?>> arguments,

View File

@ -8,8 +8,10 @@ package org.hibernate.dialect.function;
import java.util.List;
import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect;
import org.hibernate.query.ReturnableType;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
@ -27,8 +29,15 @@ import org.hibernate.type.spi.TypeConfiguration;
import static java.util.Arrays.asList;
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.getSqlTemporalType;
/**
* The {@code timestampdiff()} or {@code datediff()} function has a funny
* syntax which accepts a {@link TemporalUnit} as the first argument, and
* the actual set of accepted units varies widely. This class uses
* {@link Dialect#timestampdiffPattern(TemporalUnit, TemporalType, TemporalType)}
* to abstract these differences.
*
* @author Gavin King
*/
public class TimestampdiffFunction
@ -59,13 +68,14 @@ public class TimestampdiffFunction
final DurationUnit field = (DurationUnit) arguments.get( 0 );
final Expression from = (Expression) arguments.get( 1 );
final Expression to = (Expression) arguments.get( 2 );
final String pattern = dialect.timestampdiffPattern(
field.getUnit(),
TypeConfiguration.getSqlTemporalType( from.getExpressionType() ),
TypeConfiguration.getSqlTemporalType( to.getExpressionType() )
);
new PatternRenderer( pattern ).render( sqlAppender, arguments, walker );
patternRenderer( field.getUnit(), from, to ).render( sqlAppender, arguments, walker );
}
private PatternRenderer patternRenderer(TemporalUnit unit, Expression from, Expression to) {
TemporalType lhsTemporalType = getSqlTemporalType( from.getExpressionType() );
TemporalType rhsTemporalType = getSqlTemporalType( to.getExpressionType() );
return new PatternRenderer( dialect.timestampdiffPattern( unit, lhsTemporalType, rhsTemporalType ) );
}
// @Override