HHH-16185 Implement portable date_trunc function emulation and tests
This commit is contained in:
parent
3864b71696
commit
821e31b481
|
@ -201,6 +201,7 @@ public class HSQLLegacyDialect extends Dialect {
|
|||
functionFactory.rand();
|
||||
functionFactory.trunc();
|
||||
// functionFactory.truncate();
|
||||
functionFactory.dateTrunc_trunc();
|
||||
functionFactory.pi();
|
||||
functionFactory.soundex();
|
||||
functionFactory.reverse();
|
||||
|
|
|
@ -562,6 +562,7 @@ public class MySQLLegacyDialect extends Dialect {
|
|||
functionFactory.position();
|
||||
functionFactory.nowCurdateCurtime();
|
||||
functionFactory.trunc_truncate();
|
||||
functionFactory.dateTrunc_format( "str_to_date", false );
|
||||
functionFactory.insert();
|
||||
functionFactory.bitandorxornot_operator();
|
||||
functionFactory.bitAndOr();
|
||||
|
|
|
@ -366,6 +366,10 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect {
|
|||
}
|
||||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.leastGreatest();
|
||||
functionFactory.dateTrunc_datetrunc();
|
||||
}
|
||||
else {
|
||||
functionFactory.dateTrunc_format( "convert", false );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -218,6 +218,7 @@ public class SybaseLegacyDialect extends AbstractTransactSQLDialect {
|
|||
functionFactory.varPopSamp();
|
||||
functionFactory.trunc_floorPower();
|
||||
functionFactory.round_round();
|
||||
functionFactory.dateTrunc_format( "convert", true );
|
||||
|
||||
// For SQL-Server we need to cast certain arguments to varchar(16384) to be able to concat them
|
||||
functionContributions.getFunctionRegistry().register(
|
||||
|
|
|
@ -398,6 +398,7 @@ public abstract class AbstractHANADialect extends Dialect {
|
|||
functionFactory.sinh();
|
||||
functionFactory.tanh();
|
||||
functionFactory.trunc_roundMode();
|
||||
functionFactory.dateTrunc_format( "to_date", false );
|
||||
functionFactory.log10_log();
|
||||
functionFactory.log();
|
||||
functionFactory.bitand();
|
||||
|
|
|
@ -142,6 +142,7 @@ public class HSQLDialect extends Dialect {
|
|||
functionFactory.rand();
|
||||
functionFactory.trunc();
|
||||
// functionFactory.truncate();
|
||||
functionFactory.dateTrunc_trunc();
|
||||
functionFactory.pi();
|
||||
functionFactory.soundex();
|
||||
functionFactory.reverse();
|
||||
|
|
|
@ -561,6 +561,7 @@ public class MySQLDialect extends Dialect {
|
|||
functionFactory.position();
|
||||
functionFactory.nowCurdateCurtime();
|
||||
functionFactory.trunc_truncate();
|
||||
functionFactory.dateTrunc_format( "str_to_date", false );
|
||||
functionFactory.insert();
|
||||
functionFactory.bitandorxornot_operator();
|
||||
functionFactory.bitAndOr();
|
||||
|
|
|
@ -370,6 +370,10 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
|||
}
|
||||
if ( getVersion().isSameOrAfter( 16 ) ) {
|
||||
functionFactory.leastGreatest();
|
||||
functionFactory.dateTrunc_datetrunc();
|
||||
}
|
||||
else {
|
||||
functionFactory.dateTrunc_format( "convert", false );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.hibernate.persister.entity.Lockable;
|
|||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.sqm.IntervalType;
|
||||
import org.hibernate.query.sqm.TemporalUnit;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
|
@ -393,7 +394,7 @@ public class SpannerDialect extends Dialect {
|
|||
.setExactArgumentCount( 3 )
|
||||
.register();
|
||||
functionContributions.getFunctionRegistry().namedDescriptorBuilder( "date_trunc" )
|
||||
.setInvariantType( dateType )
|
||||
.setReturnTypeResolver( StandardFunctionReturnTypeResolvers.useArgType( 2 ) )
|
||||
.setExactArgumentCount( 2 )
|
||||
.register();
|
||||
functionContributions.getFunctionRegistry().namedDescriptorBuilder( "date_from_unix_date" )
|
||||
|
|
|
@ -222,6 +222,7 @@ public class SybaseDialect extends AbstractTransactSQLDialect {
|
|||
functionFactory.varPopSamp();
|
||||
functionFactory.trunc_floorPower();
|
||||
functionFactory.round_round();
|
||||
functionFactory.dateTrunc_format( "convert", true );
|
||||
|
||||
// For SQL-Server we need to cast certain arguments to varchar(16384) to be able to concat them
|
||||
functionContributions.getFunctionRegistry().register(
|
||||
|
|
|
@ -2560,7 +2560,16 @@ public class CommonFunctionFactory {
|
|||
|
||||
public void dateTrunc() {
|
||||
functionRegistry.patternDescriptorBuilder( "date_trunc", "date_trunc('?1',?2)" )
|
||||
.setInvariantType(timestampType)
|
||||
.setReturnTypeResolver( useArgType( 2 ) )
|
||||
.setExactArgumentCount( 2 )
|
||||
.setParameterTypes( TEMPORAL_UNIT, TEMPORAL )
|
||||
.setArgumentListSignature( "(TEMPORAL_UNIT field, TEMPORAL datetime)" )
|
||||
.register();
|
||||
}
|
||||
|
||||
public void dateTrunc_datetrunc() {
|
||||
functionRegistry.patternDescriptorBuilder( "date_trunc", "datetrunc(?1,?2)" )
|
||||
.setReturnTypeResolver( useArgType( 2 ) )
|
||||
.setExactArgumentCount( 2 )
|
||||
.setParameterTypes( TEMPORAL_UNIT, TEMPORAL )
|
||||
.setArgumentListSignature( "(TEMPORAL_UNIT field, TEMPORAL datetime)" )
|
||||
|
@ -2568,12 +2577,13 @@ public class CommonFunctionFactory {
|
|||
}
|
||||
|
||||
public void dateTrunc_trunc() {
|
||||
functionRegistry.patternDescriptorBuilder( "date_trunc", "trunc(?2,'?1')" )
|
||||
.setInvariantType(timestampType)
|
||||
.setExactArgumentCount( 2 )
|
||||
.setParameterTypes(TEMPORAL_UNIT, TEMPORAL)
|
||||
.setArgumentListSignature( "(TEMPORAL_UNIT field, TEMPORAL datetime)" )
|
||||
.register();
|
||||
functionRegistry.register( "date_trunc", new DateTruncTrunc( typeConfiguration ) );
|
||||
}
|
||||
|
||||
public void dateTrunc_format(String toDateFunction, boolean useConvertToFormat) {
|
||||
functionRegistry.register(
|
||||
"date_trunc",
|
||||
new DateTruncEmulation( toDateFunction, useConvertToFormat, typeConfiguration )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.TemporalUnit;
|
||||
import org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.function.FunctionRenderingSupport;
|
||||
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmDurationUnit;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmFormat;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Emulation of {@code datetrunc} function that leverages
|
||||
* formatting the datetime to string and back to truncate it
|
||||
*
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
public class DateTruncEmulation extends AbstractSqmFunctionDescriptor implements FunctionRenderingSupport {
|
||||
private final String toDateFunction;
|
||||
|
||||
private final boolean useConvertToFormat;
|
||||
|
||||
public DateTruncEmulation(String toDateFunction, boolean useConvertToFormat, TypeConfiguration typeConfiguration) {
|
||||
super(
|
||||
"date_trunc",
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL_UNIT, TEMPORAL ),
|
||||
StandardFunctionReturnTypeResolvers.useArgType( 2 ),
|
||||
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, TEMPORAL_UNIT, TEMPORAL )
|
||||
);
|
||||
this.toDateFunction = toDateFunction;
|
||||
this.useConvertToFormat = useConvertToFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
SqlAstTranslator<?> walker) {
|
||||
sqlAppender.appendSql( toDateFunction );
|
||||
sqlAppender.append( '(' );
|
||||
if ( !useConvertToFormat ) {
|
||||
if ( toDateFunction.equalsIgnoreCase( "convert" ) ) {
|
||||
sqlAppender.append( "datetime," );
|
||||
sqlAstArguments.get( 0 ).accept( walker );
|
||||
}
|
||||
else {
|
||||
sqlAstArguments.get( 0 ).accept( walker );
|
||||
sqlAppender.append( ',' );
|
||||
sqlAstArguments.get( 1 ).accept( walker );
|
||||
}
|
||||
}
|
||||
else {
|
||||
// custom implementation that uses convert instead of format for Sybase
|
||||
// see: https://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc36271.1600/doc/html/san1393050437990.html
|
||||
sqlAppender.append( "datetime,substring(convert(varchar," );
|
||||
sqlAstArguments.get( 1 ).accept( walker );
|
||||
sqlAppender.append( ",21),1,17-len(" );
|
||||
sqlAstArguments.get( 0 ).accept( walker );
|
||||
sqlAppender.append( "))+" );
|
||||
sqlAstArguments.get( 0 ).accept( walker );
|
||||
sqlAppender.append( ",21" );
|
||||
}
|
||||
sqlAppender.append( ')' );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression(
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
ReturnableType<T> impliedResultType,
|
||||
QueryEngine queryEngine,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
final NodeBuilder nodeBuilder = queryEngine.getCriteriaBuilder();
|
||||
final TemporalUnit temporalUnit = ( (SqmDurationUnit<?>) arguments.get( 0 ) ).getUnit();
|
||||
final String pattern;
|
||||
final String literal;
|
||||
switch ( temporalUnit ) {
|
||||
case YEAR:
|
||||
pattern = "yyyy";
|
||||
literal = "-01-01 00:00:00";
|
||||
break;
|
||||
case MONTH:
|
||||
pattern = "yyyy-MM";
|
||||
literal = "-01 00:00:00";
|
||||
break;
|
||||
case DAY:
|
||||
pattern = "yyyy-MM-dd";
|
||||
literal = " 00:00:00";
|
||||
break;
|
||||
case HOUR:
|
||||
pattern = "yyyy-MM-dd HH";
|
||||
literal = ":00:00";
|
||||
break;
|
||||
case MINUTE:
|
||||
pattern = "yyyy-MM-dd HH:mm";
|
||||
literal = ":00";
|
||||
break;
|
||||
case SECOND:
|
||||
pattern = "yyyy-MM-dd HH:mm:ss";
|
||||
literal = null;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException( "Temporal unit not supported [" + temporalUnit + "]" );
|
||||
}
|
||||
|
||||
final SqmTypedNode<?> datetime = arguments.get( 1 );
|
||||
final List<SqmTypedNode<?>> args = new ArrayList<>( 2 );
|
||||
if ( !useConvertToFormat ) {
|
||||
// use standard format function
|
||||
final SqmExpression<?> formatExpression = queryEngine.getSqmFunctionRegistry()
|
||||
.findFunctionDescriptor( "format" )
|
||||
.generateSqmExpression(
|
||||
asList(
|
||||
datetime,
|
||||
new SqmFormat(
|
||||
pattern,
|
||||
typeConfiguration.getBasicTypeForJavaType( String.class ),
|
||||
nodeBuilder
|
||||
)
|
||||
),
|
||||
null,
|
||||
queryEngine,
|
||||
typeConfiguration
|
||||
);
|
||||
final SqmExpression<?> formattedDatetime;
|
||||
if ( literal != null ) {
|
||||
formattedDatetime = queryEngine.getSqmFunctionRegistry()
|
||||
.findFunctionDescriptor( "concat" )
|
||||
.generateSqmExpression(
|
||||
asList(
|
||||
formatExpression,
|
||||
new SqmLiteral<>(
|
||||
literal,
|
||||
typeConfiguration.getBasicTypeForJavaType( String.class ),
|
||||
nodeBuilder
|
||||
)
|
||||
),
|
||||
null,
|
||||
queryEngine,
|
||||
typeConfiguration
|
||||
);
|
||||
}
|
||||
else {
|
||||
formattedDatetime = formatExpression;
|
||||
}
|
||||
args.add( formattedDatetime );
|
||||
args.add( new SqmFormat(
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
typeConfiguration.getBasicTypeForJavaType( String.class ),
|
||||
nodeBuilder
|
||||
) );
|
||||
}
|
||||
else {
|
||||
args.add( new SqmLiteral<>(
|
||||
literal != null ? literal.replace( "-", "/" ) : "",
|
||||
typeConfiguration.getBasicTypeForJavaType( String.class ),
|
||||
nodeBuilder
|
||||
) );
|
||||
args.add( datetime );
|
||||
}
|
||||
return new SelfRenderingSqmFunction<>(
|
||||
this,
|
||||
this,
|
||||
args,
|
||||
impliedResultType,
|
||||
getArgumentsValidator(),
|
||||
getReturnTypeResolver(),
|
||||
nodeBuilder,
|
||||
"date_trunc"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.TemporalUnit;
|
||||
import org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.function.FunctionRenderingSupport;
|
||||
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmDurationUnit;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmLiteral;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL;
|
||||
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL_UNIT;
|
||||
|
||||
/**
|
||||
* Trunc function which converts the {@link TemporalUnit}
|
||||
* to the format required for {@code trunc()}
|
||||
*
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
public class DateTruncTrunc extends AbstractSqmFunctionDescriptor implements FunctionRenderingSupport {
|
||||
|
||||
public DateTruncTrunc(TypeConfiguration typeConfiguration) {
|
||||
super(
|
||||
"date_trunc",
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL_UNIT, TEMPORAL ),
|
||||
StandardFunctionReturnTypeResolvers.useArgType( 2 ),
|
||||
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, TEMPORAL_UNIT, TEMPORAL )
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
SqlAstTranslator<?> walker) {
|
||||
sqlAppender.appendSql( "trunc(" );
|
||||
sqlAstArguments.get( 1 ).accept( walker );
|
||||
sqlAppender.append( ',' );
|
||||
sqlAstArguments.get( 0 ).accept( walker );
|
||||
sqlAppender.append( ')' );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression(
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
ReturnableType<T> impliedResultType,
|
||||
QueryEngine queryEngine,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
final NodeBuilder nodeBuilder = queryEngine.getCriteriaBuilder();
|
||||
final TemporalUnit temporalUnit = ( (SqmDurationUnit<?>) arguments.get( 0 ) ).getUnit();
|
||||
final String pattern;
|
||||
switch ( temporalUnit ) {
|
||||
case YEAR:
|
||||
pattern = "YYYY";
|
||||
break;
|
||||
case MONTH:
|
||||
pattern = "MM";
|
||||
break;
|
||||
case WEEK:
|
||||
pattern = "IW";
|
||||
break;
|
||||
case DAY:
|
||||
pattern = "DD";
|
||||
break;
|
||||
case HOUR:
|
||||
pattern = "HH";
|
||||
break;
|
||||
case MINUTE:
|
||||
pattern = "MI";
|
||||
break;
|
||||
case SECOND:
|
||||
if ( nodeBuilder.getSessionFactory().getJdbcServices().getDialect() instanceof OracleDialect ) {
|
||||
// Oracle does not support truncating to seconds with the native function, use emulation
|
||||
return new DateTruncEmulation( "to_date", false, typeConfiguration )
|
||||
.generateSqmFunctionExpression(
|
||||
arguments,
|
||||
impliedResultType,
|
||||
queryEngine,
|
||||
typeConfiguration
|
||||
);
|
||||
}
|
||||
pattern = "SS";
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException( "Temporal unit not supported [" + temporalUnit + "]" );
|
||||
}
|
||||
|
||||
final List<SqmTypedNode<?>> args = new ArrayList<>( 2 );
|
||||
args.add( new SqmLiteral<>(
|
||||
pattern,
|
||||
typeConfiguration.getBasicTypeForJavaType( String.class ),
|
||||
nodeBuilder
|
||||
) );
|
||||
args.add( arguments.get( 1 ) );
|
||||
return new SelfRenderingSqmFunction<>(
|
||||
this,
|
||||
this,
|
||||
args,
|
||||
impliedResultType,
|
||||
getArgumentsValidator(),
|
||||
getReturnTypeResolver(),
|
||||
nodeBuilder,
|
||||
"date_trunc"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -513,13 +513,17 @@ public class FunctionTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialect(H2Dialect.class)
|
||||
@RequiresDialect(DB2Dialect.class)
|
||||
@RequiresDialect(OracleDialect.class)
|
||||
@RequiresDialect(PostgreSQLDialect.class)
|
||||
@SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support any form of date truncation")
|
||||
public void testDateTruncFunction(SessionFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
session -> session.createQuery("select date_trunc(year,current_timestamp)", Timestamp.class).getSingleResult()
|
||||
session -> {
|
||||
session.createQuery( "select date_trunc(year,current_timestamp)", Timestamp.class ).getSingleResult();
|
||||
session.createQuery( "select date_trunc(month,current_timestamp)", Timestamp.class ).getSingleResult();
|
||||
session.createQuery( "select date_trunc(day,current_timestamp)", Timestamp.class ).getSingleResult();
|
||||
session.createQuery( "select date_trunc(hour,current_timestamp)", Timestamp.class ).getSingleResult();
|
||||
session.createQuery( "select date_trunc(minute,current_timestamp)", Timestamp.class ).getSingleResult();
|
||||
session.createQuery( "select date_trunc(second,current_timestamp)", Timestamp.class ).getSingleResult();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue