HHH-13785 : HQL/Criteria function support

- EXTRACT function
- LOCAL_DATETIME function
- LOCAL_DATE function
- LOCAL_TIME function
This commit is contained in:
Steve Ebersole 2019-12-26 11:19:25 -06:00
parent 77377337d6
commit 6e0d15b134
6 changed files with 159 additions and 21 deletions

2
hibernate-core/src/main/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
## Ignore IntelliJ Antlr Plugin's output
gen/

View File

@ -138,6 +138,7 @@ CURRENT_INSTANT : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [iI] [nN] [sS] [tT] [a
CURRENT_TIME : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [tT] [iI] [mM] [eE];
CURRENT_TIMESTAMP : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [tT] [iI] [mM] [eE] [sS] [tT] [aA] [mM] [pP];
DAY : [dD] [aA] [yY];
DATE : [dD] [aA] [tT] [eE];
DELETE : [dD] [eE] [lL] [eE] [tT] [eE];
DESC : [dD] [eE] [sS] [cC];
DISTINCT : [dD] [iI] [sS] [tT] [iI] [nN] [cC] [tT];
@ -178,6 +179,9 @@ LIMIT : [lL] [iI] [mM] [iI] [tT];
LIST : [lL] [iI] [sS] [tT];
LN : [lL] [nN];
LOCATE : [lL] [oO] [cC] [aA] [tT] [eE];
LOCAL_DATE : [lL] [oO] [cC] [aA] [lL] '_' [dD] [aA] [tT] [eE];
LOCAL_DATETIME : [lL] [oO] [cC] [aA] [lL] '_' [dD] [aA] [tT] [eE] [tT] [iI] [mM] [eE];
LOCAL_TIME : [lL] [oO] [cC] [aA] [lL] '_' [tT] [iI] [mM] [eE];
LOWER : [lL] [oO] [wW] [eE] [rR];
MAP : [mM] [aA] [pP];
MAX : [mM] [aA] [xX];
@ -193,6 +197,7 @@ MINUTE : [mM] [iI] [nN] [uU] [tT] [eE];
MOD : [mM] [oO] [dD];
MONTH : [mM] [oO] [nN] [tT] [hH];
NEW : [nN] [eE] [wW];
NANOSECOND : [nN] [aA] [nN] [oO] [sS] [eE] [cC] [oO] [nN] [dD];
NOT : [nN] [oO] [tT];
NULLIF : [nN] [uU] [lL] [lL] [iI] [fF];
OBJECT : [oO] [bB] [jJ] [eE] [cC] [tT];
@ -218,6 +223,7 @@ STR : [sS] [tT] [rR];
SUBSTRING : [sS] [uU] [bB] [sS] [tT] [rR] [iI] [nN] [gG];
SUM : [sS] [uU] [mM];
THEN : [tT] [hH] [eE] [nN];
TIME : [tT] [iI] [mM] [eE];
TIMEZONE_HOUR : [tT] [iI] [mM] [eE] [zZ] [oO] [nN] [eE] '_' [hH] [oO] [uU] [rR];
TIMEZONE_MINUTE : [tT] [iI] [mM] [eE] [zZ] [oO] [nN] [eE] '_' [mM] [iI] [nN] [uU] [tT] [eE];
TRAILING : [tT] [rR] [aA] [iI] [lL] [iI] [nN] [gG];

View File

@ -617,6 +617,9 @@ standardFunction
| currentTimeFunction
| currentTimestampFunction
| currentInstantFunction
| localDateFunction
| localDateTimeFunction
| localTimeFunction
;
@ -808,6 +811,18 @@ currentInstantFunction
: CURRENT_INSTANT (LEFT_PAREN RIGHT_PAREN)?
;
localDateTimeFunction
: LOCAL_DATETIME (LEFT_PAREN RIGHT_PAREN)?
;
localDateFunction
: LOCAL_DATE (LEFT_PAREN RIGHT_PAREN)?
;
localTimeFunction
: LOCAL_TIME (LEFT_PAREN RIGHT_PAREN)?
;
formatFunction
: FORMAT LEFT_PAREN expression AS format RIGHT_PAREN
;
@ -906,10 +921,12 @@ identifier
| COUNT
| CROSS
| CURRENT_DATE
| CURRENT_DATETIME
| CURRENT_INSTANT
| CURRENT_TIME
| CURRENT_TIMESTAMP
| DAY
| DATE
| DELETE
| DESC
| DISTINCT
@ -963,6 +980,7 @@ identifier
| MINUTE
| MOD
| MONTH
| NANOSECOND
| NEW
| NOT
| NULLIF
@ -989,6 +1007,7 @@ identifier
| SUBSTRING
| SUM
| THEN
| TIME
| TIMEZONE_HOUR
| TIMEZONE_MINUTE
| TRAILING

View File

@ -711,6 +711,7 @@ public class CommonFunctionFactory {
}
public static void currentDateTimeTimestamp(QueryEngine queryEngine) {
// Legacy JDK `Date`-based functions
queryEngine.getSqmFunctionRegistry().noArgsBuilder( "current_time" )
.setInvariantType( StandardBasicTypes.TIME )
.register();
@ -721,15 +722,18 @@ public class CommonFunctionFactory {
.setInvariantType( StandardBasicTypes.TIMESTAMP )
.register();
// "JDK 8" temporal-type functions.
// - These are essentially aliases for the `current_XYZ` forms
// but defining JDK 8 temporal type return values
queryEngine.getSqmFunctionRegistry().noArgsBuilder( "current_time" )
.setInvariantType( StandardBasicTypes.LOCAL_TIME )
.register();
.register( "local_time" );
queryEngine.getSqmFunctionRegistry().noArgsBuilder( "current_date" )
.setInvariantType( StandardBasicTypes.LOCAL_DATE )
.register();
.register( "local_date" );
queryEngine.getSqmFunctionRegistry().noArgsBuilder( "current_timestamp" )
.setInvariantType( StandardBasicTypes.LOCAL_DATE_TIME )
.register();
.register( "local_datetime");
queryEngine.getSqmFunctionRegistry().noArgsBuilder( "current_timestamp" )
.setInvariantType( StandardBasicTypes.INSTANT )
.register( "current_instant" );

View File

@ -1507,6 +1507,39 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
);
}
@Override
public Object visitLocalDateTimeFunction(HqlParser.LocalDateTimeFunctionContext ctx) {
//noinspection unchecked
return new SqmFunction(
"local_datetime",
getFunctionDescriptor( "local_datetime" ),
StandardBasicTypes.TIMESTAMP,
creationContext.getNodeBuilder()
);
}
@Override
public Object visitLocalDateFunction(HqlParser.LocalDateFunctionContext ctx) {
//noinspection unchecked
return new SqmFunction(
"local_date",
getFunctionDescriptor( "local_date" ),
StandardBasicTypes.TIMESTAMP,
creationContext.getNodeBuilder()
);
}
@Override
public Object visitLocalTimeFunction(HqlParser.LocalTimeFunctionContext ctx) {
//noinspection unchecked
return new SqmFunction(
"local_time",
getFunctionDescriptor( "local_time" ),
StandardBasicTypes.TIMESTAMP,
creationContext.getNodeBuilder()
);
}
@Override
public SqmExpression visitCurrentInstantFunction(HqlParser.CurrentInstantFunctionContext ctx) {
//noinspection unchecked
@ -1901,11 +1934,15 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public List<SqmTypedNode<?>> visitNonStandardFunctionArguments(HqlParser.NonStandardFunctionArgumentsContext ctx) {
if ( ctx == null || ctx.expression() == null ) {
return Collections.emptyList();
}
final List<SqmTypedNode<?>> arguments = new ArrayList<>();
for ( int i=0, size=ctx.expression().size(); i<size; i++ ) {
for ( int i = 0, size = ctx.expression().size(); i < size; i++ ) {
// we handle the final argument differently...
if ( i == size-1 ) {
if ( i == size - 1 ) {
arguments.add( visitFinalFunctionArgument( ctx.expression( i ) ) );
}
else {
@ -2109,56 +2146,56 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
@Override
public Object visitDatetimeField(HqlParser.DatetimeFieldContext ctx) {
NodeBuilder nodeBuilder = creationContext.getNodeBuilder();
if (ctx.DAY()!=null) {
if ( ctx.DAY() != null ) {
return new SqmExtractUnit<>(
TemporalUnit.DAY,
resolveExpressableTypeBasic( Integer.class ),
nodeBuilder
);
}
if (ctx.MONTH()!=null) {
if ( ctx.MONTH() != null ) {
return new SqmExtractUnit<>(
TemporalUnit.MONTH,
resolveExpressableTypeBasic( Integer.class ),
nodeBuilder
);
}
if (ctx.YEAR()!=null) {
if ( ctx.YEAR() != null ) {
return new SqmExtractUnit<>(
TemporalUnit.YEAR,
resolveExpressableTypeBasic( Integer.class ),
nodeBuilder
);
}
if (ctx.HOUR()!=null) {
if ( ctx.HOUR() != null ) {
return new SqmExtractUnit<>(
TemporalUnit.HOUR,
resolveExpressableTypeBasic( Integer.class ),
nodeBuilder
);
}
if (ctx.MINUTE()!=null) {
if ( ctx.MINUTE() != null ) {
return new SqmExtractUnit<>(
TemporalUnit.MINUTE,
resolveExpressableTypeBasic( Integer.class ),
nodeBuilder
);
}
if (ctx.SECOND()!=null) {
if ( ctx.SECOND() != null ) {
return new SqmExtractUnit<>(
TemporalUnit.SECOND,
resolveExpressableTypeBasic( Float.class ),
nodeBuilder
);
}
if (ctx.WEEK()!=null) {
if ( ctx.WEEK() != null ) {
return new SqmExtractUnit<>(
TemporalUnit.WEEK,
resolveExpressableTypeBasic( Integer.class ),
nodeBuilder
);
}
if (ctx.QUARTER()!=null) {
if ( ctx.QUARTER() != null ) {
return new SqmExtractUnit<>(
TemporalUnit.QUARTER,
resolveExpressableTypeBasic( Integer.class ),
@ -2166,7 +2203,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
);
}
return super.visitDatetimeField(ctx);
return super.visitDatetimeField( ctx );
}
@Override

View File

@ -11,7 +11,6 @@ import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
@ -82,6 +81,54 @@ public class StandardFunctionTests {
);
}
@Test
public void localDateTimeTests(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "select local_datetime from EntityOfBasics" ).list();
session.createQuery( "select local_datetime() from EntityOfBasics" ).list();
session.createQuery( "select e from EntityOfBasics e where e.theTimestamp = local_datetime" ).list();
session.createQuery( "select e from EntityOfBasics e where e.theTimestamp = local_datetime()()" ).list();
session.createQuery( "select e from EntityOfBasics e where local_datetime() between e.theTimestamp and e.theTimestamp" ).list();
session.createQuery( "select e from EntityOfBasics e where local_datetime()() between e.theTimestamp and e.theTimestamp" ).list();
}
);
}
@Test
public void localDateTests(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "select local_date from EntityOfBasics" ).list();
session.createQuery( "select local_date() from EntityOfBasics" ).list();
session.createQuery( "select e from EntityOfBasics e where e.theTimestamp = local_date" ).list();
session.createQuery( "select e from EntityOfBasics e where e.theTimestamp = local_date()()" ).list();
session.createQuery( "select e from EntityOfBasics e where local_date() between e.theTimestamp and e.theTimestamp" ).list();
session.createQuery( "select e from EntityOfBasics e where local_date()() between e.theTimestamp and e.theTimestamp" ).list();
}
);
}
@Test
public void localTimeTests(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "select local_time from EntityOfBasics" ).list();
session.createQuery( "select local_time() from EntityOfBasics" ).list();
session.createQuery( "select e from EntityOfBasics e where e.theTimestamp = local_time" ).list();
session.createQuery( "select e from EntityOfBasics e where e.theTimestamp = local_time()()" ).list();
session.createQuery( "select e from EntityOfBasics e where local_time() between e.theTimestamp and e.theTimestamp" ).list();
session.createQuery( "select e from EntityOfBasics e where local_time()() between e.theTimestamp and e.theTimestamp" ).list();
}
);
}
@Test
public void testConcatFunction(SessionFactoryScope scope) {
scope.inTransaction(
@ -489,7 +536,6 @@ public class StandardFunctionTests {
}
@Test
@FailureExpected
public void testExtractFunction(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
@ -534,20 +580,44 @@ public class StandardFunctionTests {
session.createQuery("select extract(offset hour from e.theTime) from EntityOfBasics e")
.list();
session.createQuery("select extract(offset hour minute from e.theTime) from EntityOfBasics e")
.list();
// the grammar rule is defined as `HOUR | MINUTE` so no idea how both ever worked.
// session.createQuery("select extract(offset hour minute from e.theTime) from EntityOfBasics e")
// .list();
session.createQuery("select extract(offset from e.theTimestamp) from EntityOfBasics e")
.list();
session.createQuery("select extract(time from e.theTimestamp), extract(date from e.theTimestamp) from EntityOfBasics e")
.list();
session.createQuery("select extract(time from current datetime), extract(date from current datetime) from EntityOfBasics e")
session.createQuery("select extract(time from local_datetime), extract(date from local_datetime) from EntityOfBasics e")
.list();
session.createQuery("select extract(week of month from current date) from EntityOfBasics e")
// Not a fan of these "current date", "current time" and "current timestamp" forms. They were meant to represent `LocalDate`,
// `LocalTime` and `LocalDateTime` values. Which imo is just super confusing with `current_date`, `current_time` and
// `current_timestamp` returning the `Date`, `Time` and `Timestamp` forms
// session.createQuery("select extract(time from current datetime), extract(date from current datetime) from EntityOfBasics e")
// .list();
// So I added `local_date`, `local_time` and `local_datetime` functions instead. See the `localDateTests`, etc
session.createQuery("select extract(week of month from current_date) from EntityOfBasics e")
.list();
session.createQuery("select extract(week of year from current date) from EntityOfBasics e")
session.createQuery("select extract(week of year from current_date) from EntityOfBasics e")
.list();
// I really don't like this "separate word" approach - here, even moreso. The problem is the PR also defines a
// `FIELD( temporalValue)` form which here, e.g., would mean this is a valid expression: `week of year( current date )` which is awful imo
// session.createQuery("select extract(week of year from current date) from EntityOfBasics e")
// .list();
}
);
}
@Test
public void isolated(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery("select extract(time from local_datetime), extract(date from local_datetime) from EntityOfBasics e")
.list();
}
);