Add support for JPA 3.2 additions to JPQL.

See: #3136
Original Pull Request: #3695
This commit is contained in:
Greg L. Turnquist
2023-09-01 13:48:34 -05:00
committed by Mark Paluch
parent a3a9c927ed
commit ebda35d385
4 changed files with 169 additions and 30 deletions

View File

@@ -43,7 +43,13 @@ ql_statement
;
select_statement
: select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)?
: select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (setOperator_with_select_statement)*
;
setOperator_with_select_statement
: INTERSECT select_statement
| UNION select_statement
| EXCEPT select_statement
;
update_statement
@@ -443,6 +449,7 @@ string_expression
| aggregate_expression
| case_expression
| function_invocation
| string_expression op='||' string_expression
| string_cast_function
| type_cast_function
| '(' subquery ')'
@@ -908,6 +915,7 @@ ELSE : E L S E;
EMPTY : E M P T Y;
ENTRY : E N T R Y;
ESCAPE : E S C A P E;
EXCEPT : E X C E P T;
EXISTS : E X I S T S;
EXP : E X P;
EXTRACT : E X T R A C T;
@@ -972,6 +980,7 @@ TREAT : T R E A T;
TRIM : T R I M;
TRUE : T R U E;
TYPE : T Y P E;
UNION : U N I O N;
UPDATE : U P D A T E;
UPPER : U P P E R;
VALUE : V A L U E;

View File

