Skip to content

Commit

Permalink
Add support for JPA 3.2 additions to JPQL.
Browse files Browse the repository at this point in the history
See #3136.
  • Loading branch information
gregturn committed Sep 19, 2023
1 parent 563f1ed commit 46dd7e7
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,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
Expand Down Expand Up @@ -234,7 +240,11 @@ orderby_clause

// TODO Error in spec BNF, correctly shown elsewhere in spec.
orderby_item
: (state_field_path_expression | general_identification_variable | result_variable ) (ASC | DESC)?
: (state_field_path_expression | general_identification_variable | result_variable ) (ASC | DESC)? nullsPrecedence?
;

nullsPrecedence
: NULLS (FIRST | LAST)
;

subquery
Expand Down Expand Up @@ -428,6 +438,7 @@ string_expression
| aggregate_expression
| case_expression
| function_invocation
| string_expression op='||' string_expression
| '(' subquery ')'
;

Expand Down Expand Up @@ -785,11 +796,13 @@ 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;
FALSE : F A L S E;
FETCH : F E T C H;
FIRST : F I R S T;
FLOOR : F L O O R;
FROM : F R O M;
FUNCTION : F U N C T I O N;
Expand All @@ -798,9 +811,11 @@ HAVING : H A V I N G;
IN : I N;
INDEX : I N D E X;
INNER : I N N E R;
INTERSECT : I N T E R S E C T;
IS : I S;
JOIN : J O I N;
KEY : K E Y;
LAST : L A S T;
LEADING : L E A D I N G;
LEFT : L E F T;
LENGTH : L E N G T H;
Expand All @@ -817,6 +832,7 @@ NEW : N E W;
NOT : N O T;
NULL : N U L L;
NULLIF : N U L L I F;
NULLS : N U L L S;
OBJECT : O B J E C T;
OF : O F;
ON : O N;
Expand All @@ -840,6 +856,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,29 @@ public List<JpaQueryParsingToken> visitSelect_statement(JpqlParser.Select_statem
tokens.addAll(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 tokens;
}

Expand Down Expand Up @@ -869,6 +892,25 @@ public List<JpaQueryParsingToken> visitOrderby_item(JpqlParser.Orderby_itemConte
if (ctx.DESC() != null) {
tokens.add(new JpaQueryParsingToken(ctx.DESC()));
}
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 tokens;
}
Expand Down Expand Up @@ -1527,6 +1569,11 @@ public List<JpaQueryParsingToken> visitString_expression(JpqlParser.String_expre
tokens.addAll(visit(ctx.case_expression()));
} else if (ctx.function_invocation() != null) {
tokens.addAll(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) {

tokens.add(TOKEN_OPEN_PAREN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ public List<JpaQueryParsingToken> visitSelect_statement(JpqlParser.Select_statem
}
}

ctx.setOperator_with_select_statement().forEach(setOperatorWithSelectStatementContext -> {
tokens.addAll(visit(setOperatorWithSelectStatementContext));
});

return tokens;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1450,42 +1450,36 @@ void hqlQueries() {
@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
""");
});

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 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 last
""");
});
}

@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-2981
Expand Down Expand Up @@ -1605,4 +1599,40 @@ void newShouldBeLegalAsPartOfAStateFieldPathExpression() {
void powerShouldBeLegalInAQuery() {
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'
""");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -988,4 +988,57 @@ void newShouldBeLegalAsPartOfAStateFieldPathExpression() {
void powerShouldBeLegalInAQuery() {
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
""");
}
}

0 comments on commit 46dd7e7

Please sign in to comment.