Skip to content

Commit b0a2922

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 d6ffa68 commit b0a2922

File tree

8 files changed

+325
-99
lines changed

8 files changed

+325
-99
lines changed

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

+19-3
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,8 @@ arithmetic_primary
441441
| functions_returning_numerics
442442
| aggregate_expression
443443
| case_expression
444-
| cast_function
444+
| arithmetic_cast_function
445+
| type_cast_function
445446
| function_invocation
446447
| '(' subquery ')'
447448
;
@@ -454,6 +455,8 @@ string_expression
454455
| aggregate_expression
455456
| case_expression
456457
| function_invocation
458+
| string_cast_function
459+
| type_cast_function
457460
| '(' subquery ')'
458461
;
459462

@@ -547,8 +550,16 @@ trim_specification
547550
| BOTH
548551
;
549552

550-
cast_function
551-
: CAST '(' single_valued_path_expression identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')'
553+
arithmetic_cast_function
554+
: CAST '(' string_expression (AS)? f=(INTEGER|LONG|FLOAT|DOUBLE) ')'
555+
;
556+
557+
type_cast_function
558+
: CAST '(' scalar_expression (AS)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')'
559+
;
560+
561+
string_cast_function
562+
: CAST '(' scalar_expression (AS)? STRING ')'
552563
;
553564

554565
function_invocation
@@ -903,6 +914,7 @@ DATETIME : D A T E T I M E ;
903914
DELETE : D E L E T E;
904915
DESC : D E S C;
905916
DISTINCT : D I S T I N C T;
917+
DOUBLE : D O U B L E;
906918
END : E N D;
907919
ELSE : E L S E;
908920
EMPTY : E M P T Y;
@@ -916,6 +928,7 @@ FALSE : F A L S E;
916928
FETCH : F E T C H;
917929
FIRST : F I R S T;
918930
FLOOR : F L O O R;
931+
FLOAT : F L O A T;
919932
FROM : F R O M;
920933
FUNCTION : F U N C T I O N;
921934
GROUP : G R O U P;
@@ -925,6 +938,7 @@ INDEX : I N D E X;
925938
INNER : I N N E R;
926939
INTERSECT : I N T E R S E C T;
927940
IS : I S;
941+
INTEGER : I N T E G E R;
928942
JOIN : J O I N;
929943
KEY : K E Y;
930944
LAST : L A S T;
@@ -935,6 +949,7 @@ LIKE : L I K E;
935949
LN : L N;
936950
LOCAL : L O C A L;
937951
LOCATE : L O C A T E;
952+
LONG : L O N G;
938953
LOWER : L O W E R;
939954
MAX : M A X;
940955
MEMBER : M E M B E R;
@@ -961,6 +976,7 @@ SIZE : S I Z E;
961976
SOME : S O M E;
962977
SQRT : S Q R T;
963978
SUBSTRING : S U B S T R I N G;
979+
STRING : S T R I N G;
964980
SUM : S U M;
965981
THEN : T H E N;
966982
TIME : T I M E;

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

+33-7
Original file line numberDiff line numberDiff line change
@@ -387,13 +387,15 @@ all_or_any_expression
387387
;
388388

389389
comparison_expression
390-
: string_expression comparison_operator (string_expression | all_or_any_expression)
391-
| boolean_expression op=(EQUAL | NOT_EQUAL) (boolean_expression | all_or_any_expression)
392-
| enum_expression op=(EQUAL | NOT_EQUAL) (enum_expression | all_or_any_expression)
393-
| datetime_expression comparison_operator (datetime_expression | all_or_any_expression)
394-
| entity_expression op=(EQUAL | NOT_EQUAL) (entity_expression | all_or_any_expression)
395-
| arithmetic_expression comparison_operator (arithmetic_expression | all_or_any_expression)
396-
| entity_type_expression op=(EQUAL | NOT_EQUAL) entity_type_expression
390+
: string_expression comparison_operator (string_expression | all_or_any_expression) #StringComparison
391+
| boolean_expression op=(EQUAL | NOT_EQUAL) (boolean_expression | all_or_any_expression) #BooleanComparison
392+
| boolean_expression #DirectBooleanCheck
393+
| enum_expression op=(EQUAL | NOT_EQUAL) (enum_expression | all_or_any_expression) #EnumComparison
394+
| datetime_expression comparison_operator (datetime_expression | all_or_any_expression) #DatetimeComparison
395+
| entity_expression op=(EQUAL | NOT_EQUAL) (entity_expression | all_or_any_expression) #EntityComparison
396+
| arithmetic_expression comparison_operator (arithmetic_expression | all_or_any_expression) #ArithmeticComparison
397+
| entity_type_expression op=(EQUAL | NOT_EQUAL) entity_type_expression #EntityTypeComparison
398+
| string_expression REGEXP string_literal #RegexpComparison
397399
;
398400

