Skip to content

Commit a37e01d

Browse files
committed
Handle dateTimeField property with HQL.
Hibernate supports varisous duration literals. They also support a couple formats for binary literals. See #3025
1 parent 29e16a1 commit a37e01d

File tree

4 files changed

+115
-4
lines changed

4 files changed

+115
-4
lines changed

spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4

+25-4
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ literal
299299
| stringLiteral
300300
| numericLiteral
301301
| dateTimeLiteral
302+
| binaryLiteral
302303
;
303304

304305
// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-boolean-literals
@@ -337,10 +338,24 @@ dateTimeLiteral
337338
;
338339

339340
// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-duration-literals
340-
// TBD
341+
datetimeField
342+
: YEAR
343+
| MONTH
344+
| DAY
345+
| WEEK
346+
| QUARTER
347+
| HOUR
348+
| MINUTE
349+
| SECOND
350+
| NANOSECOND
351+
| EPOCH
352+
;
341353

342354
// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-binary-literals
343-
// TBD
355+
binaryLiteral
356+
: BINARY_LITERAL
357+
| '{' HEXLITERAL (',' HEXLITERAL)* '}'
358+
;
344359

345360
// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-enum-literals
346361
// TBD
@@ -362,6 +377,8 @@ expression
362377
| primaryExpression # PlainPrimaryExpression
363378
| op=('+' | '-') numericLiteral # SignedNumericLiteral
364379
| op=('+' | '-') expression # SignedExpression
380+
| expression datetimeField # ToDurationExpression
381+
| expression BY datetimeField # FromDurationExpression
365382
| expression op=('*' | '/') expression # MultiplicationExpression
366383
| expression op=('+' | '-') expression # AdditionExpression
367384
| expression '||' expression # HqlConcatenationExpression
@@ -607,7 +624,6 @@ inList
607624
;
608625

609626
// https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-exists-predicate
610-
// TBD
611627
existsExpression
612628
: EXISTS (ELEMENTS | INDICES) '(' simplePath ')'
613629
| EXISTS expression
@@ -1058,13 +1074,18 @@ YEAR : Y E A R;
10581074

10591075
fragment INTEGER_NUMBER : ('0' .. '9')+ ;
10601076
fragment FLOAT_NUMBER : INTEGER_NUMBER+ '.'? INTEGER_NUMBER* (E [+-]? INTEGER_NUMBER)? ;
1077+
fragment HEX_DIGIT : [0-9a-fA-F];
1078+
10611079

10621080
CHARACTER : '\'' (~ ('\'' | '\\' )) '\'' ;
10631081
STRINGLITERAL : '\'' ('\'' '\'' | ~('\'' | '\\'))* '\'' ;
10641082
JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"';
10651083
INTEGER_LITERAL : INTEGER_NUMBER (L | B I)? ;
10661084
FLOAT_LITERAL : FLOAT_NUMBER (D | F | B D)?;
1067-
HEXLITERAL : '0' X ('0' .. '9' | A | B | C | D | E)+ ;
1085+
HEXLITERAL : '0' X HEX_DIGIT+ ;
1086+
BINARY_LITERAL : [xX] '\'' HEX_DIGIT+ '\''
1087+
| [xX] '"' HEX_DIGIT+ '"'
1088+
;
10681089
10691090
IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ;
10701091

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java

+76
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,8 @@ public List<JpaQueryParsingToken> visitLiteral(HqlParser.LiteralContext ctx) {
10471047
return visit(ctx.numericLiteral());
10481048
} else if (ctx.dateTimeLiteral() != null) {
10491049
return visit(ctx.dateTimeLiteral());
1050+
} else if (ctx.binaryLiteral() != null) {
1051+
return visit(ctx.binaryLiteral());
10501052
} else {
10511053
return List.of();
10521054
}
@@ -1135,6 +1137,56 @@ public List<JpaQueryParsingToken> visitDateTimeLiteral(HqlParser.DateTimeLiteral
11351137
return tokens;
11361138
}
11371139

