diff --git a/pom.xml b/pom.xml
index 617cc011d3..8167941d6c 100755
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-jpa-parent
- 3.4.0-SNAPSHOT
+ 3.4.x-GH-3136-SNAPSHOT
pom
Spring Data JPA Parent
diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml
index 8b836ae2f3..a281bfe7df 100755
--- a/spring-data-envers/pom.xml
+++ b/spring-data-envers/pom.xml
@@ -5,12 +5,12 @@
org.springframework.data
spring-data-envers
- 3.4.0-SNAPSHOT
+ 3.4.x-GH-3136-SNAPSHOT
org.springframework.data
spring-data-jpa-parent
- 3.4.0-SNAPSHOT
+ 3.4.x-GH-3136-SNAPSHOT
../pom.xml
diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml
index a90c1f7282..06e8ff9406 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.4.0-SNAPSHOT
+ 3.4.x-GH-3136-SNAPSHOT
../pom.xml
diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml
index 3006702796..bf5f3a5842 100644
--- a/spring-data-jpa/pom.xml
+++ b/spring-data-jpa/pom.xml
@@ -6,7 +6,7 @@
org.springframework.data
spring-data-jpa
- 3.4.0-SNAPSHOT
+ 3.4.x-GH-3136-SNAPSHOT
Spring Data JPA
Spring Data module for JPA repositories.
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-jpa-parent
- 3.4.0-SNAPSHOT
+ 3.4.x-GH-3136-SNAPSHOT
../pom.xml
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 3ed025efb5..b41427a202 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
@@ -309,6 +309,7 @@ scalar_expression
| datetime_expression
| boolean_expression
| case_expression
+ | cast_function
| entity_type_expression
;
@@ -450,6 +451,7 @@ string_expression
| case_expression
| function_invocation
| '(' subquery ')'
+ | string_expression '||' string_expression
;
datetime_expression
@@ -534,6 +536,9 @@ functions_returning_strings
| TRIM '(' ((trim_specification)? (trim_character)? FROM)? string_expression ')'
| LOWER '(' string_expression ')'
| UPPER '(' string_expression ')'
+ | REPLACE '(' string_expression ',' string_expression ',' string_expression ')'
+ | LEFT '(' string_expression ',' arithmetic_expression ')'
+ | RIGHT '(' string_expression ',' arithmetic_expression ')'
;
trim_specification
@@ -543,7 +548,7 @@ trim_specification
;
cast_function
- : CAST '(' single_valued_path_expression identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')'
+ : CAST '(' single_valued_path_expression (identification_variable)? identification_variable ('(' numeric_literal (',' numeric_literal)* ')')? ')'
;
function_invocation
@@ -609,6 +614,14 @@ nullif_expression
: NULLIF '(' scalar_expression ',' scalar_expression ')'
;
+type_literal
+ : STRING
+ | INTEGER
+ | LONG
+ | FLOAT
+ | DOUBLE
+ ;
+
/*******************
Gaps in the spec.
*******************/
@@ -621,6 +634,7 @@ trim_character
identification_variable
: IDENTIFICATION_VARIABLE
| f=(COUNT
+ | AS
| DATE
| FROM
| INNER
@@ -630,11 +644,13 @@ identification_variable
| ORDER
| OUTER
| POWER
+ | RIGHT
| FLOOR
| SIGN
| TIME
| TYPE
| VALUE)
+ | type_literal
;
constructor_name
@@ -811,6 +827,8 @@ reserved_word
|OR
|ORDER
|OUTER
+ |REPLACE
+ |RIGHT
|POWER
|ROUND
|SELECT
@@ -894,6 +912,7 @@ DATETIME : D A T E T I M E ;
DELETE : D E L E T E;
DESC : D E S C;
DISTINCT : D I S T I N C T;
+DOUBLE : D O U B L E;
END : E N D;
ELSE : E L S E;
EMPTY : E M P T Y;
@@ -906,6 +925,7 @@ EXTRACT : E X T R A C T;
FALSE : F A L S E;
FETCH : F E T C H;
FIRST : F I R S T;
+FLOAT : F L O A T;
FLOOR : F L O O R;
FROM : F R O M;
FUNCTION : F U N C T I O N;
@@ -914,6 +934,7 @@ HAVING : H A V I N G;
IN : I N;
INDEX : I N D E X;
INNER : I N N E R;
+INTEGER : I N T E G E R;
INTERSECT : I N T E R S E C T;
IS : I S;
JOIN : J O I N;
@@ -926,6 +947,7 @@ LIKE : L I K E;
LN : L N;
LOCAL : L O C A L;
LOCATE : L O C A T E;
+LONG : L O N G;
LOWER : L O W E R;
MAX : M A X;
MEMBER : M E M B E R;
@@ -944,6 +966,8 @@ ORDER : O R D E R;
OUTER : O U T E R;
POWER : P O W E R;
REGEXP : R E G E X P;
+REPLACE : R E P L A C E;
+RIGHT : R I G H T;
ROUND : R O U N D;
SELECT : S E L E C T;
SET : S E T;
@@ -951,6 +975,7 @@ SIGN : S I G N;
SIZE : S I Z E;
SOME : S O M E;
SQRT : S Q R T;
+STRING : S T R I N G;
SUBSTRING : S U B S T R I N G;
SUM : S U M;
THEN : T H E N;
@@ -970,7 +995,6 @@ 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 : '\'' (~ ('\'' | '\\')|'\\')* '\'' ;
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 a7f319b793..c21312c9aa 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
@@ -42,8 +42,26 @@ ql_statement
| delete_statement
;
+select_query
+ : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)?
+ ;
+
select_statement
- : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)?
+ : select_query
+ ;
+
+setOperator
+ : UNION ALL?
+ | INTERSECT ALL?
+ | EXCEPT ALL?
+ ;
+
+set_fuction
+ : setOperator set_function_select
+ ;
+
+set_function_select
+ : select_query
;
update_statement
@@ -293,6 +311,7 @@ scalar_expression
| datetime_expression
| boolean_expression
| case_expression
+ | cast_expression
| entity_type_expression
;
@@ -431,6 +450,7 @@ string_expression
| case_expression
| function_invocation
| '(' subquery ')'
+ | string_expression '||' string_expression
;
datetime_expression
@@ -514,7 +534,10 @@ functions_returning_strings
| SUBSTRING '(' string_expression ',' arithmetic_expression (',' arithmetic_expression)? ')'
| TRIM '(' ((trim_specification)? (trim_character)? FROM)? string_expression ')'
| LOWER '(' string_expression ')'
+ | REPLACE '(' string_expression ',' string_expression ',' string_expression ')'
| UPPER '(' string_expression ')'
+ | LEFT '(' string_expression ',' arithmetic_expression ')'
+ | RIGHT '(' string_expression ',' arithmetic_expression ')'
;
trim_specification
@@ -587,6 +610,10 @@ nullif_expression
: NULLIF '(' scalar_expression ',' scalar_expression ')'
;
+cast_expression
+ : CAST '(' string_expression AS type_literal ')'
+ ;
+
/*******************
Gaps in the spec.
*******************/
@@ -608,6 +635,7 @@ identification_variable
| ORDER
| OUTER
| POWER
+ | RIGHT
| FLOOR
| SIGN
| TIME
@@ -657,6 +685,14 @@ numeric_literal
| LONGLITERAL
;
+type_literal
+ : STRING
+ | INTEGER
+ | LONG
+ | FLOAT
+ | DOUBLE
+ ;
+
boolean_literal
: TRUE
| FALSE
@@ -788,6 +824,8 @@ reserved_word
|ORDER
|OUTER
|POWER
+ |REPLACE
+ |RIGHT
|ROUND
|SELECT
|SET
@@ -857,6 +895,7 @@ BETWEEN : B E T W E E N;
BOTH : B O T H;
BY : B Y;
CASE : C A S E;
+CAST : C A S T;
CEILING : C E I L I N G;
COALESCE : C O A L E S C E;
CONCAT : C O N C A T;
@@ -869,16 +908,19 @@ DATETIME : D A T E T I M E ;
DELETE : D E L E T E;
DESC : D E S C;
DISTINCT : D I S T I N C T;
+DOUBLE : D O U B L E;
END : E N D;
ELSE : E L S E;
EMPTY : E M P T Y;
ENTRY : E N T R Y;
ESCAPE : E S C A P E;
+EXCEPT : E X C E P T;
EXISTS : E X I S T S;
EXP : E X P;
EXTRACT : E X T R A C T;
FALSE : F A L S E;
FETCH : F E T C H;
+FLOAT : F L O A T;
FLOOR : F L O O R;
FROM : F R O M;
FUNCTION : F U N C T I O N;
@@ -887,6 +929,8 @@ HAVING : H A V I N G;
IN : I N;
INDEX : I N D E X;
INNER : I N N E R;
+INTEGER : I N T E G E R;
+INTERSECT : I N T E R S E C T;
IS : I S;
JOIN : J O I N;
KEY : K E Y;
@@ -897,6 +941,7 @@ LIKE : L I K E;
LN : L N;
LOCAL : L O C A L;
LOCATE : L O C A T E;
+LONG : L O N G;
LOWER : L O W E R;
MAX : M A X;
MEMBER : M E M B E R;
@@ -912,6 +957,8 @@ ON : O N;
OR : O R;
ORDER : O R D E R;
OUTER : O U T E R;
+REPLACE : R E P L A C E;
+RIGHT : R I G H T;
POWER : P O W E R;
ROUND : R O U N D;
SELECT : S E L E C T;
@@ -920,6 +967,7 @@ SIGN : S I G N;
SIZE : S I Z E;
SOME : S O M E;
SQRT : S Q R T;
+STRING : S T R I N G;
SUBSTRING : S U B S T R I N G;
SUM : S U M;
THEN : T H E N;
@@ -929,6 +977,7 @@ TREAT : T R E A T;
TRIM : T R I M;
TRUE : T R U E;
TYPE : T Y P E;
+UNION : U N I O N;
UPDATE : U P D A T E;
UPPER : U P P E R;
VALUE : V A L U E;
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 17e6e51a55..34e085ba47 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
@@ -23,6 +23,7 @@
import org.antlr.v4.runtime.tree.ParseTree;
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
+import org.springframework.util.ObjectUtils;
/**
* An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders an EQL query without making any changes.
@@ -1008,6 +1009,8 @@ public QueryTokenStream visitScalar_expression(EqlParser.Scalar_expressionContex
builder.append(visit(ctx.case_expression()));
} else if (ctx.entity_type_expression() != null) {
builder.append(visit(ctx.entity_type_expression()));
+ } else if (ctx.cast_function() != null) {
+ return (visit(ctx.cast_function()));
}
return builder;
@@ -1595,6 +1598,11 @@ public QueryTokenStream visitString_expression(EqlParser.String_expressionContex
builder.append(TOKEN_OPEN_PAREN);
builder.appendInline(visit(ctx.subquery()));
builder.append(TOKEN_CLOSE_PAREN);
+ } else if (!ObjectUtils.isEmpty(ctx.string_expression())) {
+
+ builder.appendInline(visit(ctx.string_expression(0)));
+ builder.append(TOKEN_DOUBLE_PIPE);
+ builder.appendExpression(visit(ctx.string_expression(1)));
}
return builder;
@@ -1926,6 +1934,32 @@ public QueryTokenStream visitFunctions_returning_strings(EqlParser.Functions_ret
builder.append(TOKEN_OPEN_PAREN);
builder.appendInline(visit(ctx.string_expression(0)));
builder.append(TOKEN_CLOSE_PAREN);
+ } else if (ctx.LEFT() != null) {
+
+ builder.append(QueryTokens.token(ctx.LEFT()));
+ builder.append(TOKEN_OPEN_PAREN);
+ builder.appendInline(visit(ctx.string_expression(0)));
+ builder.append(TOKEN_COMMA);
+ builder.appendInline(visit(ctx.arithmetic_expression(0)));
+ builder.append(TOKEN_CLOSE_PAREN);
+ } else if (ctx.RIGHT() != null) {
+
+ builder.append(QueryTokens.token(ctx.RIGHT()));
+ builder.append(TOKEN_OPEN_PAREN);
+ builder.appendInline(visit(ctx.string_expression(0)));
+ builder.append(TOKEN_COMMA);
+ builder.appendInline(visit(ctx.arithmetic_expression(0)));
+ builder.append(TOKEN_CLOSE_PAREN);
+ } else if (ctx.REPLACE() != null) {
+
+ builder.append(QueryTokens.token(ctx.REPLACE()));
+ builder.append(TOKEN_OPEN_PAREN);
+ builder.appendInline(visit(ctx.string_expression(0)));
+ builder.append(TOKEN_COMMA);
+ builder.appendInline(visit(ctx.string_expression(1)));
+ builder.append(TOKEN_COMMA);
+ builder.appendInline(visit(ctx.string_expression(2)));
+ builder.append(TOKEN_CLOSE_PAREN);
}
return builder;
@@ -1952,9 +1986,9 @@ public QueryTokenStream visitCast_function(EqlParser.Cast_functionContext ctx) {
builder.append(TOKEN_OPEN_PAREN);
builder.appendInline(visit(ctx.single_valued_path_expression()));
builder.append(TOKEN_SPACE);
- builder.appendInline(visit(ctx.identification_variable()));
+ builder.appendInline(QueryTokenStream.concat(ctx.identification_variable(), this::visit, TOKEN_SPACE));
- if (ctx.numeric_literal() != null) {
+ if (!ObjectUtils.isEmpty(ctx.numeric_literal())) {
builder.append(TOKEN_OPEN_PAREN);
builder.appendInline(QueryTokenStream.concat(ctx.numeric_literal(), this::visit, TOKEN_COMMA));
@@ -2055,6 +2089,14 @@ public QueryTokenStream visitCase_expression(EqlParser.Case_expressionContext ct
}
}
+ @Override
+ public QueryRendererBuilder visitType_literal(EqlParser.Type_literalContext ctx) {
+
+ QueryRendererBuilder builder = QueryRenderer.builder();
+ ctx.children.forEach(it -> builder.append(QueryTokens.expression(it.getText())));
+ return builder;
+ }
+
@Override
public QueryTokenStream visitGeneral_case_expression(EqlParser.General_case_expressionContext ctx) {
@@ -2175,9 +2217,11 @@ public QueryTokenStream visitIdentification_variable(EqlParser.Identification_va
return QueryRendererBuilder.from(QueryTokens.expression(ctx.IDENTIFICATION_VARIABLE()));
} else if (ctx.f != null) {
return QueryRendererBuilder.from(QueryTokens.expression(ctx.f));
- } else {
- return QueryRenderer.builder();
+ } else if (ctx.type_literal() != null) {
+ return visit(ctx.type_literal());
}
+
+ return QueryRenderer.builder();
}
@Override
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java
index 6ceb6e171a..63f56862be 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java
@@ -42,7 +42,17 @@ class JpqlCountQueryTransformer extends JpqlQueryRenderer {
}
@Override
- public QueryRenderer.QueryRendererBuilder visitSelect_statement(JpqlParser.Select_statementContext ctx) {
+ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) {
+
+ if(ctx.select_query() != null) {
+ return visitSelect_query(ctx.select_query());
+ }
+
+ return QueryTokenStream.empty();
+ }
+
+ @Override
+ public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) {
QueryRendererBuilder builder = QueryRenderer.builder();
@@ -58,6 +68,9 @@ public QueryRenderer.QueryRendererBuilder visitSelect_statement(JpqlParser.Selec
if (ctx.having_clause() != null) {
builder.appendExpression(visit(ctx.having_clause()));
}
+ if(ctx.set_fuction() != null) {
+ builder.appendExpression(visit(ctx.set_fuction()));
+ }
return builder;
}
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 fe8fac1bfa..6ce42dddad 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
@@ -15,15 +15,28 @@
*/
package org.springframework.data.jpa.repository.query;
-import static org.springframework.data.jpa.repository.query.QueryTokens.*;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_CLOSE_PAREN;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_COLON;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_COMMA;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_DOT;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_EQUALS;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_OPEN_PAREN;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_QUESTION_MARK;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_DOUBLE_PIPE;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_SPACE;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_CLOSE_PAREN;
+import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_OPEN_PAREN;
import java.util.ArrayList;
import java.util.List;
import org.antlr.v4.runtime.tree.ParseTree;
-
+import org.springframework.data.jpa.repository.query.JpqlParser.Cast_expressionContext;
import org.springframework.data.jpa.repository.query.JpqlParser.Reserved_wordContext;
+import org.springframework.data.jpa.repository.query.JpqlParser.Set_fuctionContext;
+import org.springframework.data.jpa.repository.query.JpqlParser.Type_literalContext;
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
+import org.springframework.util.ObjectUtils;
/**
* An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that renders a JPQL query without making any changes.
@@ -54,8 +67,17 @@ public QueryTokenStream visitQl_statement(JpqlParser.Ql_statementContext ctx) {
}
}
- @Override
- public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) {
+ @Override
+ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) {
+
+ if(ctx.select_query() != null) {
+ return visitSelect_query(ctx.select_query());
+ }
+
+ return QueryTokenStream.empty();
+ }
+
+ public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) {
QueryRendererBuilder builder = QueryRenderer.builder();
@@ -78,6 +100,10 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext
builder.appendExpression(visit(ctx.orderby_clause()));
}
+ if(ctx.set_fuction() != null) {
+ builder.appendExpression(visit(ctx.set_fuction()));
+ }
+
return builder;
}
@@ -775,6 +801,19 @@ public QueryTokenStream visitOrderby_clause(JpqlParser.Orderby_clauseContext ctx
return builder;
}
+ @Override
+ public QueryTokenStream visitSet_fuction(Set_fuctionContext ctx) {
+
+ QueryRendererBuilder builder = QueryRenderer.builder();
+
+ builder.append(QueryTokens.expression(ctx.setOperator().getStart()));
+ if(ctx.setOperator().ALL() != null) {
+ builder.append(QueryTokens.expression(ctx.setOperator().ALL()));
+ }
+ builder.appendExpression(visit(ctx.set_function_select().select_query()));
+ return builder;
+ }
+
@Override
public QueryTokenStream visitOrderby_item(JpqlParser.Orderby_itemContext ctx) {
@@ -910,6 +949,8 @@ public QueryTokenStream visitScalar_expression(JpqlParser.Scalar_expressionConte
return visit(ctx.case_expression());
} else if (ctx.entity_type_expression() != null) {
return visit(ctx.entity_type_expression());
+ } else if (ctx.cast_expression() != null) {
+ return (visit(ctx.cast_expression()));
}
return QueryTokenStream.empty();
@@ -1425,6 +1466,11 @@ public QueryTokenStream visitString_expression(JpqlParser.String_expressionConte
builder.append(TOKEN_OPEN_PAREN);
builder.appendInline(visit(ctx.subquery()));
builder.append(TOKEN_CLOSE_PAREN);
+ } else if (!ObjectUtils.isEmpty(ctx.string_expression())) {
+
+ builder.appendInline(visit(ctx.string_expression(0)));
+ builder.append(TOKEN_DOUBLE_PIPE);
+ builder.appendExpression(visit(ctx.string_expression(1)));
}
return builder;
@@ -1745,6 +1791,29 @@ public QueryTokenStream visitFunctions_returning_strings(JpqlParser.Functions_re
builder.append(TOKEN_OPEN_PAREN);
builder.append(visit(ctx.string_expression(0)));
builder.append(TOKEN_CLOSE_PAREN);
+ } else if (ctx.LEFT() != null) {
+ builder.append(QueryTokens.token(ctx.LEFT()));
+ builder.append(TOKEN_OPEN_PAREN);
+ builder.appendInline(visit(ctx.string_expression(0)));
+ builder.append(TOKEN_COMMA);
+ builder.appendInline(visit(ctx.arithmetic_expression(0)));
+ builder.append(TOKEN_CLOSE_PAREN);
+ } else if (ctx.RIGHT() != null) {
+ builder.append(QueryTokens.token(ctx.RIGHT()));
+ builder.append(TOKEN_OPEN_PAREN);
+ builder.appendInline(visit(ctx.string_expression(0)));
+ builder.append(TOKEN_COMMA);
+ builder.appendInline(visit(ctx.arithmetic_expression(0)));
+ builder.append(TOKEN_CLOSE_PAREN);
+ } else if (ctx.REPLACE() != null) {
+ builder.append(QueryTokens.token(ctx.REPLACE()));
+ builder.append(TOKEN_OPEN_PAREN);
+ builder.appendInline(visit(ctx.string_expression(0)));
+ builder.append(TOKEN_COMMA);
+ builder.appendInline(visit(ctx.string_expression(1)));
+ builder.append(TOKEN_COMMA);
+ builder.appendInline(visit(ctx.string_expression(2)));
+ builder.append(TOKEN_CLOSE_PAREN);
}
return builder;
@@ -1847,6 +1916,26 @@ public QueryTokenStream visitCase_expression(JpqlParser.Case_expressionContext c
}
}
+ @Override
+ public QueryRendererBuilder visitCast_expression(Cast_expressionContext ctx) {
+ QueryRendererBuilder builder = QueryRenderer.builder();
+ builder.append(QueryTokens.token(ctx.CAST()));
+ builder.append(TOKEN_OPEN_PAREN);
+ builder.appendInline(visit(ctx.string_expression()));
+ builder.append(QueryTokens.expression(ctx.AS()));
+ builder.appendInline(visit(ctx.type_literal()));
+ builder.append(TOKEN_CLOSE_PAREN);
+ return builder;
+ }
+
+ @Override
+ public QueryRendererBuilder visitType_literal(Type_literalContext ctx) {
+
+ QueryRendererBuilder builder = QueryRenderer.builder();
+ ctx.children.forEach(it -> builder.append(QueryTokens.expression(it.getText())));
+ return builder;
+ }
+
@Override
public QueryTokenStream visitGeneral_case_expression(JpqlParser.General_case_expressionContext ctx) {
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java
index a545171bbf..f182ad2039 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java
@@ -50,6 +50,16 @@ class JpqlSortedQueryTransformer extends JpqlQueryRenderer {
@Override
public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) {
+ if(ctx.select_query() != null) {
+ return visitSelect_query(ctx.select_query());
+ }
+
+ return QueryTokenStream.empty();
+ }
+
+ @Override
+ public QueryTokenStream visitSelect_query(JpqlParser.Select_queryContext ctx) {
+
QueryRendererBuilder builder = QueryRenderer.builder();
builder.appendExpression(visit(ctx.select_clause()));
@@ -67,12 +77,16 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext
builder.appendExpression(visit(ctx.having_clause()));
}
- doVisitOrderBy(builder, ctx);
+ if(ctx.set_fuction() != null) {
+ builder.appendExpression(visit(ctx.set_fuction()));
+ } else {
+ doVisitOrderBy(builder, ctx);
+ }
return builder;
}
- private void doVisitOrderBy(QueryRendererBuilder builder, JpqlParser.Select_statementContext ctx) {
+ private void doVisitOrderBy(QueryRendererBuilder builder, JpqlParser.Select_queryContext ctx) {
if (ctx.orderby_clause() != null) {
QueryTokenStream existingOrder = visit(ctx.orderby_clause());
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 bbfbffe1ab..a0c556ee99 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
@@ -20,6 +20,8 @@
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.data.jpa.repository.query.QueryRenderer.TokenRenderer;
/**
@@ -414,4 +416,53 @@ void isNullAndIsNotNull() {
assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT null OR e.active = true)");
assertQuery("SELECT e FROM Employee e WHERE (e.active IS NOT NULL OR e.active = true)");
}
+
+
+ @Test // GH-3496
+ void lateralShouldBeAValidParameter() {
+
+ assertQuery("select e from Employee e where e.lateral = :_lateral");
+ assertQuery("select te from TestEntity te where te.lateral = :lateral");
+ }
+
+ @Test // GH-3136
+ void intersect() {
+
+ assertQuery("""
+ SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode1
+ INTERSECT SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode2
+ """);
+ }
+
+ @Test // GH-3136
+ void except() {
+
+ assertQuery("""
+ SELECT e FROM Employee e
+ EXCEPT SELECT e FROM Employee e WHERE e.salary > e.manager.salary
+ """);
+ }
+
+ @ParameterizedTest // GH-3136
+ @ValueSource(strings = {"STRING", "INTEGER", "FLOAT", "DOUBLE"})
+ void jpqlCast(String targetType) {
+ assertQuery("SELECT CAST(e.salary AS %s) FROM Employee e".formatted(targetType));
+ }
+
+ @ParameterizedTest // GH-3136
+ @ValueSource(strings = {"LEFT", "RIGHT"})
+ void leftRightStringFunctions(String keyword) {
+ assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword));
+ }
+
+ @Test // GH-3136
+ void replaceStringFunctions() {
+ assertQuery("SELECT REPLACE(e.name, 'o', 'a') FROM Employee e");
+ assertQuery("SELECT REPLACE(e.name, ' ', '_') FROM Employee e");
+ }
+
+ @Test // GH-3136
+ void stringConcatWithPipes() {
+ assertQuery("SELECT e.firstname || e.lastname AS name FROM Employee e");
+ }
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java
index 91a4bb761e..242ec81b83 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryRendererTests.java
@@ -1039,11 +1039,4 @@ void entityNameWithPackageContainingReservedWord(String reservedWord) {
String source = "select new com.company.%s.thing.stuff.ClassName(e.id) from Experience e".formatted(reservedWord);
assertQuery(source);
}
-
- @Test // GH-3496
- void lateralShouldBeAValidParameter() {
-
- assertQuery("select e from Employee e where e.lateral = :_lateral");
- assertQuery("select te from TestEntity te where te.lateral = :lateral");
- }
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java
index 8a23e279cd..6f0f211643 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryRendererTests.java
@@ -1684,4 +1684,16 @@ void entityNameWithPackageContainingReservedWord(String reservedWord) {
String source = "select new com.company.%s.thing.stuff.ClassName(e.id) from Experience e".formatted(reservedWord);
assertQuery(source);
}
+
+ @ParameterizedTest // GH-3136
+ @ValueSource(strings = {"LEFT", "RIGHT"})
+ void leftRightStringFunctions(String keyword) {
+ assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword));
+ }
+
+ @Test // GH-3136
+ void replaceStringFunctions() {
+ assertQuery("SELECT REPLACE(e.name, 'o', 'a') FROM Employee e");
+ assertQuery("SELECT REPLACE(e.name, ' ', '_') FROM Employee e");
+ }
}
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 aadf2c2589..58bb071648 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
@@ -20,6 +20,8 @@
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
/**
* Test to verify compliance of {@link JpqlParser} with standard SQL. Other than {@link JpqlSpecificationTests} tests in
@@ -70,4 +72,54 @@ void newWithStrings() {
assertQuery("select new com.example.demo.SampleObject(se.id, se.sampleValue, \"java\") from SampleEntity se");
}
+ @Test // GH-3136
+ void union() {
+
+ assertQuery("""
+ SELECT MAX(e.salary) FROM Employee e WHERE e.address.city = :city1
+ UNION SELECT MAX(e.salary) FROM Employee e WHERE e.address.city = :city2
+ """);
+ }
+
+ @Test // GH-3136
+ void intersect() {
+
+ assertQuery("""
+ SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode1
+ INTERSECT SELECT e FROM Employee e JOIN e.phones p WHERE p.areaCode = :areaCode2
+ """);
+ }
+
+ @Test // GH-3136
+ void except() {
+
+ assertQuery("""
+ SELECT e FROM Employee e
+ EXCEPT SELECT e FROM Employee e WHERE e.salary > e.manager.salary
+ """);
+ }
+
+ @ParameterizedTest // GH-3136
+ @ValueSource(strings = {"STRING", "INTEGER", "FLOAT", "DOUBLE"})
+ void cast(String targetType) {
+ assertQuery("SELECT CAST(e.salary AS %s) FROM Employee e".formatted(targetType));
+ }
+
+ @ParameterizedTest // GH-3136
+ @ValueSource(strings = {"LEFT", "RIGHT"})
+ void leftRightStringFunctions(String keyword) {
+ assertQuery("SELECT %s(e.name, 3) FROM Employee e".formatted(keyword));
+ }
+
+ @Test // GH-3136
+ void replaceStringFunctions() {
+ assertQuery("SELECT REPLACE(e.name, 'o', 'a') FROM Employee e");
+ assertQuery("SELECT REPLACE(e.name, ' ', '_') FROM Employee e");
+ }
+
+ @Test // GH-3136
+ void stringConcatWithPipes() {
+ assertQuery("SELECT e.firstname || e.lastname AS name FROM Employee e");
+ }
+
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java
index eb04b8de07..547e93f9c7 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java
@@ -776,6 +776,15 @@ void sortingRecognizesJoinAliases() {
""");
}
+ @Test // GH-3427
+ void sortShouldBeAppendedToFullSelectOnlyInCaseOfSetOperator() {
+
+ String source = "SELECT tb FROM Test tb WHERE (tb.type='A') UNION SELECT tb FROM Test tb WHERE (tb.type='B')";
+ String target = createQueryFor(source, Sort.by("Type").ascending());
+
+ assertThat(target).isEqualTo("SELECT tb FROM Test tb WHERE (tb.type = 'A') UNION SELECT tb FROM Test tb WHERE (tb.type = 'B') order by tb.Type asc");
+ }
+
static Stream queriesWithReservedWordsAsIdentifiers() {
return Stream.of( //