Skip to content

Commit 4c263f1

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 71ca324 commit 4c263f1

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
*
@@ -2143,7 +2145,7 @@ public List<JpaQueryParsingToken> visitConstructor_name(JpqlParser.Constructor_n
21432145

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

2146-
tokens.addAll(visit(ctx.state_field_path_expression()));
2148+
tokens.addAll(visit(ctx.entity_name()));
21472149
NOSPACE(tokens);
21482150

21492151
return tokens;
@@ -2296,8 +2298,8 @@ public List<JpaQueryParsingToken> visitEntity_name(JpqlParser.Entity_nameContext
22962298

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

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

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
/**
@@ -54,6 +58,10 @@ private static String parseWithoutChanges(String query) {
5458
return render(new JpqlQueryRenderer().visit(parsedQuery));
5559
}
5660

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

5967
String slimmedDownQuery = reduceWhitespace(query);
@@ -1017,4 +1025,14 @@ void signedLiteralShouldWork(String query) {
10171025
void signedExpressionsShouldWork(String query) {
10181026
assertQuery(query);
10191027
}
1028+
1029+
@ParameterizedTest // GH-3451
1030+
@MethodSource({"reservedWords"})
1031+
void entityNameWithPackageContainingReservedWord(String reservedWord) {
1032+
1033+
String source = "select new com.company.%s.thing.stuff.ClassName(e.id) from Experience e".formatted(reservedWord);
1034+
assertQuery(source);
1035+
}
1036+
1037+
10201038
}

0 commit comments

Comments
 (0)