@@ -80,6 +80,29 @@ class JpqlQueryRenderer extends JpqlBaseVisitor<QueryTokenStream> {
builder.appendExpression(visit(ctx.orderby_clause()));
}
ctx.setOperator_with_select_statement().forEach(setOperatorWithSelectStatementContext -> {
tokens.addAll(visit(setOperatorWithSelectStatementContext));
});
return tokens;
}
@Override
public List<JpaQueryParsingToken> visitSetOperator_with_select_statement(
JpqlParser.SetOperator_with_select_statementContext ctx) {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
if (ctx.INTERSECT() != null) {
tokens.add(new JpaQueryParsingToken(ctx.INTERSECT()));
} else if (ctx.UNION() != null) {
tokens.add(new JpaQueryParsingToken(ctx.UNION()));
} else if (ctx.EXCEPT() != null) {
tokens.add(new JpaQueryParsingToken(ctx.EXCEPT()));
}
tokens.addAll(visit(ctx.select_statement()));
return builder;
}
@@ -800,6 +823,25 @@ class JpqlQueryRenderer extends JpqlBaseVisitor<QueryTokenStream> {
if (ctx.nullsPrecedence() != null) {
builder.append(visit(ctx.nullsPrecedence()));
}
if (ctx.nullsPrecedence() != null) {
tokens.addAll(visit(ctx.nullsPrecedence()));
}
return tokens;
}
@Override
public List<JpaQueryParsingToken> visitNullsPrecedence(JpqlParser.NullsPrecedenceContext ctx) {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
tokens.add(new JpaQueryParsingToken(ctx.NULLS()));
if (ctx.FIRST() != null) {
tokens.add(new JpaQueryParsingToken(ctx.FIRST()));
} else if (ctx.LAST() != null) {
tokens.add(new JpaQueryParsingToken(ctx.LAST()));
}
return builder;
}
@@ -1518,6 +1560,11 @@ class JpqlQueryRenderer extends JpqlBaseVisitor<QueryTokenStream> {
builder.append(visit(ctx.type_cast_function()));
} else if (ctx.function_invocation() != null) {
builder.append(visit(ctx.function_invocation()));
} else if (ctx.op != null) {
tokens.addAll(visit(ctx.string_expression(0)));
tokens.add(new JpaQueryParsingToken(ctx.op));
tokens.addAll(visit(ctx.string_expression(1)));
} else if (ctx.subquery() != null) {
builder.append(TOKEN_OPEN_PAREN);

View File

@@ -1651,29 +1651,25 @@ class HqlQueryRendererTests {
@Test // GH-2962
void orderByWithNullsFirstOrLastShouldWork() {
assertThatNoException().isThrownBy(() -> {
parseWithoutChanges("""
select a,
case
when a.geaendertAm is null then a.erstelltAm
else a.geaendertAm end as mutationAm
from Element a
where a.erstelltDurch = :variable
order by mutationAm desc nulls first
""");
});
assertQuery("""
select a,
case
when a.geaendertAm is null then a.erstelltAm
else a.geaendertAm end as mutationAm
from Element a
where a.erstelltDurch = :variable
order by mutationAm desc nulls first
""");
assertThatNoException().isThrownBy(() -> {
parseWithoutChanges("""
select a,
case
when a.geaendertAm is null then a.erstelltAm
else a.geaendertAm end as mutationAm
from Element a
where a.erstelltDurch = :variable
order by mutationAm desc nulls last
assertQuery("""
select a,
case
when a.geaendertAm is null then a.erstelltAm
else a.geaendertAm end as mutationAm
from Element a
where a.erstelltDurch = :variable
order by mutationAm desc nulls last
""");
});
}
@Test // GH-3882
@@ -1693,14 +1689,12 @@ class HqlQueryRendererTests {
@Test // GH-2964
void roundFunctionShouldWorkLikeAnyOtherFunction() {
assertThatNoException().isThrownBy(() -> {
parseWithoutChanges("""
select round(count(ri) * 100 / max(ri.receipt.positions), 0) as perc
from StockOrderItem oi
right join StockReceiptItem ri
on ri.article = oi.article
""");
});
assertQuery("""
select round(count(ri)*100/max(ri.receipt.positions), 0) as perc
from StockOrderItem oi
right join StockReceiptItem ri
on ri.article = oi.article
""");
}
@Test // GH-3711
@@ -1883,6 +1877,42 @@ class HqlQueryRendererTests {
assertQuery("select e.power.id from MyEntity e");
}
@Test // GH-3136
void doublePipeShouldBeValidAsAStringConcatOperator() {
assertQuery("""
select e.name || ' ' || e.title
from Employee e
""");
}
@Test // GH-3136
void additionalStringOperationsShouldWork() {
assertQuery("""
select
replace(e.name, 'Baggins', 'Proudfeet'),
left(e.role, 4),
right(e.home, 5),
cast(e.distance_from_home, int)
from Employee e
""");
}
@Test // GH-3136
void combinedSelectStatementsShouldWork() {
assertQuery("""
select e from Employee e where e.last_name = 'Baggins'
intersect
select e from Employee e where e.first_name = 'Samwise'
union
select e from Employee e where e.home = 'The Shire'
except
select e from Employee e where e.home = 'Isengard'
""");
}
@Test // GH-3219
void extractFunctionShouldSupportAdditionalExtensions() {

View File

@@ -1034,6 +1034,59 @@ class JpqlQueryRendererTests {
assertQuery("select e.power.id from MyEntity e");
}
@Test // GH-3136
void doublePipeShouldBeValidAsAStringConcatOperator() {
assertQuery("""
select e.name || ' ' || e.title
from Employee e
""");
}
@Test // GH-3136
void combinedSelectStatementsShouldWork() {
assertQuery("""
select e from Employee e where e.last_name = 'Baggins'
intersect
select e from Employee e where e.first_name = 'Samwise'
union
select e from Employee e where e.home = 'The Shire'
except
select e from Employee e where e.home = 'Isengard'
""");
}
@Disabled
@Test // GH-3136
void additionalStringOperationsShouldWork() {
assertQuery("""
select
replace(e.name, 'Baggins', 'Proudfeet'),
left(e.role, 4),
right(e.home, 5),
cast(e.distance_from_home, int)
from Employee e
""");
}
@Test // GH-3136
void orderByWithNullsFirstOrLastShouldWork() {
assertQuery("""
select a
from Element a
order by mutationAm desc nulls first
""");
assertQuery("""
select a
from Element a
order by mutationAm desc nulls last
""");
}
@ParameterizedTest // GH-3342
@ValueSource(strings = { "select 1 as value from User u", "select -1 as value from User u",
"select +1 as value from User u", "select +1 * -100 as value from User u",