Skip to content

Commit 3998a6c

Browse files
christophstroblmp911de
authored andcommitted
Update JPQL grammar to support F and D suffixes.
See #3277 Original pull request: #3280
1 parent 1610f6b commit 3998a6c

File tree

4 files changed

+88
-3
lines changed

4 files changed

+88
-3
lines changed

spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Jpql.g4

+4-2
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ constructor_item
204204
| scalar_expression
205205
| aggregate_expression
206206
| identification_variable
207+
| literal
207208
;
208209

209210
aggregate_expression
@@ -620,6 +621,7 @@ constructor_name
620621

621622
literal
622623
: STRINGLITERAL
624+
| JAVASTRINGLITERAL
623625
| INTLITERAL
624626
| FLOATLITERAL
625627
| LONGLITERAL
@@ -852,10 +854,10 @@ WHERE : W H E R E;
852854
EQUAL : '=' ;
853855
NOT_EQUAL : '<>' | '!=' ;
854856

855-
856857
CHARACTER : '\'' (~ ('\'' | '\\')) '\'' ;
857858
IDENTIFICATION_VARIABLE : ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '$' | '_') ('a' .. 'z' | 'A' .. 'Z' | '\u0080' .. '\ufffe' | '0' .. '9' | '$' | '_')* ;
858859
STRINGLITERAL : '\'' (~ ('\'' | '\\'))* '\'' ;
859-
FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E '0' .. '9')* ;
860+
JAVASTRINGLITERAL : '"' ( ('\\' [btnfr"']) | ~('"'))* '"';
861+
FLOATLITERAL : ('0' .. '9')* '.' ('0' .. '9')+ (E ('0' .. '9')+)* (F|D)?;
860862
INTLITERAL : ('0' .. '9')+ ;
861863
LONGLITERAL : ('0' .. '9')+L ;

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryRenderer.java

+4
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,8 @@ public List<JpaQueryParsingToken> visitConstructor_item(JpqlParser.Constructor_i
722722
tokens.addAll(visit(ctx.aggregate_expression()));
723723
} else if (ctx.identification_variable() != null) {
724724
tokens.addAll(visit(ctx.identification_variable()));
725+
} else if (ctx.literal() != null) {
726+
tokens.addAll(visit(ctx.literal()));
725727
}
726728

727729
return tokens;
@@ -2152,6 +2154,8 @@ public List<JpaQueryParsingToken> visitLiteral(JpqlParser.LiteralContext ctx) {
21522154

21532155
if (ctx.STRINGLITERAL() != null) {
21542156
tokens.add(new JpaQueryParsingToken(ctx.STRINGLITERAL()));
2157+
} else if (ctx.JAVASTRINGLITERAL() != null) {
2158+
tokens.add(new JpaQueryParsingToken(ctx.JAVASTRINGLITERAL()));
21552159
} else if (ctx.INTLITERAL() != null) {
21562160
tokens.add(new JpaQueryParsingToken(ctx.INTLITERAL()));
21572161
} else if (ctx.FLOATLITERAL() != null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.repository.query;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
20+
21+
import org.antlr.v4.runtime.CharStreams;
22+
import org.antlr.v4.runtime.CommonTokenStream;
23+
import org.junit.jupiter.api.Test;
24+
25+
/**
26+
* Test to verify compliance of {@link JpqlParser} with standard SQL. Other than {@link JpqlSpecificationTests} tests in
27+
* this class check that the parser follows a lenient approach and does not error on well known concepts like numeric
28+
* suffix.
29+
*
30+
* @author Christoph Strobl
31+
*/
32+
class JpqlComplianceTests {
33+
34+
private static String parseWithoutChanges(String query) {
35+
36+
JpqlLexer lexer = new JpqlLexer(CharStreams.fromString(query));
37+
JpqlParser parser = new JpqlParser(new CommonTokenStream(lexer));
38+
39+
parser.addErrorListener(new BadJpqlGrammarErrorListener(query));
40+
41+
JpqlParser.StartContext parsedQuery = parser.start();
42+
43+
return render(new JpqlQueryRenderer().visit(parsedQuery));
44+
}
45+
46+
private void assertQuery(String query) {
47+
48+
String slimmedDownQuery = reduceWhitespace(query);
49+
assertThat(parseWithoutChanges(slimmedDownQuery)).isEqualTo(slimmedDownQuery);
50+
}
51+
52+
private String reduceWhitespace(String original) {
53+
54+
return original //
55+
.replaceAll("[ \\t\\n]{1,}", " ") //
56+
.trim();
57+
}
58+
59+
@Test // GH-3277
60+
void numericLiterals() {
61+
62+
assertQuery("SELECT e FROM Employee e WHERE e.id = 1234");
63+
assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L");
64+
assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14");
65+
assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F");
66+
assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D");
67+
}
68+
69+
@Test // GH-3308
70+
void newWithStrings() {
71+
assertQuery("select new com.example.demo.SampleObject(se.id, se.sampleValue, \"java\") from SampleEntity se");
72+
}
73+
74+
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,12 @@ static Stream<Arguments> jpqlCountQueries() {
164164

165165
Arguments.of( //
166166
"select distinct m.genre from Media m where m.user = ?1 order by m.genre asc", //
167-
"select count(distinct m.genre) from Media m where m.user = ?1"));
167+
"select count(distinct m.genre) from Media m where m.user = ?1"),
168+
169+
Arguments.of( //
170+
"select u from User u where MOD(u.age, 10L) = 2", //
171+
"select count(u) from User u where MOD(u.age, 10L) = 2")
172+
);
168173
}
169174

170175
@ParameterizedTest // GH-2511, GH-2773

0 commit comments

Comments
 (0)