HHH-16130 New dateTrunc criteria function

This commit is contained in:
Marco Belladelli 2023-02-14 16:49:23 +01:00 committed by Christian Beikov
parent e170eb33d1
commit a100dfceda
6 changed files with 88 additions and 15 deletions

View File

@ -38,7 +38,6 @@ 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;
@ -53,6 +52,7 @@ import org.hibernate.type.StandardBasicTypes;
import jakarta.persistence.TemporalType;
import static org.hibernate.dialect.SimpleDatabaseVersion.ZERO_VERSION;
import static org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers.useArgType;
import static org.hibernate.type.SqlTypes.BIGINT;
import static org.hibernate.type.SqlTypes.BINARY;
import static org.hibernate.type.SqlTypes.BLOB;
@ -394,7 +394,7 @@ public class SpannerDialect extends Dialect {
.setExactArgumentCount( 3 )
.register();
functionContributions.getFunctionRegistry().namedDescriptorBuilder( "date_trunc" )
.setReturnTypeResolver( StandardFunctionReturnTypeResolvers.useArgType( 2 ) )
.setReturnTypeResolver( useArgType( 1 ) )
.setExactArgumentCount( 2 )
.register();
functionContributions.getFunctionRegistry().namedDescriptorBuilder( "date_from_unix_date" )

View File

@ -23,6 +23,7 @@ import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.type.spi.TypeConfiguration;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
/**
* Custom {@link TruncFunction} for Sybase which uses a dialect-specific emulation function for datetimes
@ -94,10 +95,16 @@ public class SybaseTruncFunction extends TruncFunction {
sqlAppender.append( '(' );
sqlAppender.append( "datetime,substring(convert(varchar," );
sqlAstArguments.get( 0 ).accept( walker );
sqlAppender.append( ",21),1,17-len(" );
sqlAstArguments.get( 1 ).accept( walker );
sqlAppender.append( "))+" );
sqlAstArguments.get( 1 ).accept( walker );
sqlAppender.append( ",21),1,17" );
if ( sqlAstArguments.size() > 1 ) {
sqlAppender.append( "-len(" );
sqlAstArguments.get( 1 ).accept( walker );
sqlAppender.append( "))+" );
sqlAstArguments.get( 1 ).accept( walker );
}
else {
sqlAppender.append( ')' );
}
sqlAppender.append( ",21)" );
}
@ -127,22 +134,25 @@ public class SybaseTruncFunction extends TruncFunction {
literal = ":00";
break;
case SECOND:
literal = "";
literal = null;
break;
default:
throw new UnsupportedOperationException( "Temporal unit not supported [" + temporalUnit + "]" );
}
final SqmTypedNode<?> datetime = arguments.get( 0 );
final SqmLiteral<String> sqmLiteral = new SqmLiteral<>(
literal,
typeConfiguration.getBasicTypeForJavaType( String.class ),
nodeBuilder
);
return new SelfRenderingSqmFunction<>(
this,
this,
asList( datetime, sqmLiteral ),
literal == null ?
singletonList( arguments.get( 0 ) ) :
asList(
arguments.get( 0 ),
new SqmLiteral<>(
literal,
typeConfiguration.getBasicTypeForJavaType( String.class ),
nodeBuilder
)
),
impliedResultType,
null,
getReturnTypeResolver(),

View File

@ -36,6 +36,7 @@ import org.hibernate.Incubating;
import org.hibernate.query.sqm.FrameKind;
import org.hibernate.query.sqm.NullPrecedence;
import org.hibernate.query.sqm.SortOrder;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
/**
@ -997,6 +998,9 @@ public interface HibernateCriteriaBuilder extends CriteriaBuilder {
@Incubating
JpaFunction<Float> second(Expression<? extends TemporalAccessor> datetime);
@Incubating
<T extends TemporalAccessor> JpaFunction<T> truncate(Expression<T> datetime, TemporalUnit temporalUnit);
/**
* @see #overlay(Expression, Expression, Expression, Expression)
*/

View File

@ -52,6 +52,7 @@ import org.hibernate.query.criteria.JpaWindow;
import org.hibernate.query.criteria.JpaWindowFrame;
import org.hibernate.query.sqm.NullPrecedence;
import org.hibernate.query.sqm.SortOrder;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import jakarta.persistence.Tuple;
@ -1303,6 +1304,11 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
return criteriaBuilder.second( datetime );
}
@Override
public <T extends TemporalAccessor> JpaFunction<T> truncate(Expression<T> datetime, TemporalUnit temporalUnit) {
return criteriaBuilder.truncate( datetime, temporalUnit );
}
@Override
public JpaFunction<String> overlay(Expression<String> string, String replacement, int start) {
return criteriaBuilder.overlay( string, replacement, start );

View File

@ -2650,6 +2650,19 @@ public class SqmCriteriaNodeBuilder implements NodeBuilder, SqmCreationContext,
return extract( datetime, TemporalUnit.SECOND, Float.class );
}
@Override
public <T extends TemporalAccessor> SqmFunction<T> truncate(Expression<T> datetime, TemporalUnit temporalUnit) {
return getFunctionDescriptor( "trunc" ).generateSqmExpression(
asList(
(SqmTypedNode<?>) datetime,
new SqmExtractUnit<>( temporalUnit, getIntegerType(), this )
),
null,
queryEngine,
getJpaMetamodel().getTypeConfiguration()
);
}
@Override
public SqmFunction<String> overlay(Expression<String> string, String replacement, int start) {
return overlay( string, replacement, value( start ), null );

View File

@ -12,12 +12,15 @@ import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import org.hibernate.dialect.CockroachDialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
@ -438,7 +441,7 @@ public class CriteriaBuilderNonStandardFunctionsTest {
@Test
@JiraKey("HHH-16185")
public void testCustomFunctionTrunc(SessionFactoryScope scope) {
public void testNumericTruncFunction(SessionFactoryScope scope) {
scope.inTransaction( session -> {
final CriteriaBuilder cb = session.getCriteriaBuilder();
final CriteriaQuery<Tuple> query = cb.createTupleQuery();
@ -463,4 +466,41 @@ public class CriteriaBuilderNonStandardFunctionsTest {
assertEquals( 32.923d, result.get( 7 ) );
} );
}
@Test
@JiraKey("HHH-16130")
@SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support any form of date truncation")
public void testDateTruncFunction(SessionFactoryScope scope) {
scope.inTransaction( session -> {
HibernateCriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<EntityOfBasics> from = query.from( EntityOfBasics.class );
Expression<LocalDateTime> theLocalDateTime = from.get( "theLocalDateTime" );
query.multiselect(
from.get( "id" ),
cb.truncate( theLocalDateTime, TemporalUnit.YEAR ),
cb.truncate( theLocalDateTime, TemporalUnit.MONTH ),
cb.truncate( theLocalDateTime, TemporalUnit.DAY ),
cb.truncate( theLocalDateTime, TemporalUnit.HOUR ),
cb.truncate( theLocalDateTime, TemporalUnit.MINUTE ),
cb.truncate( theLocalDateTime, TemporalUnit.SECOND )
).where( cb.isNotNull( theLocalDateTime ) );
Tuple result = session.createQuery( query ).getSingleResult();
EntityOfBasics eob = session.find( EntityOfBasics.class, result.get( 0 ) );
assertEquals(
eob.getTheLocalDateTime().withMonth( 1 ).withDayOfMonth( 1 ).truncatedTo( ChronoUnit.DAYS ),
result.get( 1 )
);
assertEquals(
eob.getTheLocalDateTime().withDayOfMonth( 1 ).truncatedTo( ChronoUnit.DAYS ),
result.get( 2 )
);
assertEquals( eob.getTheLocalDateTime().truncatedTo( ChronoUnit.DAYS ), result.get( 3 ) );
assertEquals( eob.getTheLocalDateTime().truncatedTo( ChronoUnit.HOURS ), result.get( 4 ) );
assertEquals( eob.getTheLocalDateTime().truncatedTo( ChronoUnit.MINUTES ), result.get( 5 ) );
assertEquals( eob.getTheLocalDateTime().truncatedTo( ChronoUnit.SECONDS ), result.get( 6 ) );
} );
}
}