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( //