Skip to content

Commit 4b30241

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

File tree

4 files changed

+171
-31
lines changed

4 files changed

+171
-31
lines changed

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

+11-1
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
@@ -439,6 +445,7 @@ string_expression
439445
| aggregate_expression
440446
| case_expression
441447
| function_invocation
448+
| string_expression op='||' string_expression
442449
| '(' subquery ')'
443450
;
444451

@@ -887,6 +894,7 @@ ELSE : E L S E;
887894
EMPTY : E M P T Y;
888895
ENTRY : E N T R Y;
889896
ESCAPE : E S C A P E;
897+
EXCEPT : E X C E P T;
890898
EXISTS : E X I S T S;
891899
EXP : E X P;
892900
EXTRACT : E X T R A C T;
@@ -901,6 +909,7 @@ HAVING : H A V I N G;
901909
IN : I N;
902910
INDEX : I N D E X;
903911
INNER : I N N E R;
912+
INTERSECT : I N T E R S E C T;
904913
IS : I S;
905914
JOIN : J O I N;
906915
KEY : K E Y;
@@ -945,6 +954,7 @@ TREAT : T R E A T;
945954
TRIM : T R I M;
946955
TRUE : T R U E;
947956
TYPE : T Y P E;
957+
UNION : U N I O N;
948958
UPDATE : U P D A T E;
949959
UPPER : U P P E R;
950960
VALUE : V A L U E;

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

+47
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,29 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext
7979
builder.appendExpression(visit(ctx.orderby_clause()));
8080
}
8181

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

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

803845
return builder;
804846
}
@@ -1451,6 +1493,11 @@ public QueryTokenStream visitString_expression(JpqlParser.String_expressionConte
14511493
builder.append(visit(ctx.case_expression()));
14521494
} else if (ctx.function_invocation() != null) {
14531495
builder.append(visit(ctx.function_invocation()));
1496+
} else if (ctx.op != null) {
1497+
1498+
tokens.addAll(visit(ctx.string_expression(0)));
1499+
tokens.add(new JpaQueryParsingToken(ctx.op));
1500+
tokens.addAll(visit(ctx.string_expression(1)));
14541501
} else if (ctx.subquery() != null) {
14551502

14561503
builder.append(TOKEN_OPEN_PAREN);

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

+60-30
Original file line numberDiff line numberDiff line change
@@ -1639,42 +1639,36 @@ void hqlQueries() {
16391639
@Test // GH-2962
16401640
void orderByWithNullsFirstOrLastShouldWork() {
16411641

1642-
assertThatNoException().isThrownBy(() -> {
1643-
parseWithoutChanges("""
1644-
select a,
1645-
case
1646-
when a.geaendertAm is null then a.erstelltAm
1647-
else a.geaendertAm end as mutationAm
1648-
from Element a
1649-
where a.erstelltDurch = :variable
1650-
order by mutationAm desc nulls first
1651-
""");
1652-
});
1653-
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 last
1642+
assertQuery("""
1643+
select a,
1644+
case
1645+
when a.geaendertAm is null then a.erstelltAm
1646+
else a.geaendertAm end as mutationAm
1647+
from Element a
1648+
where a.erstelltDurch = :variable
1649+
order by mutationAm desc nulls first
1650+
""");
1651+
1652+
assertQuery("""
1653+
select a,
1654+
case
1655+
when a.geaendertAm is null then a.erstelltAm
1656+
else a.geaendertAm end as mutationAm
1657+
from Element a
1658+
where a.erstelltDurch = :variable
1659+
order by mutationAm desc nulls last
16631660
""");
1664-
});
16651661
}
16661662

