Skip to content

Commit 01e548f

Browse files
HHH-18837 Oracle epoch extraction doesn't work with dates
1 parent 3cfeb8f commit 01e548f

File tree

5 files changed

+99
-2
lines changed

5 files changed

+99
-2
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
418418
functionFactory.hex( "rawtohex(?1)" );
419419
functionFactory.sha( "standard_hash(?1, 'SHA256')" );
420420
functionFactory.md5( "standard_hash(?1, 'MD5')" );
421+
422+
functionFactory.extract_oracle( this );
421423
}
422424

423425
/**
@@ -624,7 +626,6 @@ public String extractPattern(TemporalUnit unit) {
624626
case HOUR -> "to_number(to_char(?2,'HH24'))";
625627
case MINUTE -> "to_number(to_char(?2,'MI'))";
626628
case SECOND -> "to_number(to_char(?2,'SS'))";
627-
case EPOCH -> "trunc((cast(?2 at time zone 'UTC' as date) - date '1970-1-1')*86400)";
628629
default -> super.extractPattern(unit);
629630
};
630631
}

hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4403,4 +4403,11 @@ public void xmltable_sqlserver() {
44034403
public void xmltable_sybasease() {
44044404
functionRegistry.register( "xmltable", new SybaseASEXmlTableFunction( typeConfiguration ) );
44054405
}
4406+
4407+
/**
4408+
* Oracle extract() function
4409+
*/
4410+
public void extract_oracle(Dialect dialect) {
4411+
functionRegistry.register( "extract", new OracleExtractFunction( dialect, typeConfiguration ) );
4412+
}
44064413
}

hibernate-core/src/main/java/org/hibernate/dialect/function/ExtractFunction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
*/
4949
public class ExtractFunction extends AbstractSqmFunctionDescriptor implements FunctionRenderer {
5050

51-
private final Dialect dialect;
51+
protected final Dialect dialect;
5252

5353
public ExtractFunction(Dialect dialect, TypeConfiguration typeConfiguration) {
5454
super(
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.dialect.function;
6+
7+
import jakarta.persistence.TemporalType;
8+
import org.hibernate.dialect.Dialect;
9+
import org.hibernate.metamodel.model.domain.ReturnableType;
10+
import org.hibernate.query.common.TemporalUnit;
11+
import org.hibernate.query.sqm.produce.function.internal.PatternRenderer;
12+
import org.hibernate.sql.ast.SqlAstTranslator;
13+
import org.hibernate.sql.ast.spi.SqlAppender;
14+
import org.hibernate.sql.ast.tree.SqlAstNode;
15+
import org.hibernate.sql.ast.tree.expression.Expression;
16+
import org.hibernate.sql.ast.tree.expression.ExtractUnit;
17+
import org.hibernate.type.spi.TypeConfiguration;
18+
19+
import java.util.List;
20+
21+
import static org.hibernate.type.spi.TypeConfiguration.getSqlTemporalType;
22+
23+
public class OracleExtractFunction extends ExtractFunction {
24+
public OracleExtractFunction(Dialect dialect, TypeConfiguration typeConfiguration) {
25+
super( dialect, typeConfiguration );
26+
}
27+
28+
@Override
29+
public void render(
30+
SqlAppender sqlAppender,
31+
List<? extends SqlAstNode> sqlAstArguments,
32+
ReturnableType<?> returnType,
33+
SqlAstTranslator<?> walker) {
34+
new PatternRenderer( extractPattern( sqlAstArguments ) ).render( sqlAppender, sqlAstArguments, walker );
35+
}
36+
37+
@SuppressWarnings("deprecation")
38+
private String extractPattern(List<? extends SqlAstNode> sqlAstArguments) {
39+
final ExtractUnit field = (ExtractUnit) sqlAstArguments.get( 0 );
40+
final TemporalUnit unit = field.getUnit();
41+
final Expression expression = (Expression) sqlAstArguments.get( 1 );
42+
final TemporalType temporalType;
43+
if ( unit.equals( TemporalUnit.EPOCH ) && (temporalType = getSqlTemporalType(
44+
expression.getExpressionType() )) != null ) {
45+
return temporalType == TemporalType.DATE
46+
? "trunc((cast(from_tz(cast(?2 as timestamp),'UTC') as date) - date '1970-1-1')*86400)"
47+
: "trunc((cast(?2 at time zone 'UTC' as date) - date '1970-1-1')*86400)";
48+
}
49+
return dialect.extractPattern( unit );
50+
}
51+
}

hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@
5858
import java.time.LocalDateTime;
5959
import java.time.LocalTime;
6060
import java.time.OffsetDateTime;
61+
import java.time.ZoneId;
6162
import java.time.ZoneOffset;
63+
import java.time.ZonedDateTime;
6264
import java.time.temporal.ChronoUnit;
6365
import java.util.Date;
6466
import java.util.List;
@@ -2644,4 +2646,40 @@ public void testHexFunction(SessionFactoryScope scope) {
26442646
.getSingleResult().toUpperCase( Locale.ROOT ) );
26452647
});
26462648
}
2649+
2650+
@Test
2651+
@JiraKey("HHH-18837")
2652+
public void testEpochFunction(SessionFactoryScope scope) {
2653+
2654+
LocalDate someLocalDate = LocalDate.of( 2013, 7, 5 );
2655+
LocalDateTime someLocalDateTime = someLocalDate.atStartOfDay();
2656+
Date someDate = Date.from( someLocalDateTime.toInstant( ZoneOffset.UTC ) );
2657+
ZonedDateTime someZonedDateTime = ZonedDateTime.of( someLocalDate, LocalTime.MIN,
2658+
ZoneId.of( "Europe/Vienna" ) );
2659+
2660+
scope.inTransaction( session -> {
2661+
EntityOfBasics entityOfBasics = new EntityOfBasics();
2662+
entityOfBasics.setId( 124 );
2663+
entityOfBasics.setTheDate( someDate );
2664+
entityOfBasics.setTheLocalDate( someLocalDate );
2665+
entityOfBasics.setTheLocalDateTime( someLocalDateTime );
2666+
entityOfBasics.setTheZonedDateTime( someZonedDateTime );
2667+
session.persist( entityOfBasics );
2668+
2669+
assertEquals( someDate.toInstant().toEpochMilli() / 1000,
2670+
session.createSelectionQuery( "select epoch(a.theDate) from EntityOfBasics a where id=124",
2671+
Long.class ).getSingleResult() );
2672+
assertEquals( someLocalDate.atStartOfDay( ZoneOffset.UTC ).toInstant().toEpochMilli() / 1000,
2673+
session.createSelectionQuery( "select epoch(a.theLocalDate) from EntityOfBasics a where id=124",
2674+
Long.class ).getSingleResult() );
2675+
assertEquals( someLocalDateTime.toEpochSecond( ZoneOffset.UTC ), session.createSelectionQuery(
2676+
"select epoch(a.theLocalDateTime) from EntityOfBasics a where id=124",
2677+
Long.class ).getSingleResult() );
2678+
assertEquals( someZonedDateTime.toEpochSecond(), session.createSelectionQuery(
2679+
"select epoch(a.theZonedDateTime) from EntityOfBasics a where id=124",
2680+
Long.class ).getSingleResult() );
2681+
2682+
session.remove( entityOfBasics );
2683+
} );
2684+
}
26472685
}

0 commit comments

Comments
 (0)