1140+
@Override
1141+
public List<JpaQueryParsingToken> visitDatetimeField(HqlParser.DatetimeFieldContext ctx) {
1142+
1143+
if (ctx.YEAR() != null) {
1144+
return List.of(new JpaQueryParsingToken(ctx.YEAR()));
1145+
} else if (ctx.MONTH() != null) {
1146+
return List.of(new JpaQueryParsingToken(ctx.MONTH()));
1147+
} else if (ctx.DAY() != null) {
1148+
return List.of(new JpaQueryParsingToken(ctx.DAY()));
1149+
} else if (ctx.WEEK() != null) {
1150+
return List.of(new JpaQueryParsingToken(ctx.WEEK()));
1151+
} else if (ctx.QUARTER() != null) {
1152+
return List.of(new JpaQueryParsingToken(ctx.QUARTER()));
1153+
} else if (ctx.HOUR() != null) {
1154+
return List.of(new JpaQueryParsingToken(ctx.HOUR()));
1155+
} else if (ctx.MINUTE() != null) {
1156+
return List.of(new JpaQueryParsingToken(ctx.MINUTE()));
1157+
} else if (ctx.SECOND() != null) {
1158+
return List.of(new JpaQueryParsingToken(ctx.SECOND()));
1159+
} else if (ctx.NANOSECOND() != null) {
1160+
return List.of(new JpaQueryParsingToken(ctx.NANOSECOND()));
1161+
} else if (ctx.EPOCH() != null) {
1162+
return List.of(new JpaQueryParsingToken(ctx.EPOCH()));
1163+
} else {
1164+
return List.of();
1165+
}
1166+
}
1167+
1168+
@Override
1169+
public List<JpaQueryParsingToken> visitBinaryLiteral(HqlParser.BinaryLiteralContext ctx) {
1170+
1171+
List<JpaQueryParsingToken> tokens = new ArrayList<>();
1172+
1173+
if (ctx.BINARY_LITERAL() != null) {
1174+
tokens.add(new JpaQueryParsingToken(ctx.BINARY_LITERAL()));
1175+
} else if (ctx.HEXLITERAL() != null) {
1176+
1177+
tokens.add(TOKEN_OPEN_BRACE);
1178+
ctx.HEXLITERAL().forEach(terminalNode -> {
1179+
tokens.add(new JpaQueryParsingToken(terminalNode));
1180+
NOSPACE(tokens);
1181+
tokens.add(TOKEN_COMMA);
1182+
});
1183+
CLIP(tokens);
1184+
tokens.add(TOKEN_CLOSE_BRACE);
1185+
}
1186+
1187+
return tokens;
1188+
}
1189+
11381190
@Override
11391191
public List<JpaQueryParsingToken> visitPlainPrimaryExpression(HqlParser.PlainPrimaryExpressionContext ctx) {
11401192
return visit(ctx.primaryExpression());
@@ -1177,6 +1229,7 @@ public List<JpaQueryParsingToken> visitGroupedExpression(HqlParser.GroupedExpres
11771229

11781230
tokens.add(TOKEN_OPEN_PAREN);
11791231
tokens.addAll(visit(ctx.expression()));
1232+
NOSPACE(tokens);
11801233
tokens.add(TOKEN_CLOSE_PAREN);
11811234

11821235
return tokens;
@@ -1242,6 +1295,29 @@ public List<JpaQueryParsingToken> visitSignedExpression(HqlParser.SignedExpressi
12421295
return tokens;
12431296
}
12441297

1298+
@Override
1299+
public List<JpaQueryParsingToken> visitToDurationExpression(HqlParser.ToDurationExpressionContext ctx) {
1300+
1301+
List<JpaQueryParsingToken> tokens = new ArrayList<>();
1302+
1303+
tokens.addAll(visit(ctx.expression()));
1304+
tokens.addAll(visit(ctx.datetimeField()));
1305+
1306+
return tokens;
1307+
}
1308+
1309+
@Override
1310+
public List<JpaQueryParsingToken> visitFromDurationExpression(HqlParser.FromDurationExpressionContext ctx) {
1311+
1312+
List<JpaQueryParsingToken> tokens = new ArrayList<>();
1313+
1314+
tokens.addAll(visit(ctx.expression()));
1315+
tokens.add(new JpaQueryParsingToken(ctx.BY()));
1316+
tokens.addAll(visit(ctx.datetimeField()));
1317+
1318+
return tokens;
1319+
}
1320+
12451321
@Override
12461322
public List<JpaQueryParsingToken> visitCaseExpression(HqlParser.CaseExpressionContext ctx) {
12471323
return visit(ctx.caseList());

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryParsingToken.java

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class JpaQueryParsingToken {
4848
public static final JpaQueryParsingToken TOKEN_CLOSE_SQUARE_BRACKET = new JpaQueryParsingToken("]");
4949
public static final JpaQueryParsingToken TOKEN_COLON = new JpaQueryParsingToken(":", false);
5050
public static final JpaQueryParsingToken TOKEN_QUESTION_MARK = new JpaQueryParsingToken("?", false);
51+
public static final JpaQueryParsingToken TOKEN_OPEN_BRACE = new JpaQueryParsingToken("{", false);
5152
public static final JpaQueryParsingToken TOKEN_CLOSE_BRACE = new JpaQueryParsingToken("}");
5253
public static final JpaQueryParsingToken TOKEN_CLOSE_SQUARE_BRACKET_BRACE = new JpaQueryParsingToken("]}");
5354
public static final JpaQueryParsingToken TOKEN_CLOSE_PAREN_BRACE = new JpaQueryParsingToken(")}");

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java

+13
Original file line numberDiff line numberDiff line change
@@ -1526,4 +1526,17 @@ void queryWithSignShouldWork() {
15261526
void castFunctionWithFqdnShouldWork() {
15271527
assertQuery("SELECT o FROM Order o WHERE CAST(:userId AS java.util.UUID) IS NULL OR o.user.id = :userId");
15281528
}
1529+
1530+
@Test // GH-3025
1531+
void durationLiteralsShouldWork() {
1532+
assertQuery("SELECT ce.id FROM CalendarEvent ce WHERE (ce.endDate - ce.startDate) > 5 MINUTE");
1533+
}
1534+
1535+
@Test // GH-3025
1536+
void binaryLiteralsShouldWork() {
1537+
1538+
assertQuery("SELECT ce.id FROM CalendarEvent ce WHERE ce.value = {0xDE, 0xAD, 0xBE, 0xEF}");
1539+
assertQuery("SELECT ce.id FROM CalendarEvent ce WHERE ce.value = X'DEADBEEF'");
1540+
assertQuery("SELECT ce.id FROM CalendarEvent ce WHERE ce.value = x'deadbeef'");
1541+
}
15291542
}

0 commit comments

Comments
 (0)