16671663
@Test // GH-2964
16681664
void roundFunctionShouldWorkLikeAnyOtherFunction() {
16691665

1670-
assertThatNoException().isThrownBy(() -> {
1671-
parseWithoutChanges("""
1672-
select round(count(ri) * 100 / max(ri.receipt.positions), 0) as perc
1673-
from StockOrderItem oi
1674-
right join StockReceiptItem ri
1675-
on ri.article = oi.article
1676-
""");
1677-
});
1666+
assertQuery("""
1667+
select round(count(ri)*100/max(ri.receipt.positions), 0) as perc
1668+
from StockOrderItem oi
1669+
right join StockReceiptItem ri
1670+
on ri.article = oi.article
1671+
""");
16781672
}
16791673

16801674
@Test // GH-3711
@@ -1854,6 +1848,42 @@ void powerShouldBeLegalInAQuery() {
18541848
assertQuery("select e.power.id from MyEntity e");
18551849
}
18561850

1851+
@Test // GH-3136
1852+
void doublePipeShouldBeValidAsAStringConcatOperator() {
1853+
1854+
assertQuery("""
1855+
select e.name || ' ' || e.title
1856+
from Employee e
1857+
""");
1858+
}
1859+
1860+
@Test // GH-3136
1861+
void additionalStringOperationsShouldWork() {
1862+
1863+
assertQuery("""
1864+
select
1865+
replace(e.name, 'Baggins', 'Proudfeet'),
1866+
left(e.role, 4),
1867+
right(e.home, 5),
1868+
cast(e.distance_from_home, int)
1869+
from Employee e
1870+
""");
1871+
}
1872+
1873+
@Test // GH-3136
1874+
void combinedSelectStatementsShouldWork() {
1875+
1876+
assertQuery("""
1877+
select e from Employee e where e.last_name = 'Baggins'
1878+
intersect
1879+
select e from Employee e where e.first_name = 'Samwise'
1880+
union
1881+
select e from Employee e where e.home = 'The Shire'
1882+
except
1883+
select e from Employee e where e.home = 'Isengard'
1884+
""");
1885+
}
1886+
18571887
@Test // GH-3219
18581888
void extractFunctionShouldSupportAdditionalExtensions() {
18591889

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

+53
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,59 @@ void powerShouldBeLegalInAQuery() {
10171017
assertQuery("select e.power.id from MyEntity e");
10181018
}
10191019

1020+
@Test // GH-3136
1021+
void doublePipeShouldBeValidAsAStringConcatOperator() {
1022+
1023+
assertQuery("""
1024+
select e.name || ' ' || e.title
1025+
from Employee e
1026+
""");
1027+
}
1028+
1029+
@Test // GH-3136
1030+
void combinedSelectStatementsShouldWork() {
1031+
1032+
assertQuery("""
1033+
select e from Employee e where e.last_name = 'Baggins'
1034+
intersect
1035+
select e from Employee e where e.first_name = 'Samwise'
1036+
union
1037+
select e from Employee e where e.home = 'The Shire'
1038+
except
1039+
select e from Employee e where e.home = 'Isengard'
1040+
""");
1041+
}
1042+
1043+
@Disabled
1044+
@Test // GH-3136
1045+
void additionalStringOperationsShouldWork() {
1046+
1047+
assertQuery("""
1048+
select
1049+
replace(e.name, 'Baggins', 'Proudfeet'),
1050+
left(e.role, 4),
1051+
right(e.home, 5),
1052+
cast(e.distance_from_home, int)
1053+
from Employee e
1054+
""");
1055+
}
1056+
1057+
@Test // GH-3136
1058+
void orderByWithNullsFirstOrLastShouldWork() {
1059+
1060+
assertQuery("""
1061+
select a
1062+
from Element a
1063+
order by mutationAm desc nulls first
1064+
""");
1065+
1066+
assertQuery("""
1067+
select a
1068+
from Element a
1069+
order by mutationAm desc nulls last
1070+
""");
1071+
}
1072+
10201073
@ParameterizedTest // GH-3342
10211074
@ValueSource(strings = { "select 1 as value from User u", "select -1 as value from User u",
10221075
"select +1 as value from User u", "select +1 * -100 as value from User u",

0 commit comments

Comments
 (0)