From 3103ca292e82411e1001e1b5bdd285ccf9eb701e Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 19 Dec 2023 13:41:12 +0100 Subject: [PATCH 1/9] Prepare issue branch. --- pom.xml | 2 +- spring-data-envers/pom.xml | 4 ++-- spring-data-jpa-distribution/pom.xml | 2 +- spring-data-jpa/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index a3be6252ed..d3d1f65c5c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 3.3.0-SNAPSHOT + 3.3.x-3277-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 470678d048..311a906934 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 3.3.0-SNAPSHOT + 3.3.x-3277-SNAPSHOT org.springframework.data spring-data-jpa-parent - 3.3.0-SNAPSHOT + 3.3.x-3277-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index 6bd074181c..69d44c6176 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 3.3.0-SNAPSHOT + 3.3.x-3277-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index 4a2875c758..25442f5243 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jpa - 3.3.0-SNAPSHOT + 3.3.x-3277-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-jpa-parent - 3.3.0-SNAPSHOT + 3.3.x-3277-SNAPSHOT ../pom.xml From 17ec98b38e07d043087938bc922c1d4a32096733 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 20 Dec 2023 09:31:19 +0100 Subject: [PATCH 2/9] Add testcase --- .../data/jpa/repository/query/QueryEnhancerTckTests.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java index a82d71c21b..3e3465e3d3 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java @@ -164,7 +164,12 @@ static Stream jpqlCountQueries() { Arguments.of( // "select distinct m.genre from Media m where m.user = ?1 order by m.genre asc", // - "select count(distinct m.genre) from Media m where m.user = ?1")); + "select count(distinct m.genre) from Media m where m.user = ?1"), + + Arguments.of( // + "select u from User u where MOD(u.age, 10L) = 2", // + "select count(u) from User u where MOD(u.age, 10L) = 2") + ); } @ParameterizedTest // GH-2511, GH-2773 From d347374642655ef406571bf787c3a7201ae71e6e Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 20 Dec 2023 10:29:39 +0100 Subject: [PATCH 3/9] Fix Jpql Long value parsing --- .../org/springframework/data/jpa/repository/query/Jpql.g4 | 4 ++++ .../data/jpa/repository/query/JpqlQueryRenderer.java | 5 +++++ .../repository/query/JSqlParserQueryEnhancerUnitTests.java | 3 +++ 3 files changed, 12 insertions(+) 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 637bac6c34..fe1e502a68 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 @@ -23,6 +23,7 @@ grammar Jpql; * * @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#bnf * @author Greg Turnquist + * @author Christoph Strobl * @since 3.1 */ } @@ -621,6 +622,7 @@ literal : STRINGLITERAL | INTLITERAL | FLOATLITERAL + | LONGLITERAL | boolean_literal | entity_type_literal ; @@ -650,6 +652,7 @@ escape_character numeric_literal : INTLITERAL | FLOATLITERAL + | LONGLITERAL ; boolean_literal @@ -855,3 +858,4 @@ IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | STRINGLITERAL : '\'' (~ ('\'' | '\\'))* '\'' ; FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E '0' .. '9')* ; INTLITERAL : ('0' .. '9')+ ; +LONGLITERAL : ('0' .. '9')+L ; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java index bdd5ddc1ec..65b21569d7 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java @@ -24,6 +24,7 @@ * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders a JPQL query without making any changes. * * @author Greg Turnquist + * @author Christoph Strobl * @since 3.1 */ @SuppressWarnings({ "ConstantConditions", "DuplicatedCode" }) @@ -2156,6 +2157,8 @@ public List visitLiteral(JpqlParser.LiteralContext ctx) { tokens.add(new JpaQueryParsingToken(ctx.INTLITERAL())); } else if (ctx.FLOATLITERAL() != null) { tokens.add(new JpaQueryParsingToken(ctx.FLOATLITERAL())); + } else if(ctx.LONGLITERAL() != null) { + tokens.add(new JpaQueryParsingToken(ctx.LONGLITERAL())); } else if (ctx.boolean_literal() != null) { tokens.addAll(visit(ctx.boolean_literal())); } else if (ctx.entity_type_literal() != null) { @@ -2216,6 +2219,8 @@ public List visitNumeric_literal(JpqlParser.Numeric_litera return List.of(new JpaQueryParsingToken(ctx.INTLITERAL())); } else if (ctx.FLOATLITERAL() != null) { return List.of(new JpaQueryParsingToken(ctx.FLOATLITERAL())); + } else if(ctx.LONGLITERAL() != null) { + return List.of(new JpaQueryParsingToken(ctx.LONGLITERAL())); } else { return List.of(); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java index 5cbcda1a54..639a156bf3 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java @@ -32,6 +32,7 @@ * @author Mark Paluch * @author Diego Krupitza * @author Geoffrey Deremetz + * @author Christoph Strobl */ public class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests { @@ -49,6 +50,8 @@ void shouldDeriveJpqlCountQuery(String query, String expected) { assumeThat(query).as("JSQLParser does not support constructor JPQL syntax").doesNotContain(" new "); + assumeThat(query).as("JSQLParser does not support MOD JPQL syntax").doesNotContain("MOD("); + super.shouldDeriveJpqlCountQuery(query, expected); } From c99e7a1e6613433fdd49d0f1cce29e2dbd8d549f Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 20 Dec 2023 10:32:41 +0100 Subject: [PATCH 4/9] Fix EQL parsing for MOD expression. The samples used to validate MOD parsing in tests does not comply with the specification and falsely uses '/' instead of ',' as delimiter. Definition is: expression ::= MOD(arithmetic_expression, arithmetic_expression) See: https://eclipse.dev/eclipselink/api/4.0/eclipselink/org/eclipse/persistence/jpa/jpql/parser/ModExpression.html --- .../data/jpa/repository/query/Eql.g4 | 5 +++-- .../data/jpa/repository/query/EqlQueryRenderer.java | 4 +++- .../jpa/repository/query/EqlComplianceTests.java | 13 ++++++++----- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 index a0aa24491a..9bbf47a86c 100644 --- a/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 +++ b/spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4 @@ -23,6 +23,7 @@ grammar Eql; * * https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL * * @author Greg Turnquist + * @author Christoph Strobl * @since 3.2 */ } @@ -509,7 +510,7 @@ functions_returning_numerics | LN '(' arithmetic_expression ')' | SIGN '(' arithmetic_expression ')' | SQRT '(' arithmetic_expression ')' - | MOD '(' arithmetic_expression '/' arithmetic_expression ')' + | MOD '(' arithmetic_expression ',' arithmetic_expression ')' | POWER '(' arithmetic_expression ',' arithmetic_expression ')' | ROUND '(' arithmetic_expression ',' arithmetic_expression ')' | SIZE '(' collection_valued_path_expression ')' @@ -894,4 +895,4 @@ INTLITERAL : ('0' .. '9')+ ; LONGLITERAL : ('0' .. '9')+ L; DATELITERAL : '{' D STRINGLITERAL '}'; TIMELITERAL : '{' T STRINGLITERAL '}'; -TIMESTAMPLITERAL : '{' T S STRINGLITERAL '}'; \ No newline at end of file +TIMESTAMPLITERAL : '{' T S STRINGLITERAL '}'; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java index 07201fd20c..6688d78f01 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryRenderer.java @@ -24,6 +24,7 @@ * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an EQL query without making any changes. * * @author Greg Turnquist + * @author Christoph Strobl * @since 3.2 */ @SuppressWarnings({ "ConstantConditions", "DuplicatedCode" }) @@ -1912,7 +1913,8 @@ public List visitFunctions_returning_numerics( tokens.add(new JpaQueryParsingToken(ctx.MOD(), false)); tokens.add(TOKEN_OPEN_PAREN); tokens.addAll(visit(ctx.arithmetic_expression(0))); - tokens.add(new JpaQueryParsingToken("/")); + NOSPACE(tokens); + tokens.add(TOKEN_COMMA); tokens.addAll(visit(ctx.arithmetic_expression(1))); NOSPACE(tokens); tokens.add(TOKEN_CLOSE_PAREN); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java index aacf1e6c50..67cbf22372 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java @@ -25,10 +25,13 @@ /** * Tests built around examples of EQL found in the EclipseLink's docs at * https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL
+ * With the exception of {@literal MOD} which is defined as {@literal MOD(arithmetic_expression , arithmetic_expression)}, + * but shown in tests as {@literal MOD(arithmetic_expression ? arithmetic_expression)}. *
* IMPORTANT: Purely verifies the parser without any transformations. * * @author Greg Turnquist + * @author Christoph Strobl */ class EqlComplianceTests { @@ -214,7 +217,7 @@ void functionsInSelect() { assertQuery("SELECT e.name, CURRENT_TIMESTAMP FROM Employee e"); assertQuery("SELECT LENGTH(e.lastName) FROM Employee e"); assertQuery("SELECT LOWER(e.lastName) FROM Employee e"); - assertQuery("SELECT MOD(e.hoursWorked / 8) FROM Employee e"); + assertQuery("SELECT MOD(e.hoursWorked, 8) FROM Employee e"); assertQuery("SELECT NULLIF(e.salary, 0) FROM Employee e"); assertQuery("SELECT SQRT(o.RESULT) FROM Output o"); assertQuery("SELECT SUBSTRING(e.lastName, 0, 2) FROM Employee e"); @@ -243,7 +246,7 @@ void functionsInWhere() { assertQuery("SELECT e FROM Employee e WHERE CURRENT_TIME > CURRENT_TIMESTAMP"); assertQuery("SELECT e FROM Employee e WHERE LENGTH(e.lastName) > 0"); assertQuery("SELECT e FROM Employee e WHERE LOWER(e.lastName) = 'bilbo'"); - assertQuery("SELECT e FROM Employee e WHERE MOD(e.hoursWorked / 8) > 0"); + assertQuery("SELECT e FROM Employee e WHERE MOD(e.hoursWorked, 8) > 0"); assertQuery("SELECT e FROM Employee e WHERE NULLIF(e.salary, 0) is null"); assertQuery("SELECT e FROM Employee e WHERE SQRT(o.RESULT) > 0.0"); assertQuery("SELECT e FROM Employee e WHERE SUBSTRING(e.lastName, 0, 2) = 'Bilbo'"); @@ -272,7 +275,7 @@ void functionsInOrderBy() { assertQuery("SELECT e FROM Employee e ORDER BY CURRENT_TIMESTAMP"); assertQuery("SELECT e FROM Employee e ORDER BY LENGTH(e.lastName)"); assertQuery("SELECT e FROM Employee e ORDER BY LOWER(e.lastName)"); - assertQuery("SELECT e FROM Employee e ORDER BY MOD(e.hoursWorked / 8)"); + assertQuery("SELECT e FROM Employee e ORDER BY MOD(e.hoursWorked, 8)"); assertQuery("SELECT e FROM Employee e ORDER BY NULLIF(e.salary, 0)"); assertQuery("SELECT e FROM Employee e ORDER BY SQRT(o.RESULT)"); assertQuery("SELECT e FROM Employee e ORDER BY SUBSTRING(e.lastName, 0, 2)"); @@ -301,7 +304,7 @@ void functionsInGroupBy() { assertQuery("SELECT e FROM Employee e GROUP BY CURRENT_TIMESTAMP"); assertQuery("SELECT e FROM Employee e GROUP BY LENGTH(e.lastName)"); assertQuery("SELECT e FROM Employee e GROUP BY LOWER(e.lastName)"); - assertQuery("SELECT e FROM Employee e GROUP BY MOD(e.hoursWorked / 8)"); + assertQuery("SELECT e FROM Employee e GROUP BY MOD(e.hoursWorked, 8)"); assertQuery("SELECT e FROM Employee e GROUP BY NULLIF(e.salary, 0)"); assertQuery("SELECT e FROM Employee e GROUP BY SQRT(o.RESULT)"); assertQuery("SELECT e FROM Employee e GROUP BY SUBSTRING(e.lastName, 0, 2)"); @@ -329,7 +332,7 @@ void functionsInHaving() { assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING CURRENT_TIME > CURRENT_TIMESTAMP"); assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING LENGTH(e.lastName) > 0"); assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING LOWER(e.lastName) = 'bilbo'"); - assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING MOD(e.hoursWorked / 8) > 0"); + assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING MOD(e.hoursWorked, 8) > 0"); assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING NULLIF(e.salary, 0) is null"); assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING SQRT(o.RESULT) > 0.0"); assertQuery("SELECT e FROM Employee e GROUP BY e.salary HAVING SUBSTRING(e.lastName, 0, 2) = 'Bilbo'"); From 07c44aaad47a44dde65f7de73f094bf15178abd9 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 20 Dec 2023 11:08:25 +0100 Subject: [PATCH 5/9] Update jpql grammar to support F & D for numeric values. --- .../data/jpa/repository/query/Jpql.g4 | 3 +- .../repository/query/JpqlComplianceTests.java | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java 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 fe1e502a68..cfe1d08b9f 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 @@ -852,10 +852,9 @@ WHERE : W H E R E; EQUAL : '=' ; NOT_EQUAL : '<>' | '!=' ; - CHARACTER : '\'' (~ ('\'' | '\\')) '\'' ; IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ; STRINGLITERAL : '\'' (~ ('\'' | '\\'))* '\'' ; -FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E '0' .. '9')* ; +FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E ('0' .. '9')+)* (F|D)?; INTLITERAL : ('0' .. '9')+ ; LONGLITERAL : ('0' .. '9')+L ; diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java new file mode 100644 index 0000000000..acb2f51004 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.junit.jupiter.api.Test; + +/** + * @author Christoph Strobl + */ +public class JpqlComplianceTests { + + private static String parseWithoutChanges(String query) { + + JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(query)); + JpqlParser parser = new JpqlParser(new CommonTokenStream(lexer)); + + parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); + + JpqlParser.StartContext parsedQuery = parser.start(); + + return render(new JpqlQueryRenderer().visit(parsedQuery)); + } + + private void assertQuery(String query) { + + String slimmedDownQuery = reduceWhitespace(query); + assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery); + } + + private String reduceWhitespace(String original) { + + return original // + .replaceAll("[ \\t\\n]{1,}", " ") // + .trim(); + } + + @Test + void numericLiterals() { + + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); + } + +} From 3cac506c8c0e3fdd082a9906385d2eb6e6c087c6 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 20 Dec 2023 12:20:36 +0100 Subject: [PATCH 6/9] Introduce common test base for parser tests --- .../repository/query/EqlComplianceTests.java | 70 ++--------- .../repository/query/JpqlComplianceTests.java | 38 ++---- .../jpa/repository/query/SqlParserTests.java | 113 ++++++++++++++++++ 3 files changed, 131 insertions(+), 90 deletions(-) create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SqlParserTests.java diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java index 67cbf22372..37fc3f9cce 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java @@ -15,11 +15,11 @@ */ 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.List; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTree; import org.junit.jupiter.api.Test; /** @@ -33,69 +33,22 @@ * @author Greg Turnquist * @author Christoph Strobl */ -class EqlComplianceTests { +class EqlComplianceTests extends SqlParserTests{ - /** - * Parse the query using {@link EqlParser} then run it through the query-preserving {@link EqlQueryRenderer}. - * - * @param query - */ - private static String parseWithoutChanges(String query) { + @Override + ParseTree parse(String query) { EqlLexer lexer = new EqlLexer(CharStreams.fromString(query)); EqlParser parser = new EqlParser(new CommonTokenStream(lexer)); parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - EqlParser.StartContext parsedQuery = parser.start(); - - return render(new EqlQueryRenderer().visit(parsedQuery)); - } - - private void assertQuery(String query) { - - String slimmedDownQuery = reduceWhitespace(query); - assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery); - } - - private String reduceWhitespace(String original) { - - return original // - .replaceAll("[ \\t\\n]{1,}", " ") // - .trim(); - } - - @Test - void selectQueries() { - - assertQuery("Select e FROM Employee e WHERE e.salary > 100000"); - assertQuery("Select e FROM Employee e WHERE e.id = :id"); - assertQuery("Select MAX(e.salary) FROM Employee e"); - assertQuery("Select e.firstName FROM Employee e"); - assertQuery("Select e.firstName, e.lastName FROM Employee e"); - } - - @Test - void selectClause() { - - assertQuery("SELECT COUNT(e) FROM Employee e"); - assertQuery("SELECT MAX(e.salary) FROM Employee e"); - assertQuery("SELECT NEW com.acme.reports.EmpReport(e.firstName, e.lastName, e.salary) FROM Employee e"); - } - - @Test - void fromClause() { - - assertQuery("SELECT e FROM Employee e"); - assertQuery("SELECT e, a FROM Employee e, MailingAddress a WHERE e.address = a.address"); - assertQuery("SELECT e FROM com.acme.Employee e"); + return parser.start(); } - @Test - void join() { - - assertQuery("SELECT e FROM Employee e JOIN e.address a WHERE a.city = :city"); - assertQuery("SELECT e FROM Employee e JOIN e.projects p JOIN e.projects p2 WHERE p.name = :p1 AND p2.name = :p2"); + @Override + List analyze(T parseTree) { + return new EqlQueryRenderer().visit(parseTree); } @Test @@ -105,11 +58,6 @@ void joinFetch() { assertQuery("SELECT e FROM Employee e JOIN FETCH e.address a ORDER BY a.city"); } - @Test - void leftJoin() { - assertQuery("SELECT e FROM Employee e LEFT JOIN e.address a ORDER BY a.city"); - } - @Test void on() { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java index acb2f51004..c4d3e5c452 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java @@ -15,51 +15,31 @@ */ 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.List; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; -import org.junit.jupiter.api.Test; +import org.antlr.v4.runtime.tree.ParseTree; /** * @author Christoph Strobl */ -public class JpqlComplianceTests { +public class JpqlComplianceTests extends SqlParserTests { - private static String parseWithoutChanges(String query) { + @Override + ParseTree parse(String query) { JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(query)); JpqlParser parser = new JpqlParser(new CommonTokenStream(lexer)); parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - JpqlParser.StartContext parsedQuery = parser.start(); - - return render(new JpqlQueryRenderer().visit(parsedQuery)); - } - - private void assertQuery(String query) { - - String slimmedDownQuery = reduceWhitespace(query); - assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery); + return parser.start(); } - private String reduceWhitespace(String original) { - - return original // - .replaceAll("[ \\t\\n]{1,}", " ") // - .trim(); - } - - @Test - void numericLiterals() { - - assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); - assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L"); - assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14"); - assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F"); - assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); + @Override + List analyze(T parseTree) { + return new JpqlQueryRenderer().visit(parseTree); } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SqlParserTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SqlParserTests.java new file mode 100644 index 0000000000..058abbe322 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SqlParserTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +/** + * @author Christoph Strobl + */ +public abstract class SqlParserTests { + + abstract T parse(String query); + + abstract List analyze(T parseTree); + + protected String render(List tokens) { + + StringBuilder results = new StringBuilder(); + + tokens.forEach(token -> { + + results.append(token.getToken()); + + if (token.getSpace()) { + results.append(" "); + } + }); + + return results.toString().trim(); + } + + @Test // GH-3277 + void numericLiterals() { + + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); + } + + @Test + void selectQueries() { + + assertQuery("Select e FROM Employee e WHERE e.salary > 100000"); + assertQuery("Select e FROM Employee e WHERE e.id = :id"); + assertQuery("Select MAX(e.salary) FROM Employee e"); + assertQuery("Select e.firstName FROM Employee e"); + assertQuery("Select e.firstName, e.lastName FROM Employee e"); + } + + @Test + void selectClause() { + + assertQuery("SELECT COUNT(e) FROM Employee e"); + assertQuery("SELECT MAX(e.salary) FROM Employee e"); + assertQuery("SELECT NEW com.acme.reports.EmpReport(e.firstName, e.lastName, e.salary) FROM Employee e"); + } + + + @Test + void fromClause() { + + assertQuery("SELECT e FROM Employee e"); + assertQuery("SELECT e, a FROM Employee e, MailingAddress a WHERE e.address = a.address"); + assertQuery("SELECT e FROM com.acme.Employee e"); + } + + @Test + void join() { + + assertQuery("SELECT e FROM Employee e JOIN e.address a WHERE a.city = :city"); + assertQuery("SELECT e FROM Employee e JOIN e.projects p JOIN e.projects p2 WHERE p.name = :p1 AND p2.name = :p2"); + } + @Test + void leftJoin() { + assertQuery("SELECT e FROM Employee e LEFT JOIN e.address a ORDER BY a.city"); + } + + protected void assertQuery(String query) { + + String slimmedDownQuery = reduceWhitespace(query); + ParseTree tree = parse(slimmedDownQuery); + List tokens = analyze(tree); + + assertThat(render(tokens)).isEqualTo(slimmedDownQuery); + } + + private static String reduceWhitespace(String original) { + + return original // + .replaceAll("[ \\t\\n]{1,}", " ") // + .trim(); + } +} From 16518ab84f92f8b614cb7d1c7febb74be06a726b Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 10 Jan 2024 09:00:58 +0100 Subject: [PATCH 7/9] Revert "Introduce common test base for parser tests" This reverts commit 3cac506c8c0e3fdd082a9906385d2eb6e6c087c6. --- .../repository/query/EqlComplianceTests.java | 70 +++++++++-- .../repository/query/JpqlComplianceTests.java | 38 ++++-- .../jpa/repository/query/SqlParserTests.java | 113 ------------------ 3 files changed, 90 insertions(+), 131 deletions(-) delete mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SqlParserTests.java diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java index 37fc3f9cce..67cbf22372 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlComplianceTests.java @@ -15,11 +15,11 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.List; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.tree.ParseTree; import org.junit.jupiter.api.Test; /** @@ -33,22 +33,69 @@ * @author Greg Turnquist * @author Christoph Strobl */ -class EqlComplianceTests extends SqlParserTests{ +class EqlComplianceTests { - @Override - ParseTree parse(String query) { + /** + * Parse the query using {@link EqlParser} then run it through the query-preserving {@link EqlQueryRenderer}. + * + * @param query + */ + private static String parseWithoutChanges(String query) { EqlLexer lexer = new EqlLexer(CharStreams.fromString(query)); EqlParser parser = new EqlParser(new CommonTokenStream(lexer)); parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - return parser.start(); + EqlParser.StartContext parsedQuery = parser.start(); + + return render(new EqlQueryRenderer().visit(parsedQuery)); + } + + private void assertQuery(String query) { + + String slimmedDownQuery = reduceWhitespace(query); + assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery); + } + + private String reduceWhitespace(String original) { + + return original // + .replaceAll("[ \\t\\n]{1,}", " ") // + .trim(); + } + + @Test + void selectQueries() { + + assertQuery("Select e FROM Employee e WHERE e.salary > 100000"); + assertQuery("Select e FROM Employee e WHERE e.id = :id"); + assertQuery("Select MAX(e.salary) FROM Employee e"); + assertQuery("Select e.firstName FROM Employee e"); + assertQuery("Select e.firstName, e.lastName FROM Employee e"); + } + + @Test + void selectClause() { + + assertQuery("SELECT COUNT(e) FROM Employee e"); + assertQuery("SELECT MAX(e.salary) FROM Employee e"); + assertQuery("SELECT NEW com.acme.reports.EmpReport(e.firstName, e.lastName, e.salary) FROM Employee e"); + } + + @Test + void fromClause() { + + assertQuery("SELECT e FROM Employee e"); + assertQuery("SELECT e, a FROM Employee e, MailingAddress a WHERE e.address = a.address"); + assertQuery("SELECT e FROM com.acme.Employee e"); } - @Override - List analyze(T parseTree) { - return new EqlQueryRenderer().visit(parseTree); + @Test + void join() { + + assertQuery("SELECT e FROM Employee e JOIN e.address a WHERE a.city = :city"); + assertQuery("SELECT e FROM Employee e JOIN e.projects p JOIN e.projects p2 WHERE p.name = :p1 AND p2.name = :p2"); } @Test @@ -58,6 +105,11 @@ void joinFetch() { assertQuery("SELECT e FROM Employee e JOIN FETCH e.address a ORDER BY a.city"); } + @Test + void leftJoin() { + assertQuery("SELECT e FROM Employee e LEFT JOIN e.address a ORDER BY a.city"); + } + @Test void on() { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java index c4d3e5c452..acb2f51004 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java @@ -15,31 +15,51 @@ */ package org.springframework.data.jpa.repository.query; -import java.util.List; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; /** * @author Christoph Strobl */ -public class JpqlComplianceTests extends SqlParserTests { +public class JpqlComplianceTests { - @Override - ParseTree parse(String query) { + private static String parseWithoutChanges(String query) { JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(query)); JpqlParser parser = new JpqlParser(new CommonTokenStream(lexer)); parser.addErrorListener(new BadJpqlGrammarErrorListener(query)); - return parser.start(); + JpqlParser.StartContext parsedQuery = parser.start(); + + return render(new JpqlQueryRenderer().visit(parsedQuery)); + } + + private void assertQuery(String query) { + + String slimmedDownQuery = reduceWhitespace(query); + assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery); } - @Override - List analyze(T parseTree) { - return new JpqlQueryRenderer().visit(parseTree); + private String reduceWhitespace(String original) { + + return original // + .replaceAll("[ \\t\\n]{1,}", " ") // + .trim(); + } + + @Test + void numericLiterals() { + + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); + assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F"); + assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SqlParserTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SqlParserTests.java deleted file mode 100644 index 058abbe322..0000000000 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SqlParserTests.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.jpa.repository.query; - -import static org.assertj.core.api.Assertions.*; - -import java.util.List; - -import org.antlr.v4.runtime.tree.ParseTree; -import org.junit.jupiter.api.Test; - -/** - * @author Christoph Strobl - */ -public abstract class SqlParserTests { - - abstract T parse(String query); - - abstract List analyze(T parseTree); - - protected String render(List tokens) { - - StringBuilder results = new StringBuilder(); - - tokens.forEach(token -> { - - results.append(token.getToken()); - - if (token.getSpace()) { - results.append(" "); - } - }); - - return results.toString().trim(); - } - - @Test // GH-3277 - void numericLiterals() { - - assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); - assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L"); - assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14"); - assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F"); - assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); - } - - @Test - void selectQueries() { - - assertQuery("Select e FROM Employee e WHERE e.salary > 100000"); - assertQuery("Select e FROM Employee e WHERE e.id = :id"); - assertQuery("Select MAX(e.salary) FROM Employee e"); - assertQuery("Select e.firstName FROM Employee e"); - assertQuery("Select e.firstName, e.lastName FROM Employee e"); - } - - @Test - void selectClause() { - - assertQuery("SELECT COUNT(e) FROM Employee e"); - assertQuery("SELECT MAX(e.salary) FROM Employee e"); - assertQuery("SELECT NEW com.acme.reports.EmpReport(e.firstName, e.lastName, e.salary) FROM Employee e"); - } - - - @Test - void fromClause() { - - assertQuery("SELECT e FROM Employee e"); - assertQuery("SELECT e, a FROM Employee e, MailingAddress a WHERE e.address = a.address"); - assertQuery("SELECT e FROM com.acme.Employee e"); - } - - @Test - void join() { - - assertQuery("SELECT e FROM Employee e JOIN e.address a WHERE a.city = :city"); - assertQuery("SELECT e FROM Employee e JOIN e.projects p JOIN e.projects p2 WHERE p.name = :p1 AND p2.name = :p2"); - } - @Test - void leftJoin() { - assertQuery("SELECT e FROM Employee e LEFT JOIN e.address a ORDER BY a.city"); - } - - protected void assertQuery(String query) { - - String slimmedDownQuery = reduceWhitespace(query); - ParseTree tree = parse(slimmedDownQuery); - List tokens = analyze(tree); - - assertThat(render(tokens)).isEqualTo(slimmedDownQuery); - } - - private static String reduceWhitespace(String original) { - - return original // - .replaceAll("[ \\t\\n]{1,}", " ") // - .trim(); - } -} From 0d9c1e65d03c722d848f5df9ce6b14c0ed7bb4b3 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 10 Jan 2024 09:09:22 +0100 Subject: [PATCH 8/9] foo --- .../data/jpa/repository/query/JpqlComplianceTests.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java index acb2f51004..4f94fec871 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,13 @@ import org.junit.jupiter.api.Test; /** + * Test to verify compliance of {@link JpqlParser} with standard SQL. Other than {@link JpqlSpecificationTests} tests in + * this class check that the parser follows a lenient approach and does not error on well known concepts like numeric + * suffix. + * * @author Christoph Strobl */ -public class JpqlComplianceTests { +class JpqlComplianceTests { private static String parseWithoutChanges(String query) { @@ -52,7 +56,7 @@ private String reduceWhitespace(String original) { .trim(); } - @Test + @Test // GH-3277 void numericLiterals() { assertQuery("SELECT e FROM Employee e WHERE e.id = 1234"); From 362663818e1f2e5663a49e120502c4c72ce7e2ce Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 10 Jan 2024 10:58:17 +0100 Subject: [PATCH 9/9] Fix Java String literal parsing in JQPL grammar. Allow using java string literals using escaped double quotes next to single quoted values. --- .../org/springframework/data/jpa/repository/query/Jpql.g4 | 3 +++ .../data/jpa/repository/query/JpqlQueryRenderer.java | 4 ++++ .../data/jpa/repository/query/JpqlComplianceTests.java | 5 +++++ 3 files changed, 12 insertions(+) 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 cfe1d08b9f..b80e499ffa 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 @@ -204,6 +204,7 @@ constructor_item | scalar_expression | aggregate_expression | identification_variable + | literal ; aggregate_expression @@ -620,6 +621,7 @@ constructor_name literal : STRINGLITERAL + | JAVASTRINGLITERAL | INTLITERAL | FLOATLITERAL | LONGLITERAL @@ -855,6 +857,7 @@ NOT_EQUAL : '<>' | '!=' ; CHARACTER : '\'' (~ ('\'' | '\\')) '\'' ; IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ; STRINGLITERAL : '\'' (~ ('\'' | '\\'))* '\'' ; +JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"'; FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E ('0' .. '9')+)* (F|D)?; INTLITERAL : ('0' .. '9')+ ; LONGLITERAL : ('0' .. '9')+L ; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java index 65b21569d7..46f5335cc5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java @@ -723,6 +723,8 @@ public List visitConstructor_item(JpqlParser.Constructor_i tokens.addAll(visit(ctx.aggregate_expression())); } else if (ctx.identification_variable() != null) { tokens.addAll(visit(ctx.identification_variable())); + } else if (ctx.literal() != null) { + tokens.addAll(visit(ctx.literal())); } return tokens; @@ -2153,6 +2155,8 @@ public List visitLiteral(JpqlParser.LiteralContext ctx) { if (ctx.STRINGLITERAL() != null) { tokens.add(new JpaQueryParsingToken(ctx.STRINGLITERAL())); + } else if (ctx.JAVASTRINGLITERAL() != null) { + tokens.add(new JpaQueryParsingToken(ctx.JAVASTRINGLITERAL())); } else if (ctx.INTLITERAL() != null) { tokens.add(new JpaQueryParsingToken(ctx.INTLITERAL())); } else if (ctx.FLOATLITERAL() != null) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java index 4f94fec871..f858594973 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlComplianceTests.java @@ -66,4 +66,9 @@ void numericLiterals() { assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D"); } + @Test // GH-3308 + void newWithStrings() { + assertQuery("select new com.example.demo.SampleObject(se.id, se.sampleValue, \"java\") from SampleEntity se"); + } + }