Skip to content

Commit a328989

Browse files
committed
Polishing.
Update author tags. Refine introspection defaults instead of assuming SELECT statements by default. Extract code duplications from introspection to a mutable QueryInformationHolder. Retrofit AOT query derivation to skip count query creation for non-SELECT queries. Original pull request: #4052 See #2856
1 parent e1be294 commit a328989

File tree

14 files changed

+303
-264
lines changed

14 files changed

+303
-264
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotQueries.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818

1919
import java.util.LinkedHashMap;
20+
import java.util.List;
2021
import java.util.Map;
2122
import java.util.function.Function;
2223

@@ -36,6 +37,10 @@
3637
*/
3738
record AotQueries(AotQuery result, AotQuery count) {
3839

40+
public AotQueries(AotQuery query) {
41+
this(query, AbsentCountQuery.INSTANCE);
42+
}
43+
3944
/**
4045
* Derive a count query from the given query.
4146
*/
@@ -45,6 +50,10 @@ public static <T extends AotQuery> AotQueries withDerivedCountQuery(T query, Fun
4550
DeclaredQuery underlyingQuery = queryMapper.apply(query);
4651
QueryEnhancer queryEnhancer = selector.select(underlyingQuery).create(underlyingQuery);
4752

53+
if (!queryEnhancer.isSelectQuery()) {
54+
return new AotQueries(query);
55+
}
56+
4857
String derivedCountQuery = queryEnhancer
4958
.createCountQueryFor(StringUtils.hasText(countProjection) ? countProjection : null);
5059

@@ -66,6 +75,25 @@ public QueryMetadata toMetadata(boolean paging) {
6675
return new AotQueryMetadata(paging);
6776
}
6877

78+
private static class AbsentCountQuery extends AotQuery {
79+
80+
static final AbsentCountQuery INSTANCE = new AbsentCountQuery();
81+
82+
AbsentCountQuery() {
83+
super(List.of());
84+
}
85+
86+
@Override
87+
public boolean isNative() {
88+
return false;
89+
}
90+
91+
@Override
92+
public boolean hasConstructorExpressionOrDefaultProjection() {
93+
return false;
94+
}
95+
}
96+
6997
/**
7098
* String and Named Query-based {@link QueryMetadata}.
7199
*/

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/QueriesFactory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ public ReturnedType getReturnedType() {
180180
createNamedAotQuery(returnedType, selector, queryMethod.getNamedCountQueryName(), queryMethod, isNative));
181181
}
182182

183+
if (queryMethod.isModifyingQuery()) {
184+
185+
}
186+
183187
String countProjection = query.getString("countProjection");
184188
return AotQueries.withDerivedCountQuery(aotStringQuery, StringAotQuery::getQuery, countProjection, selector);
185189
}

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

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,79 +15,65 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import static org.springframework.data.jpa.repository.query.QueryTokens.*;
19-
20-
import java.util.ArrayList;
21-
import java.util.Collections;
22-
import java.util.List;
23-
24-
import org.jspecify.annotations.Nullable;
25-
2618
/**
2719
* {@link ParsedQueryIntrospector} for EQL queries.
2820
*
2921
* @author Mark Paluch
3022
* @author Christoph Strobl
31-
* @author kssumin
23+
* @author Soomin Kim
3224
*/
3325
@SuppressWarnings("UnreachableCode")
3426
class EqlQueryIntrospector extends EqlBaseVisitor<Void> implements ParsedQueryIntrospector<QueryInformation> {
3527

3628
private final EqlQueryRenderer renderer = new EqlQueryRenderer();
37-
38-
private @Nullable String primaryFromAlias = null;
39-
private @Nullable List<QueryToken> projection;
40-
private boolean projectionProcessed;
41-
private boolean hasConstructorExpression = false;
42-
private QueryInformation.StatementType statementType = QueryInformation.StatementType.SELECT;
29+
private final QueryInformationHolder introspection = new QueryInformationHolder();
4330

4431
@Override
4532
public QueryInformation getParsedQueryInformation() {
46-
return new QueryInformation(primaryFromAlias, projection == null ? Collections.emptyList() : projection,
47-
hasConstructorExpression, statementType);
33+
return new QueryInformation(introspection);
4834
}
4935

5036
@Override
5137
public Void visitSelectQuery(EqlParser.SelectQueryContext ctx) {
52-
statementType = QueryInformation.StatementType.SELECT;
38+
39+
introspection.setStatementType(QueryInformation.StatementType.SELECT);
5340
return super.visitSelectQuery(ctx);
5441
}
5542

5643
@Override
5744
public Void visitFromQuery(EqlParser.FromQueryContext ctx) {
58-
statementType = QueryInformation.StatementType.SELECT;
45+
46+
introspection.setStatementType(QueryInformation.StatementType.SELECT);
5947
return super.visitFromQuery(ctx);
6048
}
6149

6250
@Override
6351
public Void visitUpdate_statement(EqlParser.Update_statementContext ctx) {
64-
statementType = QueryInformation.StatementType.UPDATE;
52+
53+
introspection.setStatementType(QueryInformation.StatementType.UPDATE);
6554
return super.visitUpdate_statement(ctx);
6655
}
6756

6857
@Override
6958
public Void visitDelete_statement(EqlParser.Delete_statementContext ctx) {
70-
statementType = QueryInformation.StatementType.DELETE;
59+
60+
introspection.setStatementType(QueryInformation.StatementType.DELETE);
7161
return super.visitDelete_statement(ctx);
7262
}
7363

7464
@Override
7565
public Void visitSelect_clause(EqlParser.Select_clauseContext ctx) {
7666

77-
if (!projectionProcessed) {
78-
projection = captureSelectItems(ctx.select_item(), renderer);
79-
projectionProcessed = true;
80-
}
81-
67+
introspection.captureProjection(ctx.select_item(), renderer::visitSelect_item);
8268
return super.visitSelect_clause(ctx);
8369
}
8470

8571
@Override
8672
public Void visitRange_variable_declaration(EqlParser.Range_variable_declarationContext ctx) {
8773

88-
if (primaryFromAlias == null && ctx.identification_variable() != null && !EqlQueryRenderer.isSubquery(ctx)
74+
if (ctx.identification_variable() != null && !EqlQueryRenderer.isSubquery(ctx)
8975
&& !EqlQueryRenderer.isSetQuery(ctx)) {
90-
primaryFromAlias = ctx.identification_variable().getText();
76+
introspection.capturePrimaryAlias(ctx.identification_variable().getText());
9177
}
9278

9379
return super.visitRange_variable_declaration(ctx);
@@ -96,23 +82,8 @@ public Void visitRange_variable_declaration(EqlParser.Range_variable_declaration
9682
@Override
9783
public Void visitConstructor_expression(EqlParser.Constructor_expressionContext ctx) {
9884

99-
hasConstructorExpression = true;
85+
introspection.constructorExpressionPresent();
10086
return super.visitConstructor_expression(ctx);
10187
}
10288

103-
private static List<QueryToken> captureSelectItems(List<EqlParser.Select_itemContext> selections,
104-
EqlQueryRenderer itemRenderer) {
105-
106-
List<QueryToken> selectItemTokens = new ArrayList<>(selections.size() * 2);
107-
for (EqlParser.Select_itemContext selection : selections) {
108-
109-
if (!selectItemTokens.isEmpty()) {
110-
selectItemTokens.add(TOKEN_COMMA);
111-
}
112-
113-
selectItemTokens.add(QueryTokens.token(QueryRenderer.from(itemRenderer.visitSelect_item(selection)).render()));
114-
}
115-
return selectItemTokens;
116-
}
117-
11889
}

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

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,12 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import java.util.List;
19-
20-
import org.jspecify.annotations.Nullable;
21-
2218
/**
2319
* Hibernate-specific query details capturing common table expression details.
2420
*
2521
* @author Mark Paluch
2622
* @author Oscar Fanchin
27-
* @author kssumin
23+
* @author Soomin Kim
2824
* @since 3.5
2925
*/
3026
class HibernateQueryInformation extends QueryInformation {
@@ -33,16 +29,8 @@ class HibernateQueryInformation extends QueryInformation {
3329

3430
private final boolean hasFromFunction;
3531

36-
public HibernateQueryInformation(@Nullable String alias, List<QueryToken> projection,
37-
boolean hasConstructorExpression, boolean hasCte, boolean hasFromFunction) {
38-
super(alias, projection, hasConstructorExpression);
39-
this.hasCte = hasCte;
40-
this.hasFromFunction = hasFromFunction;
41-
}
42-
43-
public HibernateQueryInformation(@Nullable String alias, List<QueryToken> projection,
44-
boolean hasConstructorExpression, StatementType statementType, boolean hasCte, boolean hasFromFunction) {
45-
super(alias, projection, hasConstructorExpression, statementType);
32+
public HibernateQueryInformation(QueryInformationHolder introspection, boolean hasCte, boolean hasFromFunction) {
33+
super(introspection);
4634
this.hasCte = hasCte;
4735
this.hasFromFunction = hasFromFunction;
4836
}

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

Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,73 +15,68 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import static org.springframework.data.jpa.repository.query.QueryTokens.*;
19-
20-
import java.util.ArrayList;
21-
import java.util.Collections;
22-
import java.util.List;
23-
24-
import org.jspecify.annotations.Nullable;
25-
2618
import org.springframework.data.jpa.repository.query.HqlParser.VariableContext;
2719

2820
/**
2921
* {@link ParsedQueryIntrospector} for HQL queries.
3022
*
3123
* @author Mark Paluch
3224
* @author Oscar Fanchin
33-
* @author kssumin
25+
* @author Soomin Kim
3426
*/
35-
@SuppressWarnings({ "UnreachableCode", "ConstantValue" })
27+
@SuppressWarnings({ "UnreachableCode" })
3628
class HqlQueryIntrospector extends HqlBaseVisitor<Void> implements ParsedQueryIntrospector<HibernateQueryInformation> {
3729

3830
private final HqlQueryRenderer renderer = new HqlQueryRenderer();
31+
private final QueryInformationHolder introspection = new QueryInformationHolder();
3932

40-
private @Nullable String primaryFromAlias = null;
41-
private @Nullable List<QueryToken> projection;
42-
private boolean projectionProcessed;
43-
private boolean hasConstructorExpression = false;
4433
private boolean hasCte = false;
4534
private boolean hasFromFunction = false;
46-
private QueryInformation.StatementType statementType = QueryInformation.StatementType.SELECT;
4735

4836
@Override
4937
public HibernateQueryInformation getParsedQueryInformation() {
50-
return new HibernateQueryInformation(primaryFromAlias, projection == null ? Collections.emptyList() : projection,
51-
hasConstructorExpression, statementType, hasCte, hasFromFunction);
38+
return new HibernateQueryInformation(introspection, hasCte, hasFromFunction);
5239
}
5340

5441
@Override
5542
public Void visitSelectStatement(HqlParser.SelectStatementContext ctx) {
56-
statementType = QueryInformation.StatementType.SELECT;
43+
44+
introspection.setStatementType(QueryInformation.StatementType.SELECT);
5745
return super.visitSelectStatement(ctx);
5846
}
5947

48+
@Override
49+
public Void visitFromQuery(HqlParser.FromQueryContext ctx) {
50+
51+
introspection.setStatementType(QueryInformation.StatementType.SELECT);
52+
return super.visitFromQuery(ctx);
53+
}
54+
6055
@Override
6156
public Void visitInsertStatement(HqlParser.InsertStatementContext ctx) {
62-
statementType = QueryInformation.StatementType.INSERT;
57+
58+
introspection.setStatementType(QueryInformation.StatementType.INSERT);
6359
return super.visitInsertStatement(ctx);
6460
}
6561

6662
@Override
6763
public Void visitUpdateStatement(HqlParser.UpdateStatementContext ctx) {
68-
statementType = QueryInformation.StatementType.UPDATE;
64+
65+
introspection.setStatementType(QueryInformation.StatementType.UPDATE);
6966
return super.visitUpdateStatement(ctx);
7067
}
7168

7269
@Override
7370
public Void visitDeleteStatement(HqlParser.DeleteStatementContext ctx) {
74-
statementType = QueryInformation.StatementType.DELETE;
71+
72+
introspection.setStatementType(QueryInformation.StatementType.DELETE);
7573
return super.visitDeleteStatement(ctx);
7674
}
7775

7876
@Override
7977
public Void visitSelectClause(HqlParser.SelectClauseContext ctx) {
8078

81-
if (!this.projectionProcessed) {
82-
this.projection = captureSelectItems(ctx.selectionList().selection(), renderer);
83-
this.projectionProcessed = true;
84-
}
79+
introspection.captureProjection(ctx.selectionList().selection(), renderer::visitSelection);
8580

8681
return super.visitSelectClause(ctx);
8782
}
@@ -95,9 +90,9 @@ public Void visitCte(HqlParser.CteContext ctx) {
9590
@Override
9691
public Void visitRootEntity(HqlParser.RootEntityContext ctx) {
9792

98-
if (this.primaryFromAlias == null && ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)
93+
if (ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)
9994
&& !HqlQueryRenderer.isSetQuery(ctx)) {
100-
this.primaryFromAlias = capturePrimaryAlias(ctx.variable());
95+
capturePrimaryAlias(ctx.variable());
10196
}
10297

10398
return super.visitRootEntity(ctx);
@@ -106,9 +101,9 @@ public Void visitRootEntity(HqlParser.RootEntityContext ctx) {
106101
@Override
107102
public Void visitRootSubquery(HqlParser.RootSubqueryContext ctx) {
108103

109-
if (this.primaryFromAlias == null && ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)
104+
if (ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)
110105
&& !HqlQueryRenderer.isSetQuery(ctx)) {
111-
this.primaryFromAlias = capturePrimaryAlias(ctx.variable());
106+
capturePrimaryAlias(ctx.variable());
112107
}
113108

114109
return super.visitRootSubquery(ctx);
@@ -117,9 +112,9 @@ public Void visitRootSubquery(HqlParser.RootSubqueryContext ctx) {
117112
@Override
118113
public Void visitRootFunction(HqlParser.RootFunctionContext ctx) {
119114

120-
if (this.primaryFromAlias == null && ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)
115+
if (ctx.variable() != null && !HqlQueryRenderer.isSubquery(ctx)
121116
&& !HqlQueryRenderer.isSetQuery(ctx)) {
122-
this.primaryFromAlias = capturePrimaryAlias(ctx.variable());
117+
capturePrimaryAlias(ctx.variable());
123118
this.hasFromFunction = true;
124119
}
125120

@@ -129,27 +124,13 @@ public Void visitRootFunction(HqlParser.RootFunctionContext ctx) {
129124
@Override
130125
public Void visitInstantiation(HqlParser.InstantiationContext ctx) {
131126

132-
hasConstructorExpression = true;
133-
127+
introspection.constructorExpressionPresent();
134128
return super.visitInstantiation(ctx);
135129
}
136130

137-
private static String capturePrimaryAlias(VariableContext ctx) {
138-
return ((ctx).nakedIdentifier() != null ? ctx.nakedIdentifier() : ctx.identifier()).getText();
131+
private void capturePrimaryAlias(VariableContext ctx) {
132+
introspection
133+
.capturePrimaryAlias((ctx.nakedIdentifier() != null ? ctx.nakedIdentifier() : ctx.identifier()).getText());
139134
}
140135

141-
private static List<QueryToken> captureSelectItems(List<HqlParser.SelectionContext> selections,
142-
HqlQueryRenderer itemRenderer) {
143-
144-
List<QueryToken> selectItemTokens = new ArrayList<>(selections.size() * 2);
145-
for (HqlParser.SelectionContext selection : selections) {
146-
147-
if (!selectItemTokens.isEmpty()) {
148-
selectItemTokens.add(TOKEN_COMMA);
149-
}
150-
151-
selectItemTokens.add(QueryTokens.token(QueryRenderer.from(itemRenderer.visitSelection(selection)).render()));
152-
}
153-
return selectItemTokens;
154-
}
155136
}

0 commit comments

Comments
 (0)