Skip to content

Commit b5ecb8b

Browse files
committed
Fix JPQL and EQL CAST(…) function parsing.
We now define arithmetic, string, and typed cast functions to support all potential variants of casting supported by JPQL and EQL. Closes #3863
1 parent cdcac86 commit b5ecb8b

File tree

9 files changed

+293
-116
lines changed

9 files changed

+293
-116
lines changed

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,6 @@ scalar_expression
314314
| datetime_expression
315315
| boolean_expression
316316
| case_expression
317-
| cast_function
318317
| entity_type_expression
319318
;
320319

@@ -447,7 +446,8 @@ arithmetic_primary
447446
| functions_returning_numerics
448447
| aggregate_expression
449448
| case_expression
450-
| cast_function
449+
| arithmetic_cast_function
450+
| type_cast_function
451451
| function_invocation
452452
| '(' subquery ')'
453453
;
@@ -460,6 +460,8 @@ string_expression
460460
| aggregate_expression
461461
| case_expression
462462
| function_invocation
463+
| string_cast_function
464+
| type_cast_function
463465
| '(' subquery ')'
464466
| string_expression '||' string_expression
465467
;
@@ -557,8 +559,16 @@ trim_specification
557559
| BOTH
558560
;
559561

560-
cast_function
561-
: CAST '(' single_valued_path_expression (identification_variable)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')'
562+
arithmetic_cast_function
563+
: CAST '(' string_expression (AS)? f=(INTEGER|LONG|FLOAT|DOUBLE) ')'
564+
;
565+
566+
type_cast_function
567+
: CAST '(' scalar_expression (AS)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')'
568+
;
569+
570+
string_cast_function
571+
: CAST '(' scalar_expression (AS)? STRING ')'
562572
;
563573

564574
function_invocation

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

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,6 @@ scalar_expression
307307
| datetime_expression
308308
| boolean_expression
309309
| case_expression
310-
| cast_function
311310
| entity_type_expression
312311
;
313312

@@ -398,13 +397,15 @@ all_or_any_expression
398397
;
399398

400399
comparison_expression
401-
: string_expression comparison_operator (string_expression | all_or_any_expression)
402-
| boolean_expression op=(EQUAL | NOT_EQUAL) (boolean_expression | all_or_any_expression)
403-
| enum_expression op=(EQUAL | NOT_EQUAL) (enum_expression | all_or_any_expression)
404-
| datetime_expression comparison_operator (datetime_expression | all_or_any_expression)
405-
| entity_expression op=(EQUAL | NOT_EQUAL) (entity_expression | all_or_any_expression)
406-
| arithmetic_expression comparison_operator (arithmetic_expression | all_or_any_expression)
407-
| entity_type_expression op=(EQUAL | NOT_EQUAL) entity_type_expression
400+
: string_expression comparison_operator (string_expression | all_or_any_expression) #StringComparison
401+
| boolean_expression op=(EQUAL | NOT_EQUAL) (boolean_expression | all_or_any_expression) #BooleanComparison
402+
| boolean_expression #DirectBooleanCheck
403+
| enum_expression op=(EQUAL | NOT_EQUAL) (enum_expression | all_or_any_expression) #EnumComparison
404+
| datetime_expression comparison_operator (datetime_expression | all_or_any_expression) #DatetimeComparison
405+
| entity_expression op=(EQUAL | NOT_EQUAL) (entity_expression | all_or_any_expression) #EntityComparison
406+
| arithmetic_expression comparison_operator (arithmetic_expression | all_or_any_expression) #ArithmeticComparison
407+
| entity_type_expression op=(EQUAL | NOT_EQUAL) entity_type_expression #EntityTypeComparison
408+
| string_expression REGEXP string_literal #RegexpComparison
408409
;
409410

410411
comparison_operator
@@ -438,7 +439,8 @@ arithmetic_primary
438439
| functions_returning_numerics
439440
| aggregate_expression
440441
| case_expression
441-
| cast_function
442+
| arithmetic_cast_function
443+
| type_cast_function
442444
| function_invocation
443445
| '(' subquery ')'
444446
;
@@ -451,6 +453,8 @@ string_expression
451453
| aggregate_expression
452454
| case_expression
453455
| function_invocation
456+
| string_cast_function
457+
| type_cast_function
454458
| '(' subquery ')'
455459
| string_expression '||' string_expression
456460
;
@@ -548,8 +552,16 @@ trim_specification
548552
| BOTH
549553
;
550554