399401
comparison_operator
@@ -427,6 +429,8 @@ arithmetic_primary
427429
| functions_returning_numerics
428430
| aggregate_expression
429431
| case_expression
432+
| arithmetic_cast_function
433+
| type_cast_function
430434
| function_invocation
431435
| '(' subquery ')'
432436
;
@@ -439,6 +443,8 @@ string_expression
439443
| aggregate_expression
440444
| case_expression
441445
| function_invocation
446+
| string_cast_function
447+
| type_cast_function
442448
| '(' subquery ')'
443449
;
444450

@@ -532,6 +538,17 @@ trim_specification
532538
| BOTH
533539
;
534540

541+
arithmetic_cast_function
542+
: CAST '(' string_expression (AS)? f=(INTEGER|LONG|FLOAT|DOUBLE) ')'
543+
;
544+
545+
type_cast_function
546+
: CAST '(' scalar_expression (AS)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')'
547+
;
548+
549+
string_cast_function
550+
: CAST '(' scalar_expression (AS)? STRING ')'
551+
;
535552

536553
function_invocation
537554
: FUNCTION '(' function_name (',' function_arg)* ')'
@@ -750,6 +767,7 @@ reserved_word
750767
|BOTH
751768
|BY
752769
|CASE
770+
|CAST
753771
|CEILING
754772
|COALESCE
755773
|CONCAT
@@ -870,6 +888,7 @@ BETWEEN : B E T W E E N;
870888
BOTH : B O T H;
871889
BY : B Y;
872890
CASE : C A S E;
891+
CAST : C A S T;
873892
CEILING : C E I L I N G;
874893
COALESCE : C O A L E S C E;
875894
CONCAT : C O N C A T;
@@ -882,6 +901,7 @@ DATETIME : D A T E T I M E ;
882901
DELETE : D E L E T E;
883902
DESC : D E S C;
884903
DISTINCT : D I S T I N C T;
904+
DOUBLE : D O U B L E;
885905
END : E N D;
886906
ELSE : E L S E;
887907
EMPTY : E M P T Y;
@@ -894,14 +914,17 @@ FALSE : F A L S E;
894914
FETCH : F E T C H;
895915
FIRST : F I R S T;
896916
FLOOR : F L O O R;
917+
FLOAT : F L O A T;
897918
FROM : F R O M;
898919
FUNCTION : F U N C T I O N;
899920
GROUP : G R O U P;
900921
HAVING : H A V I N G;
901922
IN : I N;
902923
INDEX : I N D E X;
903924
INNER : I N N E R;
925+
INTERSECT : I N T E R S E C T;
904926
IS : I S;
927+
INTEGER : I N T E G E R;
905928
JOIN : J O I N;
906929
KEY : K E Y;
907930
LAST : L A S T;
@@ -912,6 +935,7 @@ LIKE : L I K E;
912935
LN : L N;
913936
LOCAL : L O C A L;
914937
LOCATE : L O C A T E;
938+
LONG : L O N G;
915939
LOWER : L O W E R;
916940
MAX : M A X;
917941
MEMBER : M E M B E R;
@@ -929,6 +953,7 @@ OR : O R;
929953
ORDER : O R D E R;
930954
OUTER : O U T E R;
931955
POWER : P O W E R;
956+
REGEXP : R E G E X P;
932957
ROUND : R O U N D;
933958
SELECT : S E L E C T;
934959
SET : S E T;
@@ -937,6 +962,7 @@ SIZE : S I Z E;
937962
SOME : S O M E;
938963
SQRT : S Q R T;
939964
SUBSTRING : S U B S T R I N G;
965+
STRING : S T R I N G;
940966
SUM : S U M;
941967
THEN : T H E N;
942968
TIME : T I M E;

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

+54-11
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

