Skip to content

Fix JPQL parser issues with numeric values #3280

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.x-3277-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data JPA Parent</name>
Expand Down
4 changes: 2 additions & 2 deletions spring-data-envers/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-envers</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.x-3277-SNAPSHOT</version>

<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.x-3277-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-jpa-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.x-3277-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jpa/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.x-3277-SNAPSHOT</version>

<name>Spring Data JPA</name>
<description>Spring Data module for JPA repositories.</description>
Expand All @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.x-3277-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
}
Expand Down Expand Up @@ -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 ')'
Expand Down Expand Up @@ -894,4 +895,4 @@ INTLITERAL : ('0' .. '9')+ ;
LONGLITERAL : ('0' .. '9')+ L;
DATELITERAL : '{' D STRINGLITERAL '}';
TIMELITERAL : '{' T STRINGLITERAL '}';
TIMESTAMPLITERAL : '{' T S STRINGLITERAL '}';
TIMESTAMPLITERAL : '{' T S STRINGLITERAL '}';
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
}
Expand Down Expand Up @@ -203,6 +204,7 @@ constructor_item
| scalar_expression
| aggregate_expression
| identification_variable
| literal
;

aggregate_expression
Expand Down Expand Up @@ -619,8 +621,10 @@ constructor_name

literal
: STRINGLITERAL
| JAVASTRINGLITERAL
| INTLITERAL
| FLOATLITERAL
| LONGLITERAL
| boolean_literal
| entity_type_literal
;
Expand Down Expand Up @@ -650,6 +654,7 @@ escape_character
numeric_literal
: INTLITERAL
| FLOATLITERAL
| LONGLITERAL
;

boolean_literal
Expand Down Expand Up @@ -849,9 +854,10 @@ 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')* ;
JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"';
FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E ('0' .. '9')+)* (F|D)?;
INTLITERAL : ('0' .. '9')+ ;
LONGLITERAL : ('0' .. '9')+L ;
Original file line number Diff line number Diff line change
Expand Up @@ -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" })
Expand Down Expand Up @@ -1912,7 +1913,8 @@ public List<JpaQueryParsingToken> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" })
Expand Down Expand Up @@ -722,6 +723,8 @@ public List<JpaQueryParsingToken> 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;
Expand Down Expand Up @@ -2152,10 +2155,14 @@ public List<JpaQueryParsingToken> 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) {
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) {
Expand Down Expand Up @@ -2216,6 +2223,8 @@ public List<JpaQueryParsingToken> 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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<br/>
* 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)}.
* <br/>
* IMPORTANT: Purely verifies the parser without any transformations.
*
* @author Greg Turnquist
* @author Christoph Strobl
*/
class EqlComplianceTests {

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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'");
Expand Down Expand Up @@ -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)");
Expand Down Expand Up @@ -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)");
Expand Down Expand Up @@ -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'");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
* @author Mark Paluch
* @author Diego Krupitza
* @author Geoffrey Deremetz
* @author Christoph Strobl
*/
public class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {

Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.
* 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;

/**
* 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
*/
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 // 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 // GH-3308
void newWithStrings() {
assertQuery("select new com.example.demo.SampleObject(se.id, se.sampleValue, \"java\") from SampleEntity se");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ static Stream<Arguments> 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
Expand Down