Skip to content

Commit ebda35d

Browse files
gregturnmp911de
authored andcommitted
Add support for JPA 3.2 additions to JPQL.
See: #3136 Original Pull Request: #3695
1 parent a3a9c92 commit ebda35d

File tree

4 files changed

+170
-31
lines changed

4 files changed

+170
-31
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ ql_statement
4343
;
4444

4545
select_statement
46-
: select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)?
46+
: select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (setOperator_with_select_statement)*
47+
;
48+
49+
setOperator_with_select_statement
50+
: INTERSECT select_statement
51+
| UNION select_statement
52+
| EXCEPT select_statement
4753
;
4854

4955
update_statement
@@ -443,6 +449,7 @@ string_expression
443449
| aggregate_expression
444450
| case_expression
445451
| function_invocation
452+
| string_expression op='||' string_expression
446453
| string_cast_function
447454
| type_cast_function
448455
| '(' subquery ')'
@@ -908,6 +915,7 @@ ELSE : E L S E;
908915
EMPTY : E M P T Y;
909916
ENTRY : E N T R Y;
910917
ESCAPE : E S C A P E;
918+
EXCEPT : E X C E P T;
911919
EXISTS : E X I S T S;
912920
EXP : E X P;
913921
EXTRACT : E X T R A C T;
@@ -972,6 +980,7 @@ TREAT : T R E A T;
972980
TRIM : T R I M;
973981
TRUE : T R U E;
974982
TYPE : T Y P E;
983+
UNION : U N I O N;
975984
UPDATE : U P D A T E;
976985
UPPER : U P P E R;
977986
VALUE : V A L U E;

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,29 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext
8080
builder.appendExpression(visit(ctx.orderby_clause()));
8181
}
8282

83+
ctx.setOperator_with_select_statement().forEach(setOperatorWithSelectStatementContext -> {
84+
tokens.addAll(visit(setOperatorWithSelectStatementContext));
85+
});
86+
87+
return tokens;
88+
}
89+
90+
@Override
91+
public List<JpaQueryParsingToken> visitSetOperator_with_select_statement(
92+
JpqlParser.SetOperator_with_select_statementContext ctx) {
93+
94+
List<JpaQueryParsingToken> tokens = new ArrayList<>();
95+
96+
if (ctx.INTERSECT() != null) {
97+
tokens.add(new JpaQueryParsingToken(ctx.INTERSECT()));
98+
} else if (ctx.UNION() != null) {
99+
tokens.add(new JpaQueryParsingToken(ctx.UNION()));
100+
} else if (ctx.EXCEPT() != null) {
101+
tokens.add(new JpaQueryParsingToken(ctx.EXCEPT()));
102+
}
103+
104+
tokens.addAll(visit(ctx.select_statement()));
105+
83106
return builder;
84107
}
85108