2728
/**
2829
* An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an EQL query without making any changes.
@@ -49,7 +50,7 @@ public QueryTokenStream visitQl_statement(EqlParser.Ql_statementContext ctx) {
4950
} else if (ctx.delete_statement() != null) {
5051
return visit(ctx.delete_statement());
5152
} else {
52-
return QueryRenderer.builder();
53+
return QueryTokenStream.empty();
5354
}
5455
}
5556

@@ -1312,14 +1313,14 @@ public QueryTokenStream visitSimple_entity_or_value_expression(
13121313
QueryRendererBuilder builder = QueryRenderer.builder();
13131314

13141315
if (ctx.identification_variable() != null) {
1315-
builder.append(visit(ctx.identification_variable()));
1316+
return visit(ctx.identification_variable());
13161317
} else if (ctx.input_parameter() != null) {
1317-
builder.append(visit(ctx.input_parameter()));
1318+
return visit(ctx.input_parameter());
13181319
} else if (ctx.literal() != null) {
1319-
builder.append(visit(ctx.literal()));
1320+
return visit(ctx.literal());
13201321
}
13211322

1322-
return builder;
1323+
return QueryTokenStream.empty();
13231324
}
13241325

13251326
@Override
@@ -1565,8 +1566,10 @@ public QueryTokenStream visitArithmetic_primary(EqlParser.Arithmetic_primaryCont
15651566
builder.append(visit(ctx.aggregate_expression()));
15661567
} else if (ctx.case_expression() != null) {
15671568
builder.append(visit(ctx.case_expression()));
1568-
} else if (ctx.cast_function() != null) {
1569-
builder.append(visit(ctx.cast_function()));
1569+
} else if (ctx.arithmetic_cast_function() != null) {
1570+
builder.append(visit(ctx.arithmetic_cast_function()));
1571+
} else if (ctx.type_cast_function() != null) {
1572+
builder.append(visit(ctx.type_cast_function()));
15701573
} else if (ctx.function_invocation() != null) {
15711574
builder.append(visit(ctx.function_invocation()));
15721575
} else if (ctx.subquery() != null) {
@@ -1596,6 +1599,10 @@ public QueryTokenStream visitString_expression(EqlParser.String_expressionContex
15961599
builder.append(visit(ctx.aggregate_expression()));
15971600
} else if (ctx.case_expression() != null) {
15981601
builder.append(visit(ctx.case_expression()));
1602+
} else if (ctx.string_cast_function() != null) {
1603+
builder.append(visit(ctx.string_cast_function()));
1604+
} else if (ctx.type_cast_function() != null) {
1605+
builder.append(visit(ctx.type_cast_function()));
15991606
} else if (ctx.function_invocation() != null) {
16001607
builder.append(visit(ctx.function_invocation()));
16011608
} else if (ctx.subquery() != null) {
@@ -1952,17 +1959,36 @@ public QueryTokenStream visitTrim_specification(EqlParser.Trim_specificationCont
19521959
}
19531960

19541961
@Override
1955-
public QueryTokenStream visitCast_function(EqlParser.Cast_functionContext ctx) {
1962+
public QueryTokenStream visitArithmetic_cast_function(EqlParser.Arithmetic_cast_functionContext ctx) {
19561963

19571964
QueryRendererBuilder builder = QueryRenderer.builder();
19581965

19591966
builder.append(QueryTokens.token(ctx.CAST()));
19601967
builder.append(TOKEN_OPEN_PAREN);
1961-
builder.appendInline(visit(ctx.single_valued_path_expression()));
1962-
builder.append(TOKEN_SPACE);
1968+
builder.appendExpression(visit(ctx.string_expression()));
1969+
if (ctx.AS() != null) {
1970+
builder.append(QueryTokens.expression(ctx.AS()));
1971+
}
1972+
builder.append(QueryTokens.token(ctx.f));
1973+
builder.append(TOKEN_CLOSE_PAREN);
1974+
1975+
return builder;
1976+
}
1977+
1978+
@Override
1979+
public QueryTokenStream visitType_cast_function(EqlParser.Type_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.scalar_expression()));
1986+
if (ctx.AS() != null) {
1987+
builder.append(QueryTokens.expression(ctx.AS()));
1988+
}
19631989
builder.appendInline(visit(ctx.identification_variable()));
19641990

1965-
if (ctx.numeric_literal() != null) {
1991+
if (!CollectionUtils.isEmpty(ctx.numeric_literal())) {
19661992

19671993
builder.append(TOKEN_OPEN_PAREN);
19681994
builder.appendInline(QueryTokenStream.concat(ctx.numeric_literal(), this::visit, TOKEN_COMMA));
@@ -1973,6 +1999,23 @@ public QueryTokenStream visitCast_function(EqlParser.Cast_functionContext ctx) {
19731999
return builder;
19742000
}
19752001

2002+
@Override
2003+
public QueryTokenStream visitString_cast_function(EqlParser.String_cast_functionContext ctx) {
2004+
2005+
QueryRendererBuilder builder = QueryRenderer.builder();
2006+
2007+
builder.append(QueryTokens.token(ctx.CAST()));
2008+
builder.append(TOKEN_OPEN_PAREN);
2009+
builder.appendExpression(visit(ctx.scalar_expression()));
2010+
if (ctx.AS() != null) {
2011+
builder.append(QueryTokens.expression(ctx.AS()));
2012+
}
2013+
builder.append(QueryTokens.token(ctx.STRING()));
2014+
builder.append(TOKEN_CLOSE_PAREN);
2015+
2016+
return builder;
2017+
}
2018+
19762019
@Override
19772020
public QueryTokenStream visitFunction_invocation(EqlParser.Function_invocationContext ctx) {
19782021

0 commit comments

Comments
 (0)