From 9c4273cf349a0f89a638d8096f59dbeaccf21d52 Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Mon, 20 Mar 2023 16:14:56 -0500 Subject: [PATCH] Recognize RIGHT, LEFT, OUTER, INNER and FULL as JPQL/HQL reserved words. RIGHT, LEFT, OUTER, INNER, and FULL are HQL tokens. This means they also need to be recognized as potential reserved words and thus possibly identifiers. This can show up if someone, for example, uses "right" as the name of a relationship in a JPA entity. Resolves #2864. Original pull request: #2874. --- .../data/jpa/repository/query/Hql.g4 | 10 ++--- .../data/jpa/repository/query/Jpql.g4 | 3 ++ .../query/HqlQueryTransformerTests.java | 38 +++++++++++++++++++ .../query/JpqlQueryTransformerTests.java | 38 +++++++++++++++++++ 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 index 568f5a235..c429687b7 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Hql.g4 @@ -692,7 +692,7 @@ reservedWord | FOR | FORMAT | FROM -// | FULL + | FULL | FUNCTION | GROUP | GROUPS @@ -704,7 +704,7 @@ reservedWord | IN | INDEX | INDICES -// | INNER + | INNER | INSERT | INSTANT | INTERSECT @@ -714,7 +714,7 @@ reservedWord | KEY | LAST | LEADING -// | LEFT + | LEFT | LIKE | LIMIT | LIST @@ -752,7 +752,7 @@ reservedWord | OR | ORDER | OTHERS -// | OUTER + | OUTER | OVER | OVERFLOW | OVERLAY @@ -765,7 +765,7 @@ reservedWord | QUARTER | RANGE | RESPECT -// | RIGHT + | RIGHT | ROLLUP | ROW | ROWS diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 index 2b5cc7ca4..9bad5f4c7 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4 @@ -599,6 +599,9 @@ identification_variable | ORDER // Gap in the spec requires supporting 'Order' as an entity name | COUNT // Gap in the spec requires supporting 'count' as a possible name | KEY // Gap in the sepc requires supported 'key' as a possible name + | LEFT + | INNER + | OUTER ; constructor_name diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java index 56cb2c042..f70b87540 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java @@ -18,9 +18,13 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; import java.util.regex.Pattern; +import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; 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.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.JpaSort; @@ -808,6 +812,30 @@ class HqlQueryTransformerTests { "select count(b) FROM BookError b WHERE portal = :portal"); } + @ParameterizedTest + @MethodSource("queriesWithReservedWordsAsIdentifiers") // GH-2864 + void usingReservedWordAsRelationshipNameShouldWork(String relationshipName, String joinAlias) { + + HqlQueryParser.parseQuery(String.format(""" + select u + from UserAccountEntity u + join fetch u.lossInspectorLimitConfiguration lil + join fetch u.companyTeam ct + where exists ( + select iu + from UserAccountEntity iu + join iu.roles u2r + join u2r.role r + join r.rights r2r + join r2r.%s %s + where + %s.code = :rightCode + and iu = u + ) + and ct.id = :teamId + """, relationshipName, joinAlias, joinAlias)); + } + @Test // GH-2508 void detectAliasWithCastCorrectly() { @@ -819,6 +847,16 @@ class HqlQueryTransformerTests { .isEqualTo("u"); } + static Stream queriesWithReservedWordsAsIdentifiers() { + + return Stream.of( // + Arguments.of("right", "rt"), // + Arguments.of("left", "lt"), // + Arguments.of("outer", "ou"), // + Arguments.of("full", "full"), // + Arguments.of("inner", "inr")); + } + private void assertCountQuery(String originalQuery, String countQuery) { assertThat(createCountQueryFor(originalQuery)).isEqualTo(countQuery); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java index d63cbd26d..8ebcfa83e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java @@ -18,9 +18,13 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; import java.util.regex.Pattern; +import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; 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.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.JpaSort; @@ -679,6 +683,40 @@ class JpqlQueryTransformerTests { assertThat(alias("select u from User as u left join u.roles as r")).isEqualTo("u"); } + @ParameterizedTest + @MethodSource("queriesWithReservedWordsAsIdentifiers") // GH-2864 + void usingReservedWordAsRelationshipNameShouldWork(String relationshipName, String joinAlias) { + + JpqlQueryParser.parseQuery(String.format(""" + select u + from UserAccountEntity u + join u.lossInspectorLimitConfiguration lil + join u.companyTeam ct + where exists ( + select iu + from UserAccountEntity iu + join iu.roles u2r + join u2r.role r + join r.rights r2r + join r2r.inner inr + where + inr.code = :rightCode + and iu = u + ) + and ct.id = :teamId + """, relationshipName, joinAlias, joinAlias)); + } + + static Stream queriesWithReservedWordsAsIdentifiers() { + + return Stream.of( // + Arguments.of("right", "rt"), // + Arguments.of("left", "lt"), // + Arguments.of("outer", "ou"), // + Arguments.of("full", "full"), // + Arguments.of("inner", "inr")); + } + private void assertCountQuery(String originalQuery, String countQuery) { assertThat(createCountQueryFor(originalQuery)).isEqualTo(countQuery); }