@@ -800,6 +823,25 @@ public QueryTokenStream visitOrderby_item(JpqlParser.Orderby_itemContext ctx) {
800823
if (ctx.nullsPrecedence() != null) {
801824
builder.append(visit(ctx.nullsPrecedence()));
802825
}
826+
if (ctx.nullsPrecedence() != null) {
827+
tokens.addAll(visit(ctx.nullsPrecedence()));
828+
}
829+
830+
return tokens;
831+
}
832+
833+
@Override
834+
public List<JpaQueryParsingToken> visitNullsPrecedence(JpqlParser.NullsPrecedenceContext ctx) {
835+
836+
List<JpaQueryParsingToken> tokens = new ArrayList<>();
837+
838+
tokens.add(new JpaQueryParsingToken(ctx.NULLS()));
839+
840+
if (ctx.FIRST() != null) {
841+
tokens.add(new JpaQueryParsingToken(ctx.FIRST()));
842+
} else if (ctx.LAST() != null) {
843+
tokens.add(new JpaQueryParsingToken(ctx.LAST()));
844+
}
803845

804846
return builder;
805847
}
@@ -1518,6 +1560,11 @@ public QueryTokenStream visitString_expression(JpqlParser.String_expressionConte
15181560
builder.append(visit(ctx.type_cast_function()));
15191561
} else if (ctx.function_invocation() != null) {
15201562
builder.append(visit(ctx.function_invocation()));
1563+
} else if (ctx.op != null) {
1564+
1565+
tokens.addAll(visit(ctx.string_expression(0)));
1566+
tokens.add(new JpaQueryParsingToken(ctx.op));
1567+
tokens.addAll(visit(ctx.string_expression(1)));
15211568
} else if (ctx.subquery() != null) {
15221569

15231570
builder.append(TOKEN_OPEN_PAREN);

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

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,29 +1651,25 @@ void hqlQueries() {
16511651
@Test // GH-2962
16521652
void orderByWithNullsFirstOrLastShouldWork() {
16531653

1654-
assertThatNoException().isThrownBy(() -> {
1655-
parseWithoutChanges("""
1656-
select a,
1657-
case
1658-
when a.geaendertAm is null then a.erstelltAm
1659-
else a.geaendertAm end as mutationAm
1660-
from Element a
1661-
where a.erstelltDurch = :variable
1662-
order by mutationAm desc nulls first
1663-
""");
1664-
});
1665-
1666-
assertThatNoException().isThrownBy(() -> {
1667-
parseWithoutChanges("""
1668-
select a,
1669-
case
1670-
when a.geaendertAm is null then a.erstelltAm
1671-
else a.geaendertAm end as mutationAm
1672-
from Element a
1673-
where a.erstelltDurch = :variable
1674-
order by mutationAm desc nulls last
1654+
assertQuery("""
1655+
select a,
1656+
case
1657+
when a.geaendertAm is null then a.erstelltAm
1658+
else a.geaendertAm end as mutationAm
1659+
from Element a
1660+
where a.erstelltDurch = :variable
1661+
order by mutationAm desc nulls first
1662+
""");
1663+
1664+
assertQuery("""
1665+
select a,
1666+
case
1667+
when a.geaendertAm is null then a.erstelltAm
1668+
else a.geaendertAm end as mutationAm
1669+
from Element a
1670+
where a.erstelltDurch = :variable
1671+
order by mutationAm desc nulls last
16751672
""");
1676-
});
16771673
}
16781674

16791675
@Test // GH-3882
@@ -1693,14 +1689,12 @@ void shouldSupportLimitOffset() {
16931689
@Test // GH-2964
16941690
void roundFunctionShouldWorkLikeAnyOtherFunction() {
16951691

1696-
assertThatNoException().isThrownBy(() -> {
1697-
parseWithoutChanges("""
1698-
select round(count(ri) * 100 / max(ri.receipt.positions), 0) as perc
1699-
from StockOrderItem oi
1700-
right join StockReceiptItem ri
1701-
on ri.article = oi.article
1702-
""");
1703-
});
1692+
assertQuery("""
1693+
select round(count(ri)*100/max(ri.receipt.positions), 0) as perc
1694+
from StockOrderItem oi
1695+
right join StockReceiptItem ri
1696+
on ri.article = oi.article
1697+
""");
17041698
}
17051699

17061700
@Test // GH-3711
@@ -1883,6 +1877,42 @@ void powerShouldBeLegalInAQuery() {
18831877
assertQuery("select e.power.id from MyEntity e");
18841878
}
18851879

1880+
@Test // GH-3136
1881+
void doublePipeShouldBeValidAsAStringConcatOperator() {
1882+
1883+
assertQuery("""
1884+
select e.name || ' ' || e.title
1885+
from Employee e
1886+
""");
1887+
}
1888+
1889+
@Test // GH-3136
1890+
void additionalStringOperationsShouldWork() {
1891+
1892+
assertQuery("""
1893+
select
1894+
replace(e.name, 'Baggins', 'Proudfeet'),
1895+
left(e.role, 4),
1896+
right(e.home, 5),
1897+
cast(e.distance_from_home, int)
1898+
from Employee e
1899+
""");
1900+
}
1901+
1902+
@Test // GH-3136
1903+
void combinedSelectStatementsShouldWork() {
1904+
1905+
assertQuery("""
1906+
select e from Employee e where e.last_name = 'Baggins'
1907+
intersect
1908+
select e from Employee e where e.first_name = 'Samwise'
1909+
union
1910+
select e from Employee e where e.home = 'The Shire'
1911+
except
1912+
select e from Employee e where e.home = 'Isengard'
1913+
""");
1914+
}
1915+
18861916
@Test // GH-3219
18871917
void extractFunctionShouldSupportAdditionalExtensions() {
18881918

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,59 @@ void powerShouldBeLegalInAQuery() {
10341034
assertQuery("select e.power.id from MyEntity e");
10351035
}
10361036

1037+
@Test // GH-3136
1038+
void doublePipeShouldBeValidAsAStringConcatOperator() {
1039+
1040+
assertQuery("""
1041+
select e.name || ' ' || e.title
1042+
from Employee e
1043+
""");
1044+
}
1045+
1046+
@Test // GH-3136
1047+
void combinedSelectStatementsShouldWork() {
1048+
1049+
assertQuery("""
1050+
select e from Employee e where e.last_name = 'Baggins'
1051+
intersect
1052+
select e from Employee e where e.first_name = 'Samwise'
1053+
union
1054+
select e from Employee e where e.home = 'The Shire'
1055+
except
1056+
select e from Employee e where e.home = 'Isengard'
1057+
""");
1058+
}
1059+
1060+
@Disabled
1061+
@Test // GH-3136
1062+
void additionalStringOperationsShouldWork() {
1063+
1064+
assertQuery("""
1065+
select
1066+
replace(e.name, 'Baggins', 'Proudfeet'),
1067+
left(e.role, 4),
1068+
right(e.home, 5),
1069+
cast(e.distance_from_home, int)
1070+
from Employee e
1071+
""");
1072+
}
1073+
1074+
@Test // GH-3136
1075+
void orderByWithNullsFirstOrLastShouldWork() {
1076+
1077+
assertQuery("""
1078+
select a
1079+
from Element a
1080+
order by mutationAm desc nulls first
1081+
""");
1082+
1083+
assertQuery("""
1084+
select a
1085+
from Element a
1086+
order by mutationAm desc nulls last
1087+
""");
1088+
}
1089+
10371090
@ParameterizedTest // GH-3342
10381091
@ValueSource(strings = { "select 1 as value from User u", "select -1 as value from User u",
10391092
"select +1 as value from User u", "select +1 * -100 as value from User u",

0 commit comments

Comments
 (0)