From 0aa5be8ba2d24d8e659cc2d7404bf9641fa05e27 Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Mon, 6 Nov 2023 10:47:01 -0600 Subject: [PATCH] Properly handle EXTRACT function in HQL. HQL's extract function supports things like "day of week", "day of month", and "week of year". Extend support to these alternate expressions. See #3219. --- .../data/jpa/repository/query/Hql.g4 | 3 ++ .../repository/query/HqlQueryRenderer.java | 39 ++++++++++++++++++- .../query/HqlQueryRendererTests.java | 30 ++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 index 1e80b10fec..6d6f7567d6 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 @@ -382,6 +382,9 @@ expression | expression op=('*' | '/') expression # MultiplicationExpression | expression op=('+' | '-') expression # AdditionExpression | expression '||' expression # HqlConcatenationExpression + | DAY OF WEEK # DayOfWeekExpression + | DAY OF MONTH # DayOfMonthExpression + | WEEK OF YEAR # WeekOfYearExpression ; primaryExpression diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java index ca2e934b82..bfb2991df6 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java @@ -1223,6 +1223,42 @@ public List visitHqlConcatenationExpression(HqlParser.HqlC return tokens; } + @Override + public List visitDayOfWeekExpression(HqlParser.DayOfWeekExpressionContext ctx) { + + List tokens = new ArrayList<>(); + + tokens.add(new JpaQueryParsingToken(ctx.DAY())); + tokens.add(new JpaQueryParsingToken(ctx.OF())); + tokens.add(new JpaQueryParsingToken(ctx.WEEK())); + + return tokens; + } + + @Override + public List visitDayOfMonthExpression(HqlParser.DayOfMonthExpressionContext ctx) { + + List tokens = new ArrayList<>(); + + tokens.add(new JpaQueryParsingToken(ctx.DAY())); + tokens.add(new JpaQueryParsingToken(ctx.OF())); + tokens.add(new JpaQueryParsingToken(ctx.MONTH())); + + return tokens; + } + + @Override + public List visitWeekOfYearExpression(HqlParser.WeekOfYearExpressionContext ctx) { + + List tokens = new ArrayList<>(); + + tokens.add(new JpaQueryParsingToken(ctx.WEEK())); + tokens.add(new JpaQueryParsingToken(ctx.OF())); + tokens.add(new JpaQueryParsingToken(ctx.YEAR())); + + return tokens; + } + @Override public List visitGroupedExpression(HqlParser.GroupedExpressionContext ctx) { @@ -1915,11 +1951,12 @@ public List visitExtractFunction(HqlParser.ExtractFunction if (ctx.EXTRACT() != null) { - tokens.add(new JpaQueryParsingToken(ctx.EXTRACT())); + tokens.add(new JpaQueryParsingToken(ctx.EXTRACT(), false)); tokens.add(TOKEN_OPEN_PAREN); tokens.addAll(visit(ctx.expression(0))); tokens.add(new JpaQueryParsingToken(ctx.FROM())); tokens.addAll(visit(ctx.expression(1))); + NOSPACE(tokens); tokens.add(TOKEN_CLOSE_PAREN); } else if (ctx.dateTimeFunction() != null) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java index 39ce36c605..5d3ef0da1f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java @@ -1605,4 +1605,34 @@ void newShouldBeLegalAsPartOfAStateFieldPathExpression() { void powerShouldBeLegalInAQuery() { assertQuery("select e.power.id from MyEntity e"); } + + @Test // GH-3219 + void extractFunctionShouldSupportAdditionalExtensions() { + + assertQuery(""" + select extract(day of week from departureTime) AS day, sum(duration) as duration from JourneyEntity + group by extract(day of week from departureTime) + """); + assertQuery(""" + select extract(day of month from departureTime) AS day, sum(duration) as duration from JourneyEntity + group by extract(day of month from departureTime) + """); + assertQuery(""" + select extract(week of year from departureTime) AS day, sum(duration) as duration from JourneyEntity + group by extract(week of year from departureTime) + """); + + assertQuery(""" + select extract(date from departureTime) AS date + group by extract(date from departureTime) + """); + assertQuery(""" + select extract(time from departureTime) AS time + group by extract(time from departureTime) + """); + assertQuery(""" + select extract(epoch from departureTime) AS epoch + group by extract(epoch from departureTime) + """); + } }