Skip to content

Commit 785f9f5

Browse files
christophstroblmp911de
authored andcommitted
Fix parsing entity names in JPQL query with package names that contain reserved words.
Resolves: #3451 Original pull request: #3457
1 parent b9417e5 commit 785f9f5

File tree

3 files changed

+120
-5
lines changed

3 files changed

+120
-5
lines changed

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

+86-2
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ identification_variable
616616
;
617617

618618
constructor_name
619-
: state_field_path_expression
619+
: entity_name
620620
;
621621

622622
literal
@@ -696,7 +696,7 @@ collection_value_field
696696
;
697697

698698
entity_name
699-
: identification_variable ('.' identification_variable)* // Hibernate sometimes expands the entity name to FQDN when using named queries
699+
: reserved_word ('.' reserved_word)* // Hibernate sometimes expands the entity name to FQDN when using named queries
700700
;
701701

702702
result_variable
@@ -724,6 +724,90 @@ character_valued_input_parameter
724724
| input_parameter
725725
;
726726

727+
reserved_word
728+
: IDENTIFICATION_VARIABLE
729+
| f=(ABS
730+
|ALL
731+
|AND
732+
|ANY
733+
|AS
734+
|ASC
735+
|AVG
736+
|BETWEEN
737+
|BOTH
738+
|BY
739+
|CASE
740+
|CEILING
741+
|COALESCE
742+
|CONCAT
743+
|COUNT
744+
|CURRENT_DATE
745+
|CURRENT_TIME
746+
|CURRENT_TIMESTAMP
747+
|DATE
748+
|DATETIME
749+
|DELETE
750+
|DESC
751+
|DISTINCT
752+
|END
753+
|ELSE
754+
|EMPTY
755+
|ENTRY
756+
|ESCAPE
757+
|EXISTS
758+
|EXP
759+
|EXTRACT
760+
|FALSE
761+
|FETCH
762+
|FLOOR
763+
|FUNCTION
764+
|IN
765+
|INDEX
766+
|INNER
767+
|IS
768+
|KEY
769+
|LEFT
770+
|LENGTH
771+
|LIKE
772+
|LN
773+
|LOCAL
774+
|LOCATE
775+
|LOWER
776+
|MAX
777+
|MEMBER
778+
|MIN
779+
|MOD
780+
|NEW
781+
|NOT
782+
|NULL
783+
|NULLIF
784+
|OBJECT
785+
|OF
786+
|ON
787+
|OR
788+
|ORDER
789+
|OUTER
790+
|POWER
791+
|ROUND
792+
|SELECT
793+
|SET
794+
|SIGN
795+
|SIZE
796+
|SOME
797+
|SQRT
798+
|SUBSTRING
799+
|SUM
800+
|THEN
801+
|TIME
802+
|TRAILING
803+
|TREAT
804+
|TRIM
805+
|TRUE
806+
|TYPE
807+
|UPDATE
808+
|UPPER
809+
|VALUE)
810+
;
727811
/*
728812
Lexer rules
729813
*/

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

+16-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.ArrayList;
2121
import java.util.List;
2222

23+
import org.springframework.data.jpa.repository.query.JpqlParser.Reserved_wordContext;
24+
2325
/**
2426
* An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders a JPQL query without making any changes.
2527
*
@@ -2142,7 +2144,7 @@ public List<JpaQueryParsingToken> visitConstructor_name(JpqlParser.Constructor_n
21422144

21432145
List<JpaQueryParsingToken> tokens = new ArrayList<>();
21442146

2145-
tokens.addAll(visit(ctx.state_field_path_expression()));
2147+
tokens.addAll(visit(ctx.entity_name()));
21462148
NOSPACE(tokens);
21472149

21482150
return tokens;
@@ -2295,8 +2297,8 @@ public List<JpaQueryParsingToken> visitEntity_name(JpqlParser.Entity_nameContext
22952297

22962298
List<JpaQueryParsingToken> tokens = new ArrayList<>();
22972299

2298-
ctx.identification_variable().forEach(identificationVariableContext -> {
2299-
tokens.addAll(visit(identificationVariableContext));
2300+
ctx.reserved_word().forEach(ctx2 -> {
2301+
tokens.addAll(visitReserved_word(ctx2));
23002302
NOSPACE(tokens);
23012303
tokens.add(TOKEN_DOT);
23022304
});
@@ -2346,4 +2348,15 @@ public List<JpaQueryParsingToken> visitCharacter_valued_input_parameter(
23462348
return List.of();
23472349
}
23482350
}
2351+
2352+
@Override
2353+
public List<JpaQueryParsingToken> visitReserved_word(Reserved_wordContext ctx) {
2354+
if (ctx.IDENTIFICATION_VARIABLE() != null) {
2355+
return List.of(new JpaQueryParsingToken(ctx.IDENTIFICATION_VARIABLE()));
2356+
} else if (ctx.f != null) {
2357+
return List.of(new JpaQueryParsingToken(ctx.f));
2358+
} else {
2359+
return List.of();
2360+
}
2361+
}
23492362
}

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

+18
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
2020

21+
import java.util.stream.Stream;
22+
2123
import org.antlr.v4.runtime.CharStreams;
2224
import org.antlr.v4.runtime.CommonTokenStream;
2325
import org.junit.jupiter.api.Disabled;
2426
import org.junit.jupiter.api.Test;
2527
import org.junit.jupiter.params.ParameterizedTest;
28+
import org.junit.jupiter.params.provider.Arguments;
29+
import org.junit.jupiter.params.provider.MethodSource;
2630
import org.junit.jupiter.params.provider.ValueSource;
2731

2832
/**
@@ -56,6 +60,10 @@ private static String parseWithoutChanges(String query) {
5660
return render(new JpqlQueryRenderer().visit(parsedQuery));
5761
}
5862

63+
public static Stream<Arguments> reservedWords() {
64+
return Stream.of("abs", "exp", "any", "case", "else", "index", "time").map(Arguments::of);
65+
}
66+
5967
private void assertQuery(String query) {
6068

6169
String slimmedDownQuery = reduceWhitespace(query);
@@ -1014,4 +1022,14 @@ void signedLiteralShouldWork(String query) {
10141022
void signedExpressionsShouldWork(String query) {
10151023
assertQuery(query);
10161024
}
1025+
1026+
@ParameterizedTest // GH-3451
1027+
@MethodSource({"reservedWords"})
1028+
void entityNameWithPackageContainingReservedWord(String reservedWord) {
1029+
1030+
String source = "select new com.company.%s.thing.stuff.ClassName(e.id) from Experience e".formatted(reservedWord);
1031+
assertQuery(source);
1032+
}
1033+
1034+
10171035
}

0 commit comments

Comments
 (0)