551-
cast_function
552-
: CAST '(' single_valued_path_expression (identification_variable)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')'
555+
arithmetic_cast_function
556+
: CAST '(' string_expression (AS)? f=(INTEGER|LONG|FLOAT|DOUBLE) ')'
557+
;
558+
559+
type_cast_function
560+
: CAST '(' scalar_expression (AS)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')'
561+
;
562+
563+
string_cast_function
564+
: CAST '(' scalar_expression (AS)? STRING ')'
553565
;
554566

555567
function_invocation
@@ -784,6 +796,7 @@ reserved_word
784796
|BOTH
785797
|BY
786798
|CASE
799+
|CAST
787800
|CEILING
788801
|COALESCE
789802
|CONCAT

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

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.antlr.v4.runtime.tree.ParseTree;
2424

2525
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
26+
import org.springframework.util.CollectionUtils;
2627
import org.springframework.util.ObjectUtils;
2728

2829
/**
@@ -50,7 +51,7 @@ public QueryTokenStream visitQl_statement(EqlParser.Ql_statementContext ctx) {
5051
} else if (ctx.delete_statement() != null) {
5152
return visit(ctx.delete_statement());
5253
} else {
53-
return QueryRenderer.builder();
54+
return QueryTokenStream.empty();
5455
}
5556
}
5657

@@ -1009,8 +1010,6 @@ public QueryTokenStream visitScalar_expression(EqlParser.Scalar_expressionContex
10091010
return visit(ctx.case_expression());
10101011
} else if (ctx.entity_type_expression() != null) {
10111012
return visit(ctx.entity_type_expression());
1012-
} else if (ctx.cast_function() != null) {
1013-
return (visit(ctx.cast_function()));
10141013
}
10151014

10161015
return QueryTokenStream.empty();
@@ -1362,8 +1361,8 @@ public QueryTokenStream visitStringComparison(EqlParser.StringComparisonContext
13621361

13631362
QueryRendererBuilder builder = QueryRenderer.builder();
13641363

1365-
builder.appendExpression(visit(ctx.string_expression(0)));
1366-
builder.appendExpression(visit(ctx.comparison_operator()));
1364+
builder.appendInline(visit(ctx.string_expression(0)));
1365+
builder.appendInline(visit(ctx.comparison_operator()));
13671366

13681367
if (ctx.string_expression(1) != null) {
13691368
builder.appendExpression(visit(ctx.string_expression(1)));
@@ -1419,7 +1418,7 @@ public QueryTokenStream visitDatetimeComparison(EqlParser.DatetimeComparisonCont
14191418
QueryRendererBuilder builder = QueryRenderer.builder();
14201419

14211420
builder.appendInline(visit(ctx.datetime_expression(0)));
1422-
builder.append(QueryTokens.ventilated(ctx.comparison_operator().op));
1421+
builder.appendInline(visit(ctx.comparison_operator()));
14231422

14241423
if (ctx.datetime_expression(1) != null) {
14251424
builder.appendExpression(visit(ctx.datetime_expression(1)));
@@ -1452,8 +1451,8 @@ public QueryTokenStream visitArithmeticComparison(EqlParser.ArithmeticComparison
14521451

14531452
QueryRendererBuilder builder = QueryRenderer.builder();
14541453

1455-
builder.appendExpression(visit(ctx.arithmetic_expression(0)));
1456-
builder.appendExpression(visit(ctx.comparison_operator()));
1454+
builder.appendInline(visit(ctx.arithmetic_expression(0)));
1455+
builder.appendInline(visit(ctx.comparison_operator()));
14571456

14581457
if (ctx.arithmetic_expression(1) != null) {
14591458
builder.appendExpression(visit(ctx.arithmetic_expression(1)));
@@ -1490,7 +1489,7 @@ public QueryTokenStream visitRegexpComparison(EqlParser.RegexpComparisonContext
14901489

14911490
@Override
14921491
public QueryTokenStream visitComparison_operator(EqlParser.Comparison_operatorContext ctx) {
1493-
return QueryTokenStream.ofToken(ctx.op);
1492+
return QueryTokenStream.from(QueryTokens.ventilated(ctx.op));
14941493
}
14951494

14961495
@Override
@@ -1559,8 +1558,10 @@ public QueryTokenStream visitArithmetic_primary(EqlParser.Arithmetic_primaryCont
15591558
builder.append(visit(ctx.aggregate_expression()));
15601559
} else if (ctx.case_expression() != null) {
15611560
builder.append(visit(ctx.case_expression()));
1562-
} else if (ctx.cast_function() != null) {
1563-
builder.append(visit(ctx.cast_function()));
1561+
} else if (ctx.arithmetic_cast_function() != null) {
1562+
builder.append(visit(ctx.arithmetic_cast_function()));
1563+
} else if (ctx.type_cast_function() != null) {
1564+
builder.append(visit(ctx.type_cast_function()));
15641565
} else if (ctx.function_invocation() != null) {
15651566
builder.append(visit(ctx.function_invocation()));
15661567
} else if (ctx.subquery() != null) {
@@ -1590,6 +1591,10 @@ public QueryTokenStream visitString_expression(EqlParser.String_expressionContex
15901591
builder.append(visit(ctx.aggregate_expression()));
15911592
} else if (ctx.case_expression() != null) {
15921593
builder.append(visit(ctx.case_expression()));
1594+
} else if (ctx.string_cast_function() != null) {
1595+
builder.append(visit(ctx.string_cast_function()));
1596+
} else if (ctx.type_cast_function() != null) {
1597+
builder.append(visit(ctx.type_cast_function()));
15931598
} else if (ctx.function_invocation() != null) {
15941599
builder.append(visit(ctx.function_invocation()));
15951600
} else if (ctx.subquery() != null) {
@@ -1971,17 +1976,36 @@ public QueryTokenStream visitTrim_specification(EqlParser.Trim_specificationCont
19711976
}
19721977

19731978
@Override
1974-
public QueryTokenStream visitCast_function(EqlParser.Cast_functionContext ctx) {
1979+
public QueryTokenStream visitArithmetic_cast_function(EqlParser.Arithmetic_cast_functionContext ctx) {
1980+
1981+
QueryRendererBuilder builder = QueryRenderer.builder();
1982+
1983+
builder.append(QueryTokens.token(ctx.CAST()));
1984+
builder.append(TOKEN_OPEN_PAREN);
1985+
builder.appendExpression(visit(ctx.string_expression()));
1986+
if (ctx.AS() != null) {
1987+
builder.append(QueryTokens.expression(ctx.AS()));
1988+
}
1989+
builder.append(QueryTokens.token(ctx.f));
1990+
builder.append(TOKEN_CLOSE_PAREN);
1991+
1992+
return builder;
1993+
}
1994+
1995+
@Override
1996+
public QueryTokenStream visitType_cast_function(EqlParser.Type_cast_functionContext ctx) {
19751997

19761998
QueryRendererBuilder builder = QueryRenderer.builder();
19771999

19782000
builder.append(QueryTokens.token(ctx.CAST()));
19792001
builder.append(TOKEN_OPEN_PAREN);
1980-
builder.appendInline(visit(ctx.single_valued_path_expression()));
1981-
builder.append(TOKEN_SPACE);
1982-
builder.appendInline(QueryTokenStream.concat(ctx.identification_variable(), this::visit, TOKEN_SPACE));
2002+
builder.appendExpression(visit(ctx.scalar_expression()));
2003+
if (ctx.AS() != null) {
2004+
builder.append(QueryTokens.expression(ctx.AS()));
2005+
}
2006+
builder.appendInline(visit(ctx.identification_variable()));
19832007

1984-
if (!ObjectUtils.isEmpty(ctx.numeric_literal())) {
2008+
if (!CollectionUtils.isEmpty(ctx.numeric_literal())) {
19852009

19862010
builder.append(TOKEN_OPEN_PAREN);
19872011
builder.appendInline(QueryTokenStream.concat(ctx.numeric_literal(), this::visit, TOKEN_COMMA));
@@ -1992,6 +2016,23 @@ public QueryTokenStream visitCast_function(EqlParser.Cast_functionContext ctx) {
19922016
return builder;
19932017
}
19942018

2019+
@Override
2020+
public QueryTokenStream visitString_cast_function(EqlParser.String_cast_functionContext ctx) {
2021+
2022+
QueryRendererBuilder builder = QueryRenderer.builder();
2023+
2024+
builder.append(QueryTokens.token(ctx.CAST()));
2025+
builder.append(TOKEN_OPEN_PAREN);
2026+
builder.appendExpression(visit(ctx.scalar_expression()));
2027+
if (ctx.AS() != null) {
2028+
builder.append(QueryTokens.expression(ctx.AS()));
2029+
}
2030+
builder.append(QueryTokens.token(ctx.STRING()));
2031+
builder.append(TOKEN_CLOSE_PAREN);
2032+
2033+
return builder;
2034+
}
2035+
19952036
@Override
19962037
public QueryTokenStream visitFunction_invocation(EqlParser.Function_invocationContext ctx) {
19972038

0 commit comments

Comments
 (0)