diff --git a/pom.xml b/pom.xml
index b82816e812..fcd5af54c6 100755
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-jpa-parent
- 4.0.0-SNAPSHOT
+ 4.0.0-GH-3902-SNAPSHOT
pom
Spring Data JPA Parent
diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml
index 43c08369f6..3abac42017 100755
--- a/spring-data-envers/pom.xml
+++ b/spring-data-envers/pom.xml
@@ -5,12 +5,12 @@
org.springframework.data
spring-data-envers
- 4.0.0-SNAPSHOT
+ 4.0.0-GH-3902-SNAPSHOT
org.springframework.data
spring-data-jpa-parent
- 4.0.0-SNAPSHOT
+ 4.0.0-GH-3902-SNAPSHOT
../pom.xml
diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml
index af5244a230..1b16cd8703 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
- 4.0.0-SNAPSHOT
+ 4.0.0-GH-3902-SNAPSHOT
../pom.xml
diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml
index cdb738558f..5707317dcc 100644
--- a/spring-data-jpa/pom.xml
+++ b/spring-data-jpa/pom.xml
@@ -7,7 +7,7 @@
org.springframework.data
spring-data-jpa
- 4.0.0-SNAPSHOT
+ 4.0.0-GH-3902-SNAPSHOT
Spring Data JPA
Spring Data module for JPA repositories.
@@ -16,7 +16,7 @@
org.springframework.data
spring-data-jpa-parent
- 4.0.0-SNAPSHOT
+ 4.0.0-GH-3902-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 86b0111ca1..71d9c05ab6 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
@@ -18,10 +18,9 @@ grammar Eql;
@header {
/**
* Implementation of EclipseLink Query Language (EQL)
- * See:
- * * https://eclipse.dev/eclipselink/documentation/3.0/jpa/extensions/jpql.htm
- * * https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL
*
+ * @see https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL
+ * @see https://eclipse.dev/eclipselink/documentation/3.0/jpa/extensions/jpql.htm
* @author Greg Turnquist
* @author Christoph Strobl
* @since 3.2
@@ -43,7 +42,8 @@ ql_statement
;
select_statement
- : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)?
+ : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)? # SelectQuery
+ | from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)? # FromQuery
;
setOperator
@@ -80,7 +80,7 @@ identification_variable_declaration
;
range_variable_declaration
- : (entity_name|function_invocation) AS? identification_variable
+ : (entity_name|function_invocation) AS? identification_variable?
;
join
@@ -246,14 +246,15 @@ orderby_clause
: ORDER BY orderby_item (',' orderby_item)*
;
-// TODO Error in spec BNF, correctly shown elsewhere in spec.
orderby_item
- : state_field_path_expression (ASC | DESC)? nullsPrecedence?
- | general_identification_variable (ASC | DESC)? nullsPrecedence?
- | result_variable (ASC | DESC)? nullsPrecedence?
- | string_expression (ASC | DESC)? nullsPrecedence?
- | scalar_expression (ASC | DESC)? nullsPrecedence?
- |
+ : orderby_expression (ASC | DESC)? nullsPrecedence?
+ ;
+
+orderby_expression
+ : state_field_path_expression
+ | general_identification_variable
+ | string_expression
+ | scalar_expression
;
nullsPrecedence
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 b3afdb9b1e..6632690a42 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
@@ -43,7 +43,8 @@ ql_statement
;
select_statement
- : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)?
+ : select_clause from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)? # SelectQuery
+ | from_clause (where_clause)? (groupby_clause)? (having_clause)? (orderby_clause)? (set_fuction)? # FromQuery
;
setOperator
@@ -72,6 +73,7 @@ from_clause
identificationVariableDeclarationOrCollectionMemberDeclaration
: identification_variable_declaration
| collection_member_declaration
+ | '(' subquery ')' identification_variable
;
identification_variable_declaration
@@ -79,11 +81,11 @@ identification_variable_declaration
;
range_variable_declaration
- : entity_name AS? identification_variable
+ : entity_name AS? identification_variable?
;
join
- : join_spec join_association_path_expression AS? identification_variable (join_condition)?
+ : join_spec join_association_path_expression AS? identification_variable? (join_condition)?
;
fetch_join
@@ -106,11 +108,11 @@ join_association_path_expression
;
join_collection_valued_path_expression
- : identification_variable '.' (single_valued_embeddable_object_field '.')* collection_valued_field
+ : (identification_variable '.')? (single_valued_embeddable_object_field '.')* collection_valued_field
;
join_single_valued_path_expression
- : identification_variable '.' (single_valued_embeddable_object_field '.')* single_valued_object_field
+ : (identification_variable '.')? (single_valued_embeddable_object_field '.')* single_valued_object_field
;
collection_member_declaration
@@ -244,9 +246,15 @@ orderby_clause
: ORDER BY orderby_item (',' orderby_item)*
;
-// TODO Error in spec BNF, correctly shown elsewhere in spec.
orderby_item
- : (state_field_path_expression | general_identification_variable | result_variable ) (ASC | DESC)? nullsPrecedence?
+ : orderby_expression (ASC | DESC)? nullsPrecedence?
+ ;
+
+orderby_expression
+ : state_field_path_expression
+ | general_identification_variable
+ | string_expression
+ | scalar_expression
;
nullsPrecedence
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java
index 2d8e27c167..9b4f06b381 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java
@@ -17,10 +17,11 @@
import static org.springframework.data.jpa.repository.query.QueryTokens.*;
-import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
-
import org.jspecify.annotations.Nullable;
+
+import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
import org.springframework.data.jpa.repository.query.QueryTransformers.CountSelectionTokenStream;
+import org.springframework.util.StringUtils;
/**
* An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed EQL query into a
@@ -43,7 +44,7 @@ class EqlCountQueryTransformer extends EqlQueryRenderer {
}
@Override
- public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext ctx) {
+ public QueryTokenStream visitSelectQuery(EqlParser.SelectQueryContext ctx) {
QueryRendererBuilder builder = QueryRenderer.builder();
@@ -63,6 +64,49 @@ public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext
return builder;
}
+ @Override
+ public QueryTokenStream visitFromQuery(EqlParser.FromQueryContext ctx) {
+
+ QueryRendererBuilder builder = QueryRenderer.builder();
+
+ QueryRendererBuilder countBuilder = QueryRenderer.builder();
+ countBuilder.append(TOKEN_SELECT_COUNT);
+
+ if (countProjection != null) {
+ countBuilder.append(QueryTokens.token(countProjection));
+ } else {
+ if (primaryFromAlias == null) {
+ countBuilder.append(TOKEN_DOUBLE_UNDERSCORE);
+ } else {
+ countBuilder.append(QueryTokens.token(primaryFromAlias));
+ }
+ }
+
+ countBuilder.append(TOKEN_CLOSE_PAREN);
+
+ builder.appendExpression(countBuilder);
+
+ if (ctx.from_clause() != null) {
+ builder.appendExpression(visit(ctx.from_clause()));
+ if (primaryFromAlias == null) {
+ builder.append(TOKEN_AS);
+ builder.append(TOKEN_DOUBLE_UNDERSCORE);
+ }
+ }
+
+ if (ctx.where_clause() != null) {
+ builder.appendExpression(visit(ctx.where_clause()));
+ }
+ if (ctx.groupby_clause() != null) {
+ builder.appendExpression(visit(ctx.groupby_clause()));
+ }
+ if (ctx.having_clause() != null) {
+ builder.appendExpression(visit(ctx.having_clause()));
+ }
+
+ return builder;
+ }
+
@Override
public QueryTokenStream visitSelect_clause(EqlParser.Select_clauseContext ctx) {
@@ -78,14 +122,21 @@ public QueryTokenStream visitSelect_clause(EqlParser.Select_clauseContext ctx) {
if (usesDistinct) {
nested.append(QueryTokens.expression(ctx.DISTINCT()));
nested.append(getDistinctCountSelection(QueryTokenStream.concat(ctx.select_item(), this::visit, TOKEN_COMMA)));
- } else {
+ } else if (StringUtils.hasText(primaryFromAlias)) {
nested.append(QueryTokens.token(primaryFromAlias));
+ } else {
+ if (ctx.select_item().isEmpty()) {
+ // cannot happen as per grammar, but you never know…
+ nested.append(QueryTokens.token("1"));
+ } else {
+ nested.append(visit(ctx.select_item().get(0)));
+ }
}
} else {
- builder.append(QueryTokens.token(countProjection));
if (usesDistinct) {
nested.append(QueryTokens.expression(ctx.DISTINCT()));
}
+ nested.append(QueryTokens.token(countProjection));
}
builder.appendInline(nested);
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java
index fa7fa5ec8e..71f65523b8 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java
@@ -21,8 +21,6 @@
import java.util.Collections;
import java.util.List;
-import org.springframework.data.jpa.repository.query.EqlParser.Range_variable_declarationContext;
-
import org.jspecify.annotations.Nullable;
/**
@@ -61,8 +59,9 @@ public Void visitSelect_clause(EqlParser.Select_clauseContext ctx) {
@Override
public Void visitRange_variable_declaration(EqlParser.Range_variable_declarationContext ctx) {
- if (primaryFromAlias == null) {
- primaryFromAlias = capturePrimaryAlias(ctx);
+ if (primaryFromAlias == null && ctx.identification_variable() != null && !EqlQueryRenderer.isSubquery(ctx)
+ && !EqlQueryRenderer.isSetQuery(ctx)) {
+ primaryFromAlias = ctx.identification_variable().getText();
}
return super.visitRange_variable_declaration(ctx);
@@ -75,11 +74,6 @@ public Void visitConstructor_expression(EqlParser.Constructor_expressionContext
return super.visitConstructor_expression(ctx);
}
- private static String capturePrimaryAlias(Range_variable_declarationContext ctx) {
- return ctx.identification_variable() != null ? ctx.identification_variable().getText()
- : ctx.entity_name().getText();
- }
-
private static List captureSelectItems(List selections,
EqlQueryRenderer itemRenderer) {
@@ -94,4 +88,5 @@ private static List captureSelectItems(List {
+ /**
+ * Is this AST tree a {@literal subquery}?
+ *
+ * @return boolean
+ */
+ static boolean isSubquery(ParserRuleContext ctx) {
+
+ if (ctx instanceof EqlParser.SubqueryContext) {
+ return true;
+ } else if (ctx instanceof EqlParser.Update_statementContext) {
+ return false;
+ } else if (ctx instanceof EqlParser.Delete_statementContext) {
+ return false;
+ } else {
+ return ctx.getParent() != null && isSubquery(ctx.getParent());
+ }
+ }
+
+ /**
+ * Is this AST tree a {@literal set} query that has been added through {@literal UNION|INTERSECT|EXCEPT}?
+ *
+ * @return boolean
+ */
+ static boolean isSetQuery(ParserRuleContext ctx) {
+
+ if (ctx instanceof EqlParser.Set_fuctionContext) {
+ return true;
+ }
+
+ return ctx.getParent() != null && isSetQuery(ctx.getParent());
+ }
+
@Override
public QueryTokenStream visitStart(EqlParser.StartContext ctx) {
return visit(ctx.ql_statement());
@@ -56,7 +90,7 @@ public QueryTokenStream visitQl_statement(EqlParser.Ql_statementContext ctx) {
}
@Override
- public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext ctx) {
+ public QueryTokenStream visitSelectQuery(EqlParser.SelectQueryContext ctx) {
QueryRendererBuilder builder = QueryRenderer.builder();
@@ -86,6 +120,36 @@ public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext
return builder;
}
+ @Override
+ public QueryTokenStream visitFromQuery(EqlParser.FromQueryContext ctx) {
+
+ QueryRendererBuilder builder = QueryRenderer.builder();
+
+ builder.appendExpression(visit(ctx.from_clause()));
+
+ if (ctx.where_clause() != null) {
+ builder.appendExpression(visit(ctx.where_clause()));
+ }
+
+ if (ctx.groupby_clause() != null) {
+ builder.appendExpression(visit(ctx.groupby_clause()));
+ }
+
+ if (ctx.having_clause() != null) {
+ builder.appendExpression(visit(ctx.having_clause()));
+ }
+
+ if (ctx.orderby_clause() != null) {
+ builder.appendExpression(visit(ctx.orderby_clause()));
+ }
+
+ if (ctx.set_fuction() != null) {
+ builder.appendExpression(visit(ctx.set_fuction()));
+ }
+
+ return builder;
+ }
+
@Override
public QueryTokenStream visitUpdate_statement(EqlParser.Update_statementContext ctx) {
@@ -149,7 +213,9 @@ public QueryTokenStream visitIdentificationVariableDeclarationOrCollectionMember
QueryRendererBuilder builder = QueryRenderer.builder();
builder.appendExpression(nested);
- builder.appendExpression(visit(ctx.identification_variable()));
+ if (ctx.identification_variable() != null) {
+ builder.appendExpression(visit(ctx.identification_variable()));
+ }
return builder;
} else {
@@ -185,7 +251,9 @@ public QueryTokenStream visitRange_variable_declaration(EqlParser.Range_variable
builder.append(QueryTokens.expression(ctx.AS()));
}
- builder.appendExpression(visit(ctx.identification_variable()));
+ if (ctx.identification_variable() != null) {
+ builder.appendExpression(visit(ctx.identification_variable()));
+ }
return builder;
}
@@ -309,6 +377,7 @@ public QueryTokenStream visitJoin_collection_valued_path_expression(
EqlParser.Join_collection_valued_path_expressionContext ctx) {
List items = new ArrayList<>(2 + ctx.single_valued_embeddable_object_field().size());
+
if (ctx.identification_variable() != null) {
items.add(ctx.identification_variable());
}
@@ -348,7 +417,9 @@ public QueryTokenStream visitCollection_member_declaration(EqlParser.Collection_
builder.append(QueryTokens.expression(ctx.AS()));
}
- builder.appendExpression(visit(ctx.identification_variable()));
+ if (ctx.identification_variable() != null) {
+ builder.appendExpression(visit(ctx.identification_variable()));
+ }
return builder;
}
@@ -823,22 +894,11 @@ public QueryTokenStream visitOrderby_item(EqlParser.Orderby_itemContext ctx) {
QueryRendererBuilder builder = QueryRenderer.builder();
- if (ctx.state_field_path_expression() != null) {
- builder.appendExpression(visit(ctx.state_field_path_expression()));
- } else if (ctx.general_identification_variable() != null) {
- builder.appendExpression(visit(ctx.general_identification_variable()));
- } else if (ctx.result_variable() != null) {
- builder.appendExpression(visit(ctx.result_variable()));
- } else if (ctx.string_expression() != null) {
- builder.appendExpression(visit(ctx.string_expression()));
- } else if (ctx.scalar_expression() != null) {
- builder.appendExpression(visit(ctx.scalar_expression()));
- }
+ builder.appendExpression(visit(ctx.orderby_expression()));
if (ctx.ASC() != null) {
builder.append(QueryTokens.expression(ctx.ASC()));
- }
- if (ctx.DESC() != null) {
+ } else if (ctx.DESC() != null) {
builder.append(QueryTokens.expression(ctx.DESC()));
}
@@ -849,6 +909,22 @@ public QueryTokenStream visitOrderby_item(EqlParser.Orderby_itemContext ctx) {
return builder;
}
+ @Override
+ public QueryTokenStream visitOrderby_expression(EqlParser.Orderby_expressionContext ctx) {
+
+ if (ctx.state_field_path_expression() != null) {
+ return visit(ctx.state_field_path_expression());
+ } else if (ctx.general_identification_variable() != null) {
+ return visit(ctx.general_identification_variable());
+ } else if (ctx.string_expression() != null) {
+ return visit(ctx.string_expression());
+ } else if (ctx.scalar_expression() != null) {
+ return visit(ctx.scalar_expression());
+ }
+
+ return QueryTokenStream.empty();
+ }
+
@Override
public QueryTokenStream visitNullsPrecedence(EqlParser.NullsPrecedenceContext ctx) {
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java
index 30e9106d22..15cb6b5767 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java
@@ -19,9 +19,9 @@
import java.util.List;
-import org.springframework.data.domain.Sort;
-
import org.jspecify.annotations.Nullable;
+
+import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.Assert;
@@ -54,7 +54,7 @@ class EqlSortedQueryTransformer extends EqlQueryRenderer {
}
@Override
- public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext ctx) {
+ public QueryTokenStream visitSelectQuery(EqlParser.SelectQueryContext ctx) {
QueryRendererBuilder builder = QueryRenderer.builder();
@@ -76,7 +76,35 @@ public QueryTokenStream visitSelect_statement(EqlParser.Select_statementContext
if (ctx.set_fuction() != null) {
builder.appendExpression(visit(ctx.set_fuction()));
} else {
- doVisitOrderBy(builder, ctx);
+ doVisitOrderBy(builder, ctx.orderby_clause());
+ }
+
+ return builder;
+ }
+
+ @Override
+ public QueryTokenStream visitFromQuery(EqlParser.FromQueryContext ctx) {
+
+ QueryRendererBuilder builder = QueryRenderer.builder();
+
+ builder.appendExpression(visit(ctx.from_clause()));
+
+ if (ctx.where_clause() != null) {
+ builder.appendExpression(visit(ctx.where_clause()));
+ }
+
+ if (ctx.groupby_clause() != null) {
+ builder.appendExpression(visit(ctx.groupby_clause()));
+ }
+
+ if (ctx.having_clause() != null) {
+ builder.appendExpression(visit(ctx.having_clause()));
+ }
+
+ if (ctx.set_fuction() != null) {
+ builder.appendExpression(visit(ctx.set_fuction()));
+ } else {
+ doVisitOrderBy(builder, ctx.orderby_clause());
}
return builder;
@@ -102,10 +130,10 @@ public QueryTokenStream visitSelect_clause(EqlParser.Select_clauseContext ctx) {
return builder.append(dtoDelegate.transformSelectionList(tokenStream));
}
- private void doVisitOrderBy(QueryRendererBuilder builder, EqlParser.Select_statementContext ctx) {
+ private void doVisitOrderBy(QueryRendererBuilder builder, EqlParser.Orderby_clauseContext ctx) {
- if (ctx.orderby_clause() != null) {
- QueryTokenStream existingOrder = visit(ctx.orderby_clause());
+ if (ctx != null) {
+ QueryTokenStream existingOrder = visit(ctx);
if (sort.isSorted()) {
builder.appendInline(existingOrder);
} else {
@@ -117,7 +145,7 @@ private void doVisitOrderBy(QueryRendererBuilder builder, EqlParser.Select_state
List sortBy = transformerSupport.orderBy(primaryFromAlias, sort);
- if (ctx.orderby_clause() != null) {
+ if (ctx != null) {
QueryRendererBuilder extension = QueryRenderer.builder().append(TOKEN_COMMA).append(sortBy);
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java
index e35b712589..6f9c3bb878 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java
@@ -22,6 +22,7 @@
import org.springframework.data.jpa.repository.query.HqlParser.SelectClauseContext;
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
import org.springframework.data.jpa.repository.query.QueryTransformers.CountSelectionTokenStream;
+import org.springframework.util.StringUtils;
/**
* An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed HQL query into a
@@ -107,7 +108,7 @@ public QueryRendererBuilder visitFromQuery(HqlParser.FromQueryContext ctx) {
if (ctx.fromClause() != null) {
builder.appendExpression(visit(ctx.fromClause()));
- if(primaryFromAlias == null) {
+ if (primaryFromAlias == null) {
builder.append(TOKEN_AS);
builder.append(TOKEN_DOUBLE_UNDERSCORE);
}
@@ -150,7 +151,6 @@ public QueryRendererBuilder visitJoin(HqlParser.JoinContext ctx) {
return builder;
}
-
@Override
public QueryTokenStream visitSelectClause(HqlParser.SelectClauseContext ctx) {
@@ -165,11 +165,9 @@ public QueryTokenStream visitSelectClause(HqlParser.SelectClauseContext ctx) {
boolean usesDistinct = ctx.DISTINCT() != null;
QueryRendererBuilder nested = QueryRenderer.builder();
if (countProjection == null) {
- QueryTokenStream selection = visit(ctx.selectionList());
if (usesDistinct) {
-
nested.append(QueryTokens.expression(ctx.DISTINCT()));
- nested.append(getDistinctCountSelection(selection));
+ nested.append(getDistinctCountSelection(visit(ctx.selectionList())));
} else {
// with CTE primary alias fails with hibernate (WITH entities AS (…) SELECT count(c) FROM entities c)
@@ -177,9 +175,7 @@ public QueryTokenStream visitSelectClause(HqlParser.SelectClauseContext ctx) {
nested.append(QueryTokens.token("*"));
} else {
- if (selection.size() == 1) {
- nested.append(selection);
- } else if (primaryFromAlias != null) {
+ if (StringUtils.hasText(primaryFromAlias)) {
nested.append(QueryTokens.token(primaryFromAlias));
} else {
nested.append(QueryTokens.token("*"));
@@ -187,10 +183,10 @@ public QueryTokenStream visitSelectClause(HqlParser.SelectClauseContext ctx) {
}
}
} else {
- builder.append(QueryTokens.token(countProjection));
if (usesDistinct) {
nested.append(QueryTokens.expression(ctx.DISTINCT()));
}
+ nested.append(QueryTokens.token(countProjection));
}
builder.appendInline(nested);
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java
index ba88ab2df1..c32bf27d3f 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java
@@ -69,7 +69,8 @@ public Void visitCte(HqlParser.CteContext ctx) {
@Override
public Void visitRootEntity(HqlParser.RootEntityContext ctx) {
- if (this.primaryFromAlias == null && ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)) {
+ if (this.primaryFromAlias == null && ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)
+ && !HqlQueryRenderer.isSetQuery(ctx)) {
this.primaryFromAlias = capturePrimaryAlias(ctx.variable());
}
@@ -79,7 +80,8 @@ public Void visitRootEntity(HqlParser.RootEntityContext ctx) {
@Override
public Void visitRootSubquery(HqlParser.RootSubqueryContext ctx) {
- if (this.primaryFromAlias == null && ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)) {
+ if (this.primaryFromAlias == null && ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)
+ && !HqlQueryRenderer.isSetQuery(ctx)) {
this.primaryFromAlias = capturePrimaryAlias(ctx.variable());
}
@@ -89,7 +91,8 @@ public Void visitRootSubquery(HqlParser.RootSubqueryContext ctx) {
@Override
public Void visitRootFunction(HqlParser.RootFunctionContext ctx) {
- if (this.primaryFromAlias == null && ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)) {
+ if (this.primaryFromAlias == null && ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)
+ && !HqlQueryRenderer.isSetQuery(ctx)) {
this.primaryFromAlias = capturePrimaryAlias(ctx.variable());
this.hasFromFunction = true;
}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java
index 01557b33d5..3e6f0175e1 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryRenderer.java
@@ -38,7 +38,7 @@
class HqlQueryRenderer extends HqlBaseVisitor {
/**
- * Is this select clause a {@literal subquery}?
+ * Is this AST tree a {@literal subquery}?
*
* @return boolean
*/
@@ -59,6 +59,23 @@ static boolean isSubquery(ParserRuleContext ctx) {
}
}
+ /**
+ * Is this AST tree a {@literal set} query that has been added through {@literal UNION|INTERSECT|EXCEPT}?
+ *
+ * @return boolean
+ */
+ static boolean isSetQuery(ParserRuleContext ctx) {
+
+ if (ctx instanceof HqlParser.OrderedQueryContext
+ && ctx.getParent() instanceof HqlParser.QueryExpressionContext qec) {
+ if (qec.orderedQuery().indexOf(ctx) != 0) {
+ return true;
+ }
+ }
+
+ return ctx.getParent() != null && isSetQuery(ctx.getParent());
+ }
+
@Override
public QueryTokenStream visitStart(HqlParser.StartContext ctx) {
return visit(ctx.ql_statement());
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 6318d8acfd..af7686fac2 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
@@ -44,7 +44,7 @@ class JpqlCountQueryTransformer extends JpqlQueryRenderer {
}
@Override
- public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) {
+ public QueryTokenStream visitSelectQuery(JpqlParser.SelectQueryContext ctx) {
QueryRendererBuilder builder = QueryRenderer.builder();
@@ -60,8 +60,48 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext
if (ctx.having_clause() != null) {
builder.appendExpression(visit(ctx.having_clause()));
}
- if (ctx.set_fuction() != null) {
- builder.appendExpression(visit(ctx.set_fuction()));
+
+ return builder;
+ }
+
+ @Override
+ public QueryTokenStream visitFromQuery(JpqlParser.FromQueryContext ctx) {
+
+ QueryRendererBuilder builder = QueryRenderer.builder();
+
+ QueryRendererBuilder countBuilder = QueryRenderer.builder();
+ countBuilder.append(TOKEN_SELECT_COUNT);
+
+ if (countProjection != null) {
+ countBuilder.append(QueryTokens.token(countProjection));
+ } else {
+ if (primaryFromAlias == null) {
+ countBuilder.append(TOKEN_DOUBLE_UNDERSCORE);
+ } else {
+ countBuilder.append(QueryTokens.token(primaryFromAlias));
+ }
+ }
+
+ countBuilder.append(TOKEN_CLOSE_PAREN);
+
+ builder.appendExpression(countBuilder);
+
+ if (ctx.from_clause() != null) {
+ builder.appendExpression(visit(ctx.from_clause()));
+ if (primaryFromAlias == null) {
+ builder.append(TOKEN_AS);
+ builder.append(TOKEN_DOUBLE_UNDERSCORE);
+ }
+ }
+
+ if (ctx.where_clause() != null) {
+ builder.appendExpression(visit(ctx.where_clause()));
+ }
+ if (ctx.groupby_clause() != null) {
+ builder.appendExpression(visit(ctx.groupby_clause()));
+ }
+ if (ctx.having_clause() != null) {
+ builder.appendExpression(visit(ctx.having_clause()));
}
return builder;
@@ -85,13 +125,18 @@ public QueryRendererBuilder visitSelect_clause(JpqlParser.Select_clauseContext c
} else if (StringUtils.hasText(primaryFromAlias)) {
nested.append(QueryTokens.token(primaryFromAlias));
} else {
- throw new IllegalStateException("No primary alias present");
+ if (ctx.select_item().isEmpty()) {
+ // cannot happen as per grammar, but you never know…
+ nested.append(QueryTokens.token("1"));
+ } else {
+ nested.append(visit(ctx.select_item().get(0)));
+ }
}
} else {
- builder.append(QueryTokens.token(countProjection));
if (usesDistinct) {
nested.append(QueryTokens.expression(ctx.DISTINCT()));
}
+ nested.append(QueryTokens.token(countProjection));
}
builder.appendInline(nested);
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java
index 43f6f7fd1f..f819778ce6 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java
@@ -46,39 +46,34 @@ public QueryInformation getParsedQueryInformation() {
}
@Override
- public Void visitRange_variable_declaration(JpqlParser.Range_variable_declarationContext ctx) {
+ public Void visitSelect_clause(JpqlParser.Select_clauseContext ctx) {
- if (primaryFromAlias == null) {
- primaryFromAlias = capturePrimaryAlias(ctx);
+ if (!projectionProcessed) {
+ projection = captureSelectItems(ctx.select_item(), renderer);
+ projectionProcessed = true;
}
- return super.visitRange_variable_declaration(ctx);
+ return super.visitSelect_clause(ctx);
}
@Override
- public Void visitSelect_clause(JpqlParser.Select_clauseContext ctx) {
+ public Void visitRange_variable_declaration(JpqlParser.Range_variable_declarationContext ctx) {
- if (!projectionProcessed) {
- projection = captureSelectItems(ctx.select_item(), renderer);
- projectionProcessed = true;
+ if (primaryFromAlias == null && ctx.identification_variable() != null && !JpqlQueryRenderer.isSubquery(ctx)
+ && !JpqlQueryRenderer.isSetQuery(ctx)) {
+ primaryFromAlias = ctx.identification_variable().getText();
}
- return super.visitSelect_clause(ctx);
+ return super.visitRange_variable_declaration(ctx);
}
@Override
public Void visitConstructor_expression(JpqlParser.Constructor_expressionContext ctx) {
hasConstructorExpression = true;
-
return super.visitConstructor_expression(ctx);
}
- private static String capturePrimaryAlias(JpqlParser.Range_variable_declarationContext ctx) {
- return ctx.identification_variable() != null ? ctx.identification_variable().getText()
- : ctx.entity_name().getText();
- }
-
private static List captureSelectItems(List selections,
JpqlQueryRenderer itemRenderer) {
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 03b87cdd34..75677568ca 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
@@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.List;
+import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;
import org.springframework.data.jpa.repository.query.JpqlParser.NullsPrecedenceContext;
@@ -35,11 +36,44 @@
*
* @author Greg Turnquist
* @author Christoph Strobl
+ * @author Mark Paluch
* @since 3.1
*/
@SuppressWarnings({ "ConstantConditions", "DuplicatedCode" })
class JpqlQueryRenderer extends JpqlBaseVisitor {
+ /**
+ * Is this AST tree a {@literal subquery}?
+ *
+ * @return boolean
+ */
+ static boolean isSubquery(ParserRuleContext ctx) {
+
+ if (ctx instanceof JpqlParser.SubqueryContext) {
+ return true;
+ } else if (ctx instanceof JpqlParser.Update_statementContext) {
+ return false;
+ } else if (ctx instanceof JpqlParser.Delete_statementContext) {
+ return false;
+ } else {
+ return ctx.getParent() != null && isSubquery(ctx.getParent());
+ }
+ }
+
+ /**
+ * Is this AST tree a {@literal set} query that has been added through {@literal UNION|INTERSECT|EXCEPT}?
+ *
+ * @return boolean
+ */
+ static boolean isSetQuery(ParserRuleContext ctx) {
+
+ if (ctx instanceof JpqlParser.Set_fuctionContext) {
+ return true;
+ }
+
+ return ctx.getParent() != null && isSetQuery(ctx.getParent());
+ }
+
@Override
public QueryTokenStream visitStart(JpqlParser.StartContext ctx) {
return visit(ctx.ql_statement());
@@ -60,7 +94,7 @@ public QueryTokenStream visitQl_statement(JpqlParser.Ql_statementContext ctx) {
}
@Override
- public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) {
+ public QueryTokenStream visitSelectQuery(JpqlParser.SelectQueryContext ctx) {
QueryRendererBuilder builder = QueryRenderer.builder();
@@ -90,6 +124,36 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext
return builder;
}
+ @Override
+ public QueryTokenStream visitFromQuery(JpqlParser.FromQueryContext ctx) {
+
+ QueryRendererBuilder builder = QueryRenderer.builder();
+
+ builder.appendExpression(visit(ctx.from_clause()));
+
+ if (ctx.where_clause() != null) {
+ builder.appendExpression(visit(ctx.where_clause()));
+ }
+
+ if (ctx.groupby_clause() != null) {
+ builder.appendExpression(visit(ctx.groupby_clause()));
+ }
+
+ if (ctx.having_clause() != null) {
+ builder.appendExpression(visit(ctx.having_clause()));
+ }
+
+ if (ctx.orderby_clause() != null) {
+ builder.appendExpression(visit(ctx.orderby_clause()));
+ }
+
+ if (ctx.set_fuction() != null) {
+ builder.appendExpression(visit(ctx.set_fuction()));
+ }
+
+ return builder;
+ }
+
@Override
public QueryTokenStream visitUpdate_statement(JpqlParser.Update_statementContext ctx) {
@@ -173,7 +237,9 @@ public QueryTokenStream visitRange_variable_declaration(JpqlParser.Range_variabl
builder.append(QueryTokens.expression(ctx.AS()));
}
- builder.appendExpression(visit(ctx.identification_variable()));
+ if (ctx.identification_variable() != null) {
+ builder.appendExpression(visit(ctx.identification_variable()));
+ }
return builder;
}
@@ -297,9 +363,12 @@ public QueryTokenStream visitJoin_association_path_expression(
public QueryTokenStream visitJoin_collection_valued_path_expression(
JpqlParser.Join_collection_valued_path_expressionContext ctx) {
- List items = new ArrayList<>(3 + ctx.single_valued_embeddable_object_field().size());
+ List items = new ArrayList<>(2 + ctx.single_valued_embeddable_object_field().size());
+
+ if (ctx.identification_variable() != null) {
+ items.add(ctx.identification_variable());
+ }
- items.add(ctx.identification_variable());
items.addAll(ctx.single_valued_embeddable_object_field());
items.add(ctx.collection_valued_field());
@@ -333,7 +402,9 @@ public QueryTokenStream visitCollection_member_declaration(JpqlParser.Collection
builder.append(QueryTokens.expression(ctx.AS()));
}
- builder.appendExpression(visit(ctx.identification_variable()));
+ if (ctx.identification_variable() != null) {
+ builder.appendExpression(visit(ctx.identification_variable()));
+ }
return builder;
}
@@ -581,6 +652,7 @@ public QueryTokenStream visitDelete_clause(JpqlParser.Delete_clauseContext ctx)
builder.append(QueryTokens.expression(ctx.DELETE()));
builder.append(QueryTokens.expression(ctx.FROM()));
builder.appendExpression(visit(ctx.entity_name()));
+
if (ctx.AS() != null) {
builder.append(QueryTokens.expression(ctx.AS()));
}
@@ -794,7 +866,7 @@ public QueryTokenStream visitOrderby_clause(JpqlParser.Orderby_clauseContext ctx
builder.append(QueryTokens.expression(ctx.ORDER()));
builder.append(QueryTokens.expression(ctx.BY()));
- builder.appendExpression(QueryTokenStream.concat(ctx.orderby_item(), this::visit, TOKEN_COMMA));
+ builder.append(QueryTokenStream.concat(ctx.orderby_item(), this::visit, TOKEN_COMMA));
return builder;
}
@@ -804,13 +876,7 @@ public QueryTokenStream visitOrderby_item(JpqlParser.Orderby_itemContext ctx) {
QueryRendererBuilder builder = QueryRenderer.builder();
- if (ctx.state_field_path_expression() != null) {
- builder.appendExpression(visit(ctx.state_field_path_expression()));
- } else if (ctx.general_identification_variable() != null) {
- builder.appendExpression(visit(ctx.general_identification_variable()));
- } else if (ctx.result_variable() != null) {
- builder.appendExpression(visit(ctx.result_variable()));
- }
+ builder.appendExpression(visit(ctx.orderby_expression()));
if (ctx.ASC() != null) {
builder.append(QueryTokens.expression(ctx.ASC()));
@@ -819,12 +885,28 @@ public QueryTokenStream visitOrderby_item(JpqlParser.Orderby_itemContext ctx) {
}
if (ctx.nullsPrecedence() != null) {
- builder.append(visit(ctx.nullsPrecedence()));
+ builder.appendExpression(visit(ctx.nullsPrecedence()));
}
return builder;
}
+ @Override
+ public QueryTokenStream visitOrderby_expression(JpqlParser.Orderby_expressionContext ctx) {
+
+ if (ctx.state_field_path_expression() != null) {
+ return visit(ctx.state_field_path_expression());
+ } else if (ctx.general_identification_variable() != null) {
+ return visit(ctx.general_identification_variable());
+ } else if (ctx.string_expression() != null) {
+ return visit(ctx.string_expression());
+ } else if (ctx.scalar_expression() != null) {
+ return visit(ctx.scalar_expression());
+ }
+
+ return QueryTokenStream.empty();
+ }
+
@Override
public QueryTokenStream visitNullsPrecedence(NullsPrecedenceContext ctx) {
@@ -1974,9 +2056,11 @@ public QueryTokenStream visitType_cast_function(JpqlParser.Type_cast_functionCon
builder.append(QueryTokens.token(ctx.CAST()));
builder.append(TOKEN_OPEN_PAREN);
builder.appendExpression(visit(ctx.scalar_expression()));
+
if (ctx.AS() != null) {
builder.append(QueryTokens.expression(ctx.AS()));
}
+
builder.appendInline(visit(ctx.identification_variable()));
if (!CollectionUtils.isEmpty(ctx.numeric_literal())) {
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 654fb7df88..70efd1af1b 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
@@ -19,9 +19,9 @@
import java.util.List;
-import org.springframework.data.domain.Sort;
-
import org.jspecify.annotations.Nullable;
+
+import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.Assert;
@@ -53,7 +53,7 @@ class JpqlSortedQueryTransformer extends JpqlQueryRenderer {
}
@Override
- public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext ctx) {
+ public QueryTokenStream visitSelectQuery(JpqlParser.SelectQueryContext ctx) {
QueryRendererBuilder builder = QueryRenderer.builder();
@@ -72,10 +72,38 @@ public QueryTokenStream visitSelect_statement(JpqlParser.Select_statementContext
builder.appendExpression(visit(ctx.having_clause()));
}
- if(ctx.set_fuction() != null) {
+ if (ctx.set_fuction() != null) {
+ builder.appendExpression(visit(ctx.set_fuction()));
+ } else {
+ doVisitOrderBy(builder, ctx.orderby_clause());
+ }
+
+ return builder;
+ }
+
+ @Override
+ public QueryTokenStream visitFromQuery(JpqlParser.FromQueryContext ctx) {
+
+ QueryRendererBuilder builder = QueryRenderer.builder();
+
+ builder.appendExpression(visit(ctx.from_clause()));
+
+ if (ctx.where_clause() != null) {
+ builder.appendExpression(visit(ctx.where_clause()));
+ }
+
+ if (ctx.groupby_clause() != null) {
+ builder.appendExpression(visit(ctx.groupby_clause()));
+ }
+
+ if (ctx.having_clause() != null) {
+ builder.appendExpression(visit(ctx.having_clause()));
+ }
+
+ if (ctx.set_fuction() != null) {
builder.appendExpression(visit(ctx.set_fuction()));
} else {
- doVisitOrderBy(builder, ctx);
+ doVisitOrderBy(builder, ctx.orderby_clause());
}
return builder;
@@ -101,10 +129,10 @@ public QueryTokenStream visitSelect_clause(JpqlParser.Select_clauseContext ctx)
return builder.append(dtoDelegate.transformSelectionList(tokenStream));
}
- private void doVisitOrderBy(QueryRendererBuilder builder, JpqlParser.Select_statementContext ctx) {
+ private void doVisitOrderBy(QueryRendererBuilder builder, JpqlParser.Orderby_clauseContext ctx) {
- if (ctx.orderby_clause() != null) {
- QueryTokenStream existingOrder = visit(ctx.orderby_clause());
+ if (ctx != null) {
+ QueryTokenStream existingOrder = visit(ctx);
if (sort.isSorted()) {
builder.appendInline(existingOrder);
} else {
@@ -116,7 +144,7 @@ private void doVisitOrderBy(QueryRendererBuilder builder, JpqlParser.Select_stat
List sortBy = transformerSupport.orderBy(primaryFromAlias, sort);
- if (ctx.orderby_clause() != null) {
+ if (ctx != null) {
QueryRendererBuilder extension = QueryRenderer.builder().append(TOKEN_COMMA).append(sortBy);
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 17188c06fb..c6c71a528b 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
@@ -69,6 +69,266 @@ private String reduceWhitespace(String original) {
.trim();
}
+ @Test
+ void selectQueries() {
+
+ assertQuery("Select e FROM Employee e WHERE e.salary > 100000");
+ assertQuery("Select e FROM Employee e WHERE e.id = :id");
+ assertQuery("Select MAX(e.salary) FROM Employee e");
+ assertQuery("Select e.firstName FROM Employee e");
+ assertQuery("Select e.firstName, e.lastName FROM Employee e");
+ }
+
+ @Test
+ void selectClause() {
+
+ assertQuery("SELECT COUNT(e) FROM Employee e");
+ assertQuery("SELECT MAX(e.salary) FROM Employee e");
+ assertQuery("SELECT NEW com.acme.reports.EmpReport(e.firstName, e.lastName, e.salary) FROM Employee e");
+ }
+
+ @Test
+ void fromClause() {
+
+ assertQuery("SELECT e FROM Employee e");
+ assertQuery("SELECT e, a FROM Employee e, MailingAddress a WHERE e.address = a.address");
+ assertQuery("SELECT e FROM com.acme.Employee e");
+ }
+
+ @Test
+ void join() {
+
+ assertQuery("SELECT e FROM Employee e JOIN e.address a WHERE a.city = :city");
+ assertQuery("SELECT e FROM Employee e JOIN e.projects p JOIN e.projects p2 WHERE p.name = :p1 AND p2.name = :p2");
+ }
+
+ @Test
+ void joinFetch() {
+
+ assertQuery("SELECT e FROM Employee e JOIN FETCH e.address");
+ assertQuery("SELECT e FROM Employee e JOIN FETCH e.address a ORDER BY a.city");
+ assertQuery("SELECT e FROM Employee e JOIN FETCH e.address AS a ORDER BY a.city");
+ }
+
+ @Test
+ void leftJoin() {
+ assertQuery("SELECT e FROM Employee e LEFT JOIN e.address a ORDER BY a.city");
+ }
+
+ @Test // GH-3277
+ void numericLiterals() {
+
+ assertQuery("SELECT e FROM Employee e WHERE e.id = 1234");
+ assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L");
+ assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14");
+ assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F");
+ assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D");
+ }
+
+ @Test // GH-3308
+ void newWithStrings() {
+ assertQuery("select new com.example.demo.SampleObject(se.id, se.sampleValue, \"java\") from SampleEntity se");
+ }
+
+ @Test
+ void orderByClause() {
+
+ assertQuery("SELECT e FROM Employee e ORDER BY e.lastName ASC, e.firstName ASC"); // Typo in EQL document
+ assertQuery("SELECT e FROM Employee e LEFT JOIN e.manager m ORDER BY m.lastName NULLS FIRST");
+ assertQuery("SELECT e FROM Employee e ORDER BY e.address");
+ }
+
+ @Test
+ void groupByClause() {
+
+ assertQuery("SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city");
+ assertQuery("SELECT e, COUNT(p) FROM Employee e LEFT JOIN e.projects p GROUP BY e");
+ }
+
+ @Test
+ void havingClause() {
+ assertQuery(
+ "SELECT AVG(e.salary), e.address.city FROM Employee e GROUP BY e.address.city HAVING AVG(e.salary) > 100000");
+ }
+
+ @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
+ """);
+ 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
+ """);
+ assertQuery("""
+ SELECT e FROM Employee e
+ EXCEPT SELECT e FROM Employee e WHERE e.salary > e.manager.salary
+ """);
+ }
+
+ @Test
+ void whereClause() {
+ // TBD
+ }
+
+ @Test
+ void updateQueries() {
+ assertQuery("UPDATE Employee e SET e.salary = 60000 WHERE e.salary = 50000");
+ }
+
+ @Test
+ void deleteQueries() {
+ assertQuery("DELETE FROM Employee e WHERE e.department IS NULL");
+ }
+
+ @Test
+ void literals() {
+
+ assertQuery("SELECT e FROM Employee e WHERE e.name = 'Bob'");
+ assertQuery("SELECT e FROM Employee e WHERE e.id = 1234");
+ assertQuery("SELECT e FROM Employee e WHERE e.id = 1234L");
+ assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14F");
+ assertQuery("SELECT s FROM Stat s WHERE s.ratio > 3.14e32D");
+ assertQuery("SELECT e FROM Employee e WHERE e.active = TRUE");
+ assertQuery("SELECT e FROM Employee e WHERE e.startDate = {d'2012-01-03'}");
+ assertQuery("SELECT e FROM Employee e WHERE e.startTime = {t'09:00:00'}");
+ assertQuery("SELECT e FROM Employee e WHERE e.version = {ts'2012-01-03 09:00:00.000000001'}");
+ assertQuery("SELECT e FROM Employee e WHERE e.gender = org.acme.Gender.MALE");
+ assertQuery("UPDATE Employee e SET e.manager = NULL WHERE e.manager = :manager");
+ }
+
+ @Test
+ void functionsInSelect() {
+
+ assertQuery("SELECT e.salary - 1000 FROM Employee e");
+ assertQuery("SELECT e.salary + 1000 FROM Employee e");
+ assertQuery("SELECT e.salary * 2 FROM Employee e");
+ assertQuery("SELECT e.salary * 2.0 FROM Employee e");
+ assertQuery("SELECT e.salary / 2 FROM Employee e");
+ assertQuery("SELECT e.salary / 2.0 FROM Employee e");
+ assertQuery("SELECT ABS(e.salary - e.manager.salary) FROM Employee e");
+ assertQuery(
+ "select e from Employee e where case e.firstName when 'Bob' then 'Robert' when 'Jill' then 'Gillian' else '' end = 'Robert'");
+ assertQuery(
+ "select case when e.firstName = 'Bob' then 'Robert' when e.firstName = 'Jill' then 'Gillian' else '' end from Employee e where e.firstName = 'Bob' or e.firstName = 'Jill'");
+ assertQuery(
+ "select e from Employee e where case when e.firstName = 'Bob' then 'Robert' when e.firstName = 'Jill' then 'Gillian' else '' end = 'Robert'");
+ assertQuery("SELECT COALESCE(e.salary, 0) FROM Employee e");
+ assertQuery("SELECT CONCAT(e.firstName, ' ', e.lastName) FROM Employee e");
+ assertQuery("SELECT e.name, CURRENT_DATE FROM Employee e");
+ assertQuery("SELECT e.name, CURRENT_TIME FROM Employee e");
+ assertQuery("SELECT e.name, CURRENT_TIMESTAMP FROM Employee e");
+ assertQuery("SELECT LENGTH(e.lastName) FROM Employee e");
+ assertQuery("SELECT LOWER(e.lastName) FROM Employee e");
+ assertQuery("SELECT MOD(e.hoursWorked, 8) FROM Employee e");
+ assertQuery("SELECT NULLIF(e.salary, 0) FROM Employee e");
+ assertQuery("SELECT SQRT(o.RESULT) FROM Output o");
+ assertQuery("SELECT SUBSTRING(e.lastName, 0, 2) FROM Employee e");
+ assertQuery(
+ "SELECT TRIM(TRAILING FROM e.lastName), TRIM(e.lastName), TRIM(LEADING '-' FROM e.lastName) FROM Employee e");
+ assertQuery("SELECT UPPER(e.lastName) FROM Employee e");
+ assertQuery("SELECT CAST(e.salary NUMERIC(10, 2)) FROM Employee e");
+ assertQuery("SELECT EXTRACT(YEAR FROM e.startDate) FROM Employee e");
+ }
+
+ @Test
+ void functionsInWhere() {
+
+ assertQuery("SELECT e FROM Employee e WHERE e.salary - 1000 > 0");
+ assertQuery("SELECT e FROM Employee e WHERE e.salary + 1000 > 0");
+ assertQuery("SELECT e FROM Employee e WHERE e.salary * 2 > 0");
+ assertQuery("SELECT e FROM Employee e WHERE e.salary * 2.0 > 0.0");
+ assertQuery("SELECT e FROM Employee e WHERE e.salary / 2 > 0");
+ assertQuery("SELECT e FROM Employee e WHERE e.salary / 2.0 > 0.0");
+ assertQuery("SELECT e FROM Employee e WHERE ABS(e.salary - e.manager.salary) > 0");
+ assertQuery("SELECT e FROM Employee e WHERE COALESCE(e.salary, 0) > 0");
+ assertQuery("SELECT e FROM Employee e WHERE CONCAT(e.firstName, ' ', e.lastName) = 'Bilbo'");
+ assertQuery("SELECT e FROM Employee e WHERE CURRENT_DATE > CURRENT_TIME");
+ assertQuery("SELECT e FROM Employee e WHERE CURRENT_TIME > CURRENT_TIMESTAMP");
+ assertQuery("SELECT e FROM Employee e WHERE LENGTH(e.lastName) > 0");
+ assertQuery("SELECT e FROM Employee e WHERE LOWER(e.lastName) = 'bilbo'");
+ assertQuery("SELECT e FROM Employee e WHERE MOD(e.hoursWorked, 8) > 0");
+ assertQuery("SELECT e FROM Employee e WHERE SQRT(o.RESULT) > 0.0");
+ assertQuery("SELECT e FROM Employee e WHERE SUBSTRING(e.lastName, 0, 2) = 'Bilbo'");
+ assertQuery("SELECT e FROM Employee e WHERE TRIM(TRAILING FROM e.lastName) = 'Bilbo'");
+ assertQuery("SELECT e FROM Employee e WHERE TRIM(e.lastName) = 'Bilbo'");
+ assertQuery("SELECT e FROM Employee e WHERE TRIM(LEADING '-' FROM e.lastName) = 'Bilbo'");
+ assertQuery("SELECT e FROM Employee e WHERE UPPER(e.lastName) = 'BILBO'");
+ assertQuery("SELECT e FROM Employee e WHERE CAST(e.salary NUMERIC(10, 2)) > 0.0");
+ assertQuery("SELECT e FROM Employee e WHERE EXTRACT(YEAR FROM e.startDate) = '2023'");
+ }
+
+ @Test
+ void specialOperators() {
+
+ assertQuery("SELECT toDo FROM Employee e JOIN e.toDoList toDo WHERE INDEX(toDo) = 1");
+ assertQuery("SELECT p FROM Employee e JOIN e.priorities p WHERE KEY(p) = 'high'");
+ assertQuery("SELECT e FROM Employee e WHERE SIZE(e.managedEmployees) < 2");
+ assertQuery("SELECT e FROM Employee e WHERE e.managedEmployees IS EMPTY");
+ assertQuery("SELECT e FROM Employee e WHERE 'write code' MEMBER OF e.responsibilities");
+ assertQuery("SELECT p FROM Project p WHERE TYPE(p) = LargeProject");
+
+ /**
+ * NOTE: The following query has been altered to properly align with EclipseLink test code despite NOT matching
+ * their ref docs. See https://github.com/eclipse-ee4j/eclipselink/issues/1949 for more details.
+ */
+ assertQuery("SELECT e FROM Employee e JOIN TREAT(e.projects AS LargeProject) p WHERE p.budget > 1000000");
+
+ assertQuery("SELECT p FROM Phone p WHERE FUNCTION('TO_NUMBER', p.areaCode) > 613");
+ }
+
+ @Test // GH-3314
+ void isNullAndIsNotNull() {
+
+ assertQuery("SELECT e FROM Employee e WHERE (e.active IS null OR e.active = true)");
+ assertQuery("SELECT e FROM Employee e WHERE (e.active IS NULL OR e.active = true)");
+ 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-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");
+ }
+
/**
* @see https://github.com/jakartaee/persistence/blob/master/spec/src/main/asciidoc/ch04-query-language.adoc#example
*/
@@ -1170,4 +1430,27 @@ void reservedWordsShouldWork() {
assertQuery("select f from FooEntity f where f.size IN :sizes");
}
+ @Test // GH-3902
+ void queryWithoutSelectShouldWork() {
+
+ assertQuery("from Person p");
+ assertQuery("from Person p WHERE p.name = 'John' ORDER BY p.name");
+ }
+
+ @Test // GH-3902
+ void queryWithoutSelectAndIdentificationVariableShouldWork() {
+
+ assertQuery("from Person");
+ assertQuery("from Person WHERE name = 'John' ORDER BY name");
+ assertQuery("from Person JOIN department WHERE name = 'John' ORDER BY name");
+ }
+
+ @Test // GH-3902
+ void queryWithoutIdentificationVariableShouldWork() {
+
+ assertQuery("SELECT name, lastname from Person");
+ assertQuery("SELECT name, lastname from Person WHERE lastname = 'Doe' ORDER BY name, lastname");
+ assertQuery("SELECT name, lastname from Person JOIN department");
+ }
+
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java
index 8f93859699..520039d70b 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java
@@ -37,6 +37,7 @@
* {@link JpaQueryEnhancer.EqlQueryParser}.
*
* @author Greg Turnquist
+ * @author Mark Paluch
*/
class EqlQueryTransformerTests {
@@ -82,13 +83,11 @@ void nullFirstLastSorting() {
assertThat(createQueryFor(original, Sort.unsorted())).isEqualTo(original);
- assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsLast())))
- .startsWith(original)
- .endsWithIgnoringCase("e.lastName DESC NULLS LAST");
+ assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsLast()))).startsWith(original)
+ .endsWithIgnoringCase("e.lastName DESC NULLS LAST");
- assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsFirst())))
- .startsWith(original)
- .endsWithIgnoringCase("e.lastName DESC NULLS FIRST");
+ assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsFirst()))).startsWith(original)
+ .endsWithIgnoringCase("e.lastName DESC NULLS FIRST");
}
@Test
@@ -104,6 +103,32 @@ void applyCountToSimpleQuery() {
assertThat(results).isEqualTo("SELECT count(e) FROM Employee e where e.name = :name");
}
+ @Test // GH-3902
+ void applyCountToFromQuery() {
+
+ // given
+ var original = "FROM Employee e where e.name = :name";
+
+ // when
+ var results = createCountQueryFor(original);
+
+ // then
+ assertThat(results).isEqualTo("select count(e) FROM Employee e where e.name = :name");
+ }
+
+ @Test // GH-3902
+ void applyCountToFromQueryWithoutIdentificationVariable() {
+
+ // given
+ var original = "FROM Employee where name = :name";
+
+ // when
+ var results = createCountQueryFor(original);
+
+ // then
+ assertThat(results).isEqualTo("select count(__) FROM Employee AS __ where name = :name");
+ }
+
@Test
void applyCountToMoreComplexQuery() {
@@ -117,6 +142,12 @@ void applyCountToMoreComplexQuery() {
assertThat(results).isEqualTo("SELECT count(e) FROM Employee e where e.name = :name");
}
+ @Test // GH-3902
+ void usesPrimaryAliasOfMultiselectForCountQuery() {
+ assertCountQuery("SELECT e.foo, e.bar FROM Employee e where e.name = :name ORDER BY e.modified_date",
+ "SELECT count(e) FROM Employee e where e.name = :name");
+ }
+
@Test
void applyCountToAlreadySortedQuery() {
@@ -143,8 +174,14 @@ void multipleAliasesShouldBeGathered() {
assertThat(results).isEqualTo("select e from Employee e join e.manager m");
}
- @Test
+ @Test // GH-3902
void createsCountQueryCorrectly() {
+
+ assertCountQuery("SELECT id FROM Person", "SELECT count(id) FROM Person");
+ assertCountQuery("SELECT p.id FROM Person p", "SELECT count(p) FROM Person p");
+ assertCountQuery("SELECT id FROM Person p", "SELECT count(p) FROM Person p");
+ assertCountQuery("SELECT id, name FROM Person", "SELECT count(id) FROM Person");
+ assertCountQuery("SELECT id, name FROM Person p", "SELECT count(p) FROM Person p");
assertCountQuery(QUERY, COUNT_QUERY);
}
@@ -183,6 +220,14 @@ void createsCountQueryForQueriesWithSubSelects() {
"select count(u) from User u left outer join u.roles r where r in (select r from Role r)");
}
+ @Test // GH-3902
+ void createsCountQueryForQueriesWithoutVariableWithSubSelectsSelectQuery() {
+
+ assertCountQuery(
+ "select name, (select foo from bar b) from User left outer join u.roles r where r in (select r from Role r)",
+ "select count(name) from User left outer join u.roles r where r in (select r from Role r)");
+ }
+
@Test
void createsCountQueryForAliasesCorrectly() {
assertCountQuery("select u from User as u", "select count(u) from User as u");
@@ -193,7 +238,7 @@ void allowsShortJpaSyntax() {
assertCountQuery(SIMPLE_QUERY, COUNT_QUERY);
}
- @Test // GH-2260
+ @Test // GH-2260, GH-3902
void detectsAliasCorrectly() {
assertThat(alias(QUERY)).isEqualTo("u");
@@ -210,6 +255,11 @@ void detectsAliasCorrectly() {
assertThat(alias(
"select u from User u where not exists (select u2 from User u2 where not exists (select u3 from User u3))"))
.isEqualTo("u");
+ assertThat(alias("select u, (select u2 from User u2) from User u")).isEqualTo("u");
+ assertThat(alias("select firstname from User where not exists (select u2 from User u2)")).isNull();
+ assertThat(alias("select firstname from User UNION select lastname from User b")).isNull();
+ assertThat(alias("select firstname from User UNION select lastname from User UNION select lastname from User b"))
+ .isNull();
}
@Test // GH-2557
@@ -226,12 +276,12 @@ where exists (select u2
""").rewrite(new DefaultQueryRewriteInformation(sort,
ReturnedType.of(Object.class, Object.class, new SpelAwareProxyProjectionFactory()))))
.isEqualToIgnoringWhitespace("""
- select u
- from user u
- where exists (select u2
- from user u2
- )
- order by u.age desc""");
+ select u
+ from user u
+ where exists (select u2
+ from user u2
+ )
+ order by u.age desc""");
}
@Test // GH-2563
@@ -643,20 +693,6 @@ void countProjectionDistinctQueryIncludesNewLineAfterEntityAndBeforeWhere() {
"SELECT count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN entity1.entity2 entity2 ON entity1.key = entity2.key where entity1.id = 1799");
}
- @Test // GH-3269
- void createsCountQueryUsingAliasCorrectly() {
-
- assertCountQuery("select distinct 1 as x from Employee e", "select count(distinct 1) from Employee e");
- assertCountQuery("SELECT DISTINCT abc AS x FROM T t", "SELECT count(DISTINCT abc) FROM T t");
- assertCountQuery("select distinct a as x, b as y from Employee e", "select count(distinct a, b) from Employee e");
- assertCountQuery("select distinct sum(amount) as x from Employee e GROUP BY n",
- "select count(distinct sum(amount)) from Employee e GROUP BY n");
- assertCountQuery("select distinct a, b, sum(amount) as c, d from Employee e GROUP BY n",
- "select count(distinct a, b, sum(amount), d) from Employee e GROUP BY n");
- assertCountQuery("select distinct a, count(b) as c from Employee e GROUP BY n",
- "select count(distinct a, count(b)) from Employee e GROUP BY n");
- }
-
@Test // GH-2393
void createCountQueryStartsWithWhitespace() {
@@ -698,6 +734,36 @@ void countQueryUsesCorrectVariable() {
.isEqualTo("SELECT count(us) FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)");
}
+ @Test // GH-3269
+ void createsCountQueryUsingAliasCorrectly() {
+
+ assertCountQuery("select distinct 1 as x from Employee e", "select count(distinct 1) from Employee e");
+ assertCountQuery("SELECT DISTINCT abc AS x FROM T t", "SELECT count(DISTINCT abc) FROM T t");
+ assertCountQuery("select distinct a as x, b as y from Employee e", "select count(distinct a, b) from Employee e");
+ assertCountQuery("select distinct sum(amount) as x from Employee e GROUP BY n",
+ "select count(distinct sum(amount)) from Employee e GROUP BY n");
+ assertCountQuery("select distinct a, b, sum(amount) as c, d from Employee e GROUP BY n",
+ "select count(distinct a, b, sum(amount), d) from Employee e GROUP BY n");
+ assertCountQuery("select distinct a, count(b) as c from Employee e GROUP BY n",
+ "select count(distinct a, count(b)) from Employee e GROUP BY n");
+ }
+
+ @Test // GH-3902
+ void createsCountQueryWithoutAlias() {
+
+ assertCountQuery(
+ "SELECT this.quantity FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'",
+ "SELECT count(this.quantity) FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'");
+ }
+
+ @Test // GH-3902
+ void createsCountQueryFromMultiselectWithoutAlias() {
+
+ assertCountQuery(
+ "SELECT this.quantity, that.quantity FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'",
+ "SELECT count(this.quantity) FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'");
+ }
+
@Test // GH-2496, GH-2522, GH-2537, GH-2045
void orderByShouldWorkWithSubSelectStatements() {
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 040c632dfb..0ed7f1ed75 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
@@ -2621,7 +2621,36 @@ void joinTwoFunctions() {
from some_function(:date, :integerValue) d
inner join some_function_single_param(:date) k on (d.idFunction = k.idFunctionSP)
""");
+ }
+
+ @Test // GH-3902
+ void queryWithoutSelectShouldWork() {
+
+ assertQuery("from Person p");
+ assertQuery("from Person p WHERE p.name = 'John' ORDER BY p.name");
+ }
+
+ @Test // GH-3902
+ void queryWithoutSelectAndIdentificationVariableShouldWork() {
+
+ assertQuery("from Person");
+ assertQuery("from Person WHERE name = 'John' ORDER BY name");
+ assertQuery("from Person JOIN department WHERE name = 'John' ORDER BY name");
+ assertQuery(
+ "from Person JOIN (select phone.number as n, phone.person as pp from Phone phone) WHERE name = 'John' ORDER BY name");
+ assertQuery("from Person JOIN (select number, person from Phone) WHERE name = 'John' ORDER BY name");
+ }
+ @Test // GH-3902
+ void queryWithoutIdentificationVariableShouldWork() {
+
+ assertQuery("SELECT name, lastname from Person");
+ assertQuery("SELECT name, lastname from Person WHERE lastname = 'Doe' ORDER BY name, lastname");
+ assertQuery("SELECT name, lastname from Person JOIN department");
+ assertQuery(
+ "SELECT name, lastname from Person JOIN (select phone.number as n, phone.person as pp from Phone phone) WHERE name = 'John' ORDER BY name");
+ assertQuery(
+ "SELECT name, lastname from Person JOIN (select number, person from Phone) WHERE name = 'John' ORDER BY name");
}
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java
index 260a788d64..d1c5adfa48 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java
@@ -109,6 +109,19 @@ void applyCountToSimpleQuery() {
assertThat(results).isEqualTo("select count(e) FROM Employee e where e.name = :name");
}
+ @Test // GH-3902
+ void applyCountToFromQueryWithoutIdentificationVariable() {
+
+ // given
+ var original = "FROM Employee where name = :name";
+
+ // when
+ var results = createCountQueryFor(original);
+
+ // then
+ assertThat(results).isEqualTo("select count(__) FROM Employee AS __ where name = :name");
+ }
+
@Test // GH-3536
void shouldCreateCountQueryForDistinctCount() {
@@ -139,6 +152,12 @@ void applyCountToMoreComplexQuery() {
assertThat(results).isEqualTo("SELECT count(e) FROM Employee e where e.name = :name");
}
+ @Test // GH-3902
+ void usesAsteriskAliasOfMultiselectForCountQuery() {
+ assertCountQuery("SELECT e.foo, e.bar FROM Employee e where e.name = :name ORDER BY e.modified_date",
+ "SELECT count(e) FROM Employee e where e.name = :name");
+ }
+
@Test
void applyCountToAlreadySortedQuery() {
@@ -186,9 +205,9 @@ void multipleAliasesShouldBeGathered() {
@Test
void createsCountQueryCorrectly() {
- assertCountQuery("SELECT id FROM Person", "SELECT count(id) FROM Person");
+ assertCountQuery("SELECT id FROM Person", "SELECT count(*) FROM Person");
assertCountQuery("SELECT p.id FROM Person p", "SELECT count(p) FROM Person p");
- assertCountQuery("SELECT id FROM Person p", "SELECT count(id) FROM Person p");
+ assertCountQuery("SELECT id FROM Person p", "SELECT count(p) FROM Person p");
assertCountQuery("SELECT id, name FROM Person", "SELECT count(*) FROM Person");
assertCountQuery("SELECT id, name FROM Person p", "SELECT count(p) FROM Person p");
assertCountQuery(QUERY, COUNT_QUERY);
@@ -232,7 +251,15 @@ void createsCountQueryForQueriesWithSubSelectsSelectQuery() {
"select count(u) from User u left outer join u.roles r where r in (select r from Role r)");
}
- @Test
+ @Test // GH-3902
+ void createsCountQueryForQueriesWithoutVariableWithSubSelectsSelectQuery() {
+
+ assertCountQuery(
+ "select u, (select foo from bar b) from User left outer join u.roles r where r in (select r from Role r)",
+ "select count(*) from User left outer join u.roles r where r in (select r from Role r)");
+ }
+
+ @Test // GH-3902
void createsCountQueryForQueriesWithSubSelects() {
assertCountQuery("from User u left outer join u.roles r where r in (select r from Role r) select u ",
@@ -249,7 +276,7 @@ void allowsShortJpaSyntax() {
assertCountQuery(SIMPLE_QUERY, COUNT_QUERY);
}
- @Test // GH-2260
+ @Test // GH-2260, GH-3902
void detectsAliasCorrectly() {
assertThat(alias(QUERY)).isEqualTo("u");
@@ -269,6 +296,11 @@ void detectsAliasCorrectly() {
assertThat(alias(
"SELECT e FROM DbEvent e WHERE TREAT(modifiedFrom AS date) IS NULL OR e.modificationDate >= :modifiedFrom"))
.isEqualTo("e");
+ assertThat(alias("select u, (select u2 from User u2) from User u")).isEqualTo("u");
+ assertThat(alias("select firstname from User JOIN (select u2 from User u2) u2")).isNull();
+ assertThat(alias("select firstname from User UNION select lastname from User b")).isNull();
+ assertThat(alias("select firstname from User UNION select lastname from User UNION select lastname from User b"))
+ .isNull();
}
@Test // GH-2557
@@ -285,12 +317,12 @@ where exists (select u2
""").rewrite(new DefaultQueryRewriteInformation(sort,
ReturnedType.of(Object.class, Object.class, new SpelAwareProxyProjectionFactory()))))
.isEqualToIgnoringWhitespace("""
- select u
- from user u
- where exists (select u2
- from user u2
- )
- order by u.age desc""");
+ select u
+ from user u
+ where exists (select u2
+ from user u2
+ )
+ order by u.age desc""");
}
@Test // GH-2563
@@ -834,6 +866,38 @@ void countQueryUsesCorrectVariable() {
.isEqualTo("SELECT count(us) FROM users_statuses us WHERE (user_created_at BETWEEN :fromDate AND :toDate)");
}
+ @Test // GH-3269, GH-3689
+ void createsCountQueryUsingAliasCorrectly() {
+
+ assertCountQuery("select distinct 1 as x from Employee", "select count(distinct 1) from Employee");
+ assertCountQuery("SELECT DISTINCT abc AS x FROM T", "SELECT count(DISTINCT abc) FROM T");
+ assertCountQuery("select distinct a as x, b as y from Employee", "select count(distinct a, b) from Employee");
+ assertCountQuery("select distinct sum(amount) as x from Employee GROUP BY n",
+ "select count(distinct sum(amount)) from Employee GROUP BY n");
+ assertCountQuery("select distinct a, b, sum(amount) as c, d from Employee GROUP BY n",
+ "select count(distinct a, b, sum(amount), d) from Employee GROUP BY n");
+ assertCountQuery("select distinct a, count(b) as c from Employee GROUP BY n",
+ "select count(distinct a, count(b)) from Employee GROUP BY n");
+ assertCountQuery("select distinct substring(e.firstname, 1, position('a' in e.lastname)) as x from from Employee",
+ "select count(distinct substring(e.firstname, 1, position('a' in e.lastname))) from from Employee");
+ }
+
+ @Test // GH-3902
+ void createsCountQueryWithoutAlias() {
+
+ assertCountQuery(
+ "SELECT this.quantity FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'",
+ "SELECT count(*) FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'");
+ }
+
+ @Test // GH-3902
+ void createsCountQueryFromMultiselectWithoutAlias() {
+
+ assertCountQuery(
+ "SELECT this.quantity, that.quantity FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'",
+ "SELECT count(*) FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'");
+ }
+
@Test // GH-2496, GH-2522, GH-2537, GH-2045
void orderByShouldWorkWithSubSelectStatements() {
@@ -1118,22 +1182,6 @@ void aliasesShouldNotOverlapWithSortProperties() {
"SELECT t3 FROM Test3 t3 JOIN t3.test2 x WHERE x.id = :test2Id order by t3.testDuplicateColumnName desc");
}
- @Test // GH-3269, GH-3689
- void createsCountQueryUsingAliasCorrectly() {
-
- assertCountQuery("select distinct 1 as x from Employee", "select count(distinct 1) from Employee");
- assertCountQuery("SELECT DISTINCT abc AS x FROM T", "SELECT count(DISTINCT abc) FROM T");
- assertCountQuery("select distinct a as x, b as y from Employee", "select count(distinct a, b) from Employee");
- assertCountQuery("select distinct sum(amount) as x from Employee GROUP BY n",
- "select count(distinct sum(amount)) from Employee GROUP BY n");
- assertCountQuery("select distinct a, b, sum(amount) as c, d from Employee GROUP BY n",
- "select count(distinct a, b, sum(amount), d) from Employee GROUP BY n");
- assertCountQuery("select distinct a, count(b) as c from Employee GROUP BY n",
- "select count(distinct a, count(b)) from Employee GROUP BY n");
- assertCountQuery("select distinct substring(e.firstname, 1, position('a' in e.lastname)) as x from from Employee",
- "select count(distinct substring(e.firstname, 1, position('a' in e.lastname))) from from Employee");
- }
-
@Test // GH-3864
void testCountFromFunctionWithAlias() {
@@ -1148,7 +1196,7 @@ void testCountFromFunctionWithAlias() {
}
@Test // GH-3864
- void testCountFromFunctionNoAlias() {
+ void testCountFromMultiselectFunctionNoAlias() {
// given
var original = "select id, value from some_function(:date, :integerValue)";
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java
index 3d9b1bf1b2..8fe266711e 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryRendererTests.java
@@ -44,7 +44,7 @@ class JpqlQueryRendererTests {
private static final String SPEC_FAULT = "Disabled due to spec fault> ";
/**
- * Parse the query using {@link HqlParser} then run it through the query-preserving {@link HqlQueryRenderer}.
+ * Parse the query using {@link JpqlParser} then run it through the query-preserving {@link JpqlQueryRenderer}.
*/
private static String parseWithoutChanges(String query) {
@@ -1279,23 +1279,31 @@ void typeShouldBeAValidParameter() {
assertQuery("select te from TestEntity te where te.type = :type");
}
- @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-3061
void alternateNotEqualsOperatorShouldWork() {
assertQuery("select e from Employee e where e.firstName != :name");
}
+ @Test
+ void regexShouldWork() {
+ assertQuery("select e from Employee e where e.lastName REGEXP '^Dr\\.*'");
+ }
+
@Test // GH-3092
void dateAndFromShouldBeValidNames() {
assertQuery("SELECT e FROM Entity e WHERE e.embeddedId.date BETWEEN :from AND :to");
}
+ @Test
+ void betweenStrings() {
+ assertQuery("SELECT e FROM Entity e WHERE e.embeddedId.date NOT BETWEEN 'a' AND 'b'");
+ }
+
+ @Test
+ void betweenDates() {
+ assertQuery("SELECT e FROM Entity e WHERE e.embeddedId.date BETWEEN CURRENT_DATE AND CURRENT_TIME");
+ }
+
@Test // GH-3092
void timeShouldBeAValidParameterName() {
assertQuery("""
@@ -1410,6 +1418,13 @@ void entityNameWithPackageContainingReservedWord(String 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");
+ }
+
@Test // GH-3834
void reservedWordsShouldWork() {
@@ -1417,6 +1432,32 @@ void reservedWordsShouldWork() {
assertQuery("select ie.object from ItemExample ie left join ie.object io where io.externalId = :externalId");
assertQuery("select ie from ItemExample ie left join ie.object io where io.object = :externalId");
assertQuery("select ie from ItemExample ie where ie.status = com.app.domain.object.Status.UP");
+ assertQuery("select f from FooEntity f where upper(f.name) IN :names");
+ assertQuery("select f from FooEntity f where f.size IN :sizes");
+ }
+
+ @Test // GH-3902
+ void queryWithoutSelectShouldWork() {
+
+ assertQuery("from Person p");
+ assertQuery("from Person p WHERE p.name = 'John' ORDER BY p.name");
+ }
+
+ @Test // GH-3902
+ void queryWithoutSelectAndIdentificationVariableShouldWork() {
+
+ assertQuery("from Person");
+ assertQuery("from Person WHERE name = 'John' ORDER BY name");
+ assertQuery("from Person JOIN department WHERE name = 'John' ORDER BY name");
+ }
+
+ @Test // GH-3902
+ void queryWithoutIdentificationVariableShouldWork() {
+
+ assertQuery("SELECT name, lastname from Person");
+ assertQuery("SELECT name, lastname from Person WHERE lastname = 'Doe' ORDER BY name, lastname");
+ assertQuery("SELECT name, lastname from Person WHERE lastname = 'Doe' ORDER BY name, lastname");
+ assertQuery("SELECT name, lastname from Person JOIN department");
}
}
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 39ed9b6d9d..69b8514ed3 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
@@ -83,13 +83,11 @@ void nullFirstLastSorting() {
assertThat(createQueryFor(original, Sort.unsorted())).isEqualTo(original);
- assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsLast())))
- .startsWith(original)
- .endsWithIgnoringCase("e.lastName DESC NULLS LAST");
+ assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsLast()))).startsWith(original)
+ .endsWithIgnoringCase("e.lastName DESC NULLS LAST");
- assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsFirst())))
- .startsWith(original)
- .endsWithIgnoringCase("e.lastName DESC NULLS FIRST");
+ assertThat(createQueryFor(original, Sort.by(Order.desc("lastName").nullsFirst()))).startsWith(original)
+ .endsWithIgnoringCase("e.lastName DESC NULLS FIRST");
}
@Test
@@ -105,6 +103,32 @@ void applyCountToSimpleQuery() {
assertThat(results).isEqualTo("SELECT count(e) FROM Employee e where e.name = :name");
}
+ @Test // GH-3902
+ void applyCountToFromQuery() {
+
+ // given
+ var original = "FROM Employee e where e.name = :name";
+
+ // when
+ var results = createCountQueryFor(original);
+
+ // then
+ assertThat(results).isEqualTo("select count(e) FROM Employee e where e.name = :name");
+ }
+
+ @Test // GH-3902
+ void applyCountToFromQueryWithoutIdentificationVariable() {
+
+ // given
+ var original = "FROM Employee where name = :name";
+
+ // when
+ var results = createCountQueryFor(original);
+
+ // then
+ assertThat(results).isEqualTo("select count(__) FROM Employee AS __ where name = :name");
+ }
+
@Test
void applyCountToMoreComplexQuery() {
@@ -118,6 +142,12 @@ void applyCountToMoreComplexQuery() {
assertThat(results).isEqualTo("SELECT count(e) FROM Employee e where e.name = :name");
}
+ @Test // GH-3902
+ void usesPrimaryAliasOfMultiselectForCountQuery() {
+ assertCountQuery("SELECT e.foo, e.bar FROM Employee e where e.name = :name ORDER BY e.modified_date",
+ "SELECT count(e) FROM Employee e where e.name = :name");
+ }
+
@Test
void applyCountToAlreadySortedQuery() {
@@ -144,8 +174,14 @@ void multipleAliasesShouldBeGathered() {
assertThat(results).isEqualTo("select e from Employee e join e.manager m");
}
- @Test
+ @Test // GH-3902
void createsCountQueryCorrectly() {
+
+ assertCountQuery("SELECT id FROM Person", "SELECT count(id) FROM Person");
+ assertCountQuery("SELECT p.id FROM Person p", "SELECT count(p) FROM Person p");
+ assertCountQuery("SELECT id FROM Person p", "SELECT count(p) FROM Person p");
+ assertCountQuery("SELECT id, name FROM Person", "SELECT count(id) FROM Person");
+ assertCountQuery("SELECT id, name FROM Person p", "SELECT count(p) FROM Person p");
assertCountQuery(QUERY, COUNT_QUERY);
}
@@ -184,6 +220,14 @@ void createsCountQueryForQueriesWithSubSelects() {
"select count(u) from User u left outer join u.roles r where r in (select r from Role r)");
}
+ @Test // GH-3902
+ void createsCountQueryForQueriesWithoutVariableWithSubSelectsSelectQuery() {
+
+ assertCountQuery(
+ "select name, (select foo from bar b) from User left outer join u.roles r where r in (select r from Role r)",
+ "select count(name) from User left outer join u.roles r where r in (select r from Role r)");
+ }
+
@Test
void createsCountQueryForAliasesCorrectly() {
assertCountQuery("select u from User as u", "select count(u) from User as u");
@@ -194,7 +238,7 @@ void allowsShortJpaSyntax() {
assertCountQuery(SIMPLE_QUERY, COUNT_QUERY);
}
- @Test // GH-2260
+ @Test // GH-2260, GH-3902
void detectsAliasCorrectly() {
assertThat(alias(QUERY)).isEqualTo("u");
@@ -211,6 +255,11 @@ void detectsAliasCorrectly() {
assertThat(alias(
"select u from User u where not exists (select u2 from User u2 where not exists (select u3 from User u3))"))
.isEqualTo("u");
+ assertThat(alias("select u, (select u2 from User u2) from User u")).isEqualTo("u");
+ assertThat(alias("select firstname from User where not exists (select u2 from User u2)")).isNull();
+ assertThat(alias("select firstname from User UNION select lastname from User b")).isNull();
+ assertThat(alias("select firstname from User UNION select lastname from User UNION select lastname from User b"))
+ .isNull();
}
@Test // GH-2557
@@ -218,7 +267,6 @@ void applySortingAccountsForNewlinesInSubselect() {
Sort sort = Sort.by(Sort.Order.desc("age"));
-
assertThat(newParser("""
select u
from user u
@@ -228,12 +276,12 @@ where exists (select u2
""").rewrite(new DefaultQueryRewriteInformation(sort,
ReturnedType.of(Object.class, Object.class, new SpelAwareProxyProjectionFactory()))))
.isEqualToIgnoringWhitespace("""
- select u
- from user u
- where exists (select u2
- from user u2
- )
- order by u.age desc""");
+ select u
+ from user u
+ where exists (select u2
+ from user u2
+ )
+ order by u.age desc""");
}
@Test // GH-2563
@@ -490,9 +538,6 @@ void detectsAliasWithGroupAndOrderBy() {
@Test // DATAJPA-1500
void createCountQuerySupportsWhitespaceCharacters() {
- //
- //
- //
assertThat(createCountQueryFor("""
select user from User user
where user.age = 18
@@ -568,10 +613,6 @@ void appliesSortCorrectlyForSimpleField() {
@Test
void createCountQuerySupportsLineBreakRightAfterDistinct() {
- //
- //
- //
- //
assertThat(createCountQueryFor("""
select
distinct
@@ -595,7 +636,6 @@ void detectsAliasWithGroupAndOrderByWithLineBreaks() {
.isThrownBy(() -> alias("select * from User group\nby name"));
assertThatExceptionOfType(BadJpqlGrammarException.class)
.isThrownBy(() -> alias("select * from User order\nby name"));
-
assertThat(alias("select u from User u group\nby name")).isEqualTo("u");
assertThat(alias("select u from User u order\nby name")).isEqualTo("u");
assertThat(alias("select u from User\nu\norder \n by name")).isEqualTo("u");
@@ -706,6 +746,22 @@ void createsCountQueryUsingAliasCorrectly() {
"select count(distinct a, count(b)) from Employee e GROUP BY n");
}
+ @Test // GH-3902
+ void createsCountQueryWithoutAlias() {
+
+ assertCountQuery(
+ "SELECT this.quantity FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'",
+ "SELECT count(this.quantity) FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'");
+ }
+
+ @Test // GH-3902
+ void createsCountQueryFromMultiselectWithoutAlias() {
+
+ assertCountQuery(
+ "SELECT this.quantity, that.quantity FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'",
+ "SELECT count(this.quantity) FROM Order WHERE this.customer.firstname = 'John' AND this.customer.lastname = 'Wick'");
+ }
+
@Test // GH-2496, GH-2522, GH-2537, GH-2045
void orderByShouldWorkWithSubSelectStatements() {
@@ -795,7 +851,8 @@ 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");
+ 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() {