Fix parsing entity names in JPQL query with package names that contain reserved words.

Resolves: #3451
Original pull request: #3457
This commit is contained in:
Christoph Strobl
2024-04-29 09:54:57 +02:00
committed by Mark Paluch
parent b9417e54e1
commit 785f9f5916
3 changed files with 120 additions and 5 deletions

View File

@@ -616,7 +616,7 @@ identification_variable
;
constructor_name
: state_field_path_expression
: entity_name
;
literal
@@ -696,7 +696,7 @@ collection_value_field
;
entity_name
: identification_variable ('.' identification_variable)* // Hibernate sometimes expands the entity name to FQDN when using named queries
: reserved_word ('.' reserved_word)* // Hibernate sometimes expands the entity name to FQDN when using named queries
;
result_variable
@@ -724,6 +724,90 @@ character_valued_input_parameter
| input_parameter
;
reserved_word
: IDENTIFICATION_VARIABLE
| f=(ABS
|ALL
|AND
|ANY
|AS
|ASC
|AVG
|BETWEEN
|BOTH
|BY
|CASE
|CEILING
|COALESCE
|CONCAT
|COUNT
|CURRENT_DATE
|CURRENT_TIME
|CURRENT_TIMESTAMP
|DATE
|DATETIME
|DELETE
|DESC
|DISTINCT
|END
|ELSE
|EMPTY
|ENTRY
|ESCAPE
|EXISTS
|EXP
|EXTRACT
|FALSE
|FETCH
|FLOOR
|FUNCTION
|IN
|INDEX
|INNER
|IS
|KEY
|LEFT
|LENGTH
|LIKE
|LN
|LOCAL
|LOCATE
|LOWER
|MAX
|MEMBER
|MIN
|MOD
|NEW
|NOT
|NULL
|NULLIF
|OBJECT
|OF
|ON
|OR
|ORDER
|OUTER
|POWER
|ROUND
|SELECT
|SET
|SIGN
|SIZE
|SOME
|SQRT
|SUBSTRING
|SUM
|THEN
|TIME
|TRAILING
|TREAT
|TRIM
|TRUE
|TYPE
|UPDATE
|UPPER
|VALUE)
;
/*
Lexer rules
*/

View File

@@ -20,6 +20,8 @@ import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.jpa.repository.query.JpqlParser.Reserved_wordContext;
/**
* An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders a JPQL query without making any changes.
*
@@ -2142,7 +2144,7 @@ class JpqlQueryRenderer extends JpqlBaseVisitor<List<JpaQueryParsingToken>> {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
tokens.addAll(visit(ctx.state_field_path_expression()));
tokens.addAll(visit(ctx.entity_name()));
NOSPACE(tokens);
return tokens;
@@ -2295,8 +2297,8 @@ class JpqlQueryRenderer extends JpqlBaseVisitor<List<JpaQueryParsingToken>> {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
ctx.identification_variable().forEach(identificationVariableContext -> {
tokens.addAll(visit(identificationVariableContext));
ctx.reserved_word().forEach(ctx2 -> {
tokens.addAll(visitReserved_word(ctx2));
NOSPACE(tokens);
tokens.add(TOKEN_DOT);
});
@@ -2346,4 +2348,15 @@ class JpqlQueryRenderer extends JpqlBaseVisitor<List<JpaQueryParsingToken>> {
return List.of();
}
}
@Override
public List<JpaQueryParsingToken> visitReserved_word(Reserved_wordContext ctx) {
if (ctx.IDENTIFICATION_VARIABLE() != null) {
return List.of(new JpaQueryParsingToken(ctx.IDENTIFICATION_VARIABLE()));
} else if (ctx.f != null) {
return List.of(new JpaQueryParsingToken(ctx.f));
} else {
return List.of();
}
}
}

View File

@@ -18,11 +18,15 @@ package org.springframework.data.jpa.repository.query;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
import java.util.stream.Stream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
/**
@@ -56,6 +60,10 @@ class JpqlQueryRendererTests {
return render(new JpqlQueryRenderer().visit(parsedQuery));
}
public static Stream<Arguments> reservedWords() {
return Stream.of("abs", "exp", "any", "case", "else", "index", "time").map(Arguments::of);
}
private void assertQuery(String query) {
String slimmedDownQuery = reduceWhitespace(query);
@@ -1014,4 +1022,14 @@ class JpqlQueryRendererTests {
void signedExpressionsShouldWork(String query) {
assertQuery(query);
}
@ParameterizedTest // GH-3451
@MethodSource({"reservedWords"})
void entityNameWithPackageContainingReservedWord(String reservedWord) {
String source = "select new com.company.%s.thing.stuff.ClassName(e.id) from Experience e".formatted(reservedWord);
assertQuery(source);
}
}