Polishing.
Introduce QueryInformation to provide query introspection results. Refine generics. See #3726 Original pull request: #3730
This commit is contained in:
@@ -36,10 +36,9 @@ class EqlCountQueryTransformer extends EqlQueryRenderer {
|
||||
private final @Nullable String countProjection;
|
||||
private final @Nullable String primaryFromAlias;
|
||||
|
||||
EqlCountQueryTransformer(@Nullable String countProjection, @Nullable String primaryFromAlias) {
|
||||
|
||||
EqlCountQueryTransformer(@Nullable String countProjection, QueryInformation queryInformation) {
|
||||
this.countProjection = countProjection;
|
||||
this.primaryFromAlias = primaryFromAlias;
|
||||
this.primaryFromAlias = queryInformation.getAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_COMMA;
|
||||
import static org.springframework.data.jpa.repository.query.QueryTokens.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -31,7 +31,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@SuppressWarnings("UnreachableCode")
|
||||
class EqlQueryIntrospector extends EqlBaseVisitor<Void> implements ParsedQueryIntrospector {
|
||||
class EqlQueryIntrospector extends EqlBaseVisitor<Void> implements ParsedQueryIntrospector<QueryInformation> {
|
||||
|
||||
private final EqlQueryRenderer renderer = new EqlQueryRenderer();
|
||||
|
||||
@@ -41,18 +41,9 @@ class EqlQueryIntrospector extends EqlBaseVisitor<Void> implements ParsedQueryIn
|
||||
private boolean hasConstructorExpression = false;
|
||||
|
||||
@Override
|
||||
public String getAlias() {
|
||||
return primaryFromAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QueryToken> getProjection() {
|
||||
return projection == null ? Collections.emptyList() : projection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConstructorExpression() {
|
||||
return hasConstructorExpression;
|
||||
public QueryInformation getParsedQueryInformation() {
|
||||
return new QueryInformation(primaryFromAlias, projection == null ? Collections.emptyList() : projection,
|
||||
hasConstructorExpression);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -43,12 +43,13 @@ class EqlSortedQueryTransformer extends EqlQueryRenderer {
|
||||
private final @Nullable String primaryFromAlias;
|
||||
private final @Nullable DtoProjectionTransformerDelegate dtoDelegate;
|
||||
|
||||
EqlSortedQueryTransformer(Sort sort, @Nullable String primaryFromAlias, @Nullable ReturnedType returnedType) {
|
||||
EqlSortedQueryTransformer(Sort sort, QueryInformation queryInformation, @Nullable ReturnedType returnedType) {
|
||||
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
Assert.notNull(queryInformation, "ParsedHqlQueryInformation must not be null");
|
||||
|
||||
this.sort = sort;
|
||||
this.primaryFromAlias = primaryFromAlias;
|
||||
this.primaryFromAlias = queryInformation.getAlias();
|
||||
this.dtoDelegate = returnedType == null ? null : new DtoProjectionTransformerDelegate(returnedType);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Hibernate-specific query details capturing common table expression details.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.5
|
||||
*/
|
||||
class HibernateQueryInformation extends QueryInformation {
|
||||
|
||||
private final boolean hasCte;
|
||||
|
||||
public HibernateQueryInformation(@Nullable String alias, List<QueryToken> projection,
|
||||
boolean hasConstructorExpression, boolean hasCte) {
|
||||
super(alias, projection, hasConstructorExpression);
|
||||
this.hasCte = hasCte;
|
||||
}
|
||||
|
||||
public boolean hasCte() {
|
||||
return hasCte;
|
||||
}
|
||||
}
|
||||
@@ -36,11 +36,12 @@ class HqlCountQueryTransformer extends HqlQueryRenderer {
|
||||
|
||||
private final @Nullable String countProjection;
|
||||
private final @Nullable String primaryFromAlias;
|
||||
private boolean containsCTE = false;
|
||||
private final boolean containsCTE;
|
||||
|
||||
HqlCountQueryTransformer(@Nullable String countProjection, @Nullable String primaryFromAlias) {
|
||||
HqlCountQueryTransformer(@Nullable String countProjection, HibernateQueryInformation queryInformation) {
|
||||
this.countProjection = countProjection;
|
||||
this.primaryFromAlias = primaryFromAlias;
|
||||
this.primaryFromAlias = queryInformation.getAlias();
|
||||
this.containsCTE = queryInformation.hasCte();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,12 +68,6 @@ class HqlCountQueryTransformer extends HqlQueryRenderer {
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryTokenStream visitCte(HqlParser.CteContext ctx) {
|
||||
this.containsCTE = true;
|
||||
return super.visitCte(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryRendererBuilder visitFromQuery(HqlParser.FromQueryContext ctx) {
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@SuppressWarnings({ "UnreachableCode", "ConstantValue" })
|
||||
class HqlQueryIntrospector extends HqlBaseVisitor<Void> implements ParsedQueryIntrospector {
|
||||
class HqlQueryIntrospector extends HqlBaseVisitor<Void> implements ParsedQueryIntrospector<HibernateQueryInformation> {
|
||||
|
||||
private final HqlQueryRenderer renderer = new HqlQueryRenderer();
|
||||
|
||||
@@ -38,20 +38,12 @@ class HqlQueryIntrospector extends HqlBaseVisitor<Void> implements ParsedQueryIn
|
||||
private @Nullable List<QueryToken> projection;
|
||||
private boolean projectionProcessed;
|
||||
private boolean hasConstructorExpression = false;
|
||||
private boolean hasCte = false;
|
||||
|
||||
@Override
|
||||
public String getAlias() {
|
||||
return primaryFromAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<QueryToken> getProjection() {
|
||||
return projection == null ? Collections.emptyList() : projection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConstructorExpression() {
|
||||
return hasConstructorExpression;
|
||||
public HibernateQueryInformation getParsedQueryInformation() {
|
||||
return new HibernateQueryInformation(primaryFromAlias, projection == null ? Collections.emptyList() : projection,
|
||||
hasConstructorExpression, hasCte);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,6 +57,12 @@ class HqlQueryIntrospector extends HqlBaseVisitor<Void> implements ParsedQueryIn
|
||||
return super.visitSelectClause(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitCte(HqlParser.CteContext ctx) {
|
||||
this.hasCte = true;
|
||||
return super.visitCte(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitFromRoot(HqlParser.FromRootContext ctx) {
|
||||
|
||||
|
||||
@@ -41,21 +41,14 @@ class HqlSortedQueryTransformer extends HqlQueryRenderer {
|
||||
private final @Nullable String primaryFromAlias;
|
||||
private final @Nullable DtoProjectionTransformerDelegate dtoDelegate;
|
||||
|
||||
HqlSortedQueryTransformer(Sort sort, @Nullable String primaryFromAlias) {
|
||||
HqlSortedQueryTransformer(Sort sort, HibernateQueryInformation queryInformation,
|
||||
@Nullable ReturnedType returnedType) {
|
||||
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
Assert.notNull(queryInformation, "ParsedHqlQueryInformation must not be null");
|
||||
|
||||
this.sort = sort;
|
||||
this.primaryFromAlias = primaryFromAlias;
|
||||
this.dtoDelegate = null;
|
||||
}
|
||||
|
||||
HqlSortedQueryTransformer(Sort sort, @Nullable String primaryFromAlias, @Nullable ReturnedType returnedType) {
|
||||
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
|
||||
this.sort = sort;
|
||||
this.primaryFromAlias = primaryFromAlias;
|
||||
this.primaryFromAlias = queryInformation.getAlias();
|
||||
this.dtoDelegate = returnedType == null ? null : new DtoProjectionTransformerDelegate(returnedType);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,25 +44,27 @@ import org.springframework.util.Assert;
|
||||
* @see HqlQueryParser
|
||||
* @see EqlQueryParser
|
||||
*/
|
||||
class JpaQueryEnhancer implements QueryEnhancer {
|
||||
@SuppressWarnings("removal")
|
||||
class JpaQueryEnhancer<Q extends QueryInformation> implements QueryEnhancer {
|
||||
|
||||
private final ParserRuleContext context;
|
||||
private final ParsedQueryIntrospector introspector;
|
||||
private final Q queryInformation;
|
||||
private final String projection;
|
||||
private final SortedQueryRewriteFunction sortFunction;
|
||||
private final BiFunction<String, String, ParseTreeVisitor<? extends Object>> countQueryFunction;
|
||||
private final SortedQueryRewriteFunction<Q> sortFunction;
|
||||
private final BiFunction<String, Q, ParseTreeVisitor<QueryTokenStream>> countQueryFunction;
|
||||
|
||||
JpaQueryEnhancer(ParserRuleContext context, ParsedQueryIntrospector introspector,
|
||||
SortedQueryRewriteFunction sortFunction,
|
||||
BiFunction<String, String, ParseTreeVisitor<? extends Object>> countQueryFunction) {
|
||||
JpaQueryEnhancer(ParserRuleContext context, ParsedQueryIntrospector<Q> introspector,
|
||||
SortedQueryRewriteFunction<Q> sortFunction,
|
||||
BiFunction<String, Q, ParseTreeVisitor<QueryTokenStream>> countQueryFunction) {
|
||||
|
||||
this.context = context;
|
||||
this.introspector = introspector;
|
||||
this.sortFunction = sortFunction;
|
||||
this.countQueryFunction = countQueryFunction;
|
||||
this.introspector.visit(context);
|
||||
introspector.visit(context);
|
||||
|
||||
List<QueryToken> tokens = introspector.getProjection();
|
||||
this.queryInformation = introspector.getParsedQueryInformation();
|
||||
|
||||
List<QueryToken> tokens = queryInformation.getProjection();
|
||||
this.projection = tokens.isEmpty() ? "" : new QueryRenderer.TokenRenderer(tokens).render();
|
||||
}
|
||||
|
||||
@@ -86,9 +88,10 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
/**
|
||||
* Apply common configuration (SLL prediction for performance, our own error listeners).
|
||||
*
|
||||
* @param query
|
||||
* @param lexer
|
||||
* @param parser
|
||||
* @param query the query input to parse.
|
||||
* @param grammar name of the grammar.
|
||||
* @param lexer lexer to configure.
|
||||
* @param parser parser to configure.
|
||||
*/
|
||||
static void configureParser(String query, String grammar, Lexer lexer, Parser parser) {
|
||||
|
||||
@@ -109,7 +112,7 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
* @param query must not be {@literal null}.
|
||||
* @return a new {@link JpaQueryEnhancer} using JPQL.
|
||||
*/
|
||||
public static JpaQueryEnhancer forJpql(DeclaredQuery query) {
|
||||
public static JpaQueryEnhancer<QueryInformation> forJpql(DeclaredQuery query) {
|
||||
|
||||
Assert.notNull(query, "DeclaredQuery must not be null!");
|
||||
|
||||
@@ -122,7 +125,7 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
* @param query must not be {@literal null}.
|
||||
* @return a new {@link JpaQueryEnhancer} using HQL.
|
||||
*/
|
||||
public static JpaQueryEnhancer forHql(DeclaredQuery query) {
|
||||
public static JpaQueryEnhancer<HibernateQueryInformation> forHql(DeclaredQuery query) {
|
||||
|
||||
Assert.notNull(query, "DeclaredQuery must not be null!");
|
||||
|
||||
@@ -136,17 +139,27 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
* @return a new {@link JpaQueryEnhancer} using EQL.
|
||||
* @since 3.2
|
||||
*/
|
||||
public static JpaQueryEnhancer forEql(DeclaredQuery query) {
|
||||
public static JpaQueryEnhancer<QueryInformation> forEql(DeclaredQuery query) {
|
||||
|
||||
Assert.notNull(query, "DeclaredQuery must not be null!");
|
||||
|
||||
return EqlQueryParser.parseQuery(query.getQueryString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the parser context (AST) representing the parsed query.
|
||||
*/
|
||||
ParserRuleContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the parsed query information.
|
||||
*/
|
||||
Q getQueryInformation() {
|
||||
return queryInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the select clause has a new constructor instantiation in the JPA query.
|
||||
*
|
||||
@@ -154,7 +167,7 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
*/
|
||||
@Override
|
||||
public boolean hasConstructorExpression() {
|
||||
return this.introspector.hasConstructorExpression();
|
||||
return this.queryInformation.hasConstructorExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,7 +176,7 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
*/
|
||||
@Override
|
||||
public String detectAlias() {
|
||||
return this.introspector.getAlias();
|
||||
return this.queryInformation.getAlias();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,8 +189,7 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the {@link JpaQueryParser} can already fully transform sorted and count queries by itself, this is a
|
||||
* placeholder method.
|
||||
* Since the parser can already fully transform sorted and count queries by itself, this is a placeholder method.
|
||||
*
|
||||
* @return empty set
|
||||
*/
|
||||
@@ -202,13 +214,14 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
*/
|
||||
@Override
|
||||
public String applySorting(Sort sort) {
|
||||
return QueryRenderer.TokenRenderer.render(sortFunction.apply(sort, detectAlias(), null).visit(context));
|
||||
return QueryRenderer.TokenRenderer.render(sortFunction.apply(sort, this.queryInformation, null).visit(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String rewrite(QueryRewriteInformation rewriteInformation) {
|
||||
return QueryRenderer.TokenRenderer.render(sortFunction
|
||||
.apply(rewriteInformation.getSort(), detectAlias(), rewriteInformation.getReturnedType()).visit(context));
|
||||
return QueryRenderer.TokenRenderer.render(
|
||||
sortFunction.apply(rewriteInformation.getSort(), this.queryInformation, rewriteInformation.getReturnedType())
|
||||
.visit(context));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,7 +253,21 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
*/
|
||||
@Override
|
||||
public String createCountQueryFor(@Nullable String countProjection) {
|
||||
return QueryRenderer.TokenRenderer.render(countQueryFunction.apply(countProjection, detectAlias()).visit(context));
|
||||
return QueryRenderer.TokenRenderer
|
||||
.render(countQueryFunction.apply(countProjection, this.queryInformation).visit(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface to rewrite a query considering {@link Sort} and {@link ReturnedType}. The function returns a
|
||||
* visitor object that can visit the parsed query tree.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface SortedQueryRewriteFunction<Q> {
|
||||
|
||||
ParseTreeVisitor<QueryTokenStream> apply(Sort sort, Q queryInformation, @Nullable ReturnedType returnedType);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,7 +278,7 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
* @author Mark Paluch
|
||||
* @since 3.1
|
||||
*/
|
||||
static class HqlQueryParser extends JpaQueryEnhancer {
|
||||
static class HqlQueryParser extends JpaQueryEnhancer<HibernateQueryInformation> {
|
||||
|
||||
private HqlQueryParser(String query) {
|
||||
super(parse(query, HqlLexer::new, HqlParser::new, HqlParser::start), new HqlQueryIntrospector(),
|
||||
@@ -261,9 +288,9 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
/**
|
||||
* Parse a HQL query.
|
||||
*
|
||||
* @param query
|
||||
* @param query the query to parse.
|
||||
* @return the query parser.
|
||||
* @throws BadJpqlGrammarException
|
||||
* @throws BadJpqlGrammarException in case of malformed query.
|
||||
*/
|
||||
public static HqlQueryParser parseQuery(String query) throws BadJpqlGrammarException {
|
||||
return new HqlQueryParser(query);
|
||||
@@ -279,7 +306,7 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
static class EqlQueryParser extends JpaQueryEnhancer {
|
||||
static class EqlQueryParser extends JpaQueryEnhancer<QueryInformation> {
|
||||
|
||||
private EqlQueryParser(String query) {
|
||||
super(parse(query, EqlLexer::new, EqlParser::new, EqlParser::start), new EqlQueryIntrospector(),
|
||||
@@ -289,9 +316,9 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
/**
|
||||
* Parse a EQL query.
|
||||
*
|
||||
* @param query
|
||||
* @param query the query to parse.
|
||||
* @return the query parser.
|
||||
* @throws BadJpqlGrammarException
|
||||
* @throws BadJpqlGrammarException in case of malformed query.
|
||||
*/
|
||||
public static EqlQueryParser parseQuery(String query) throws BadJpqlGrammarException {
|
||||
return new EqlQueryParser(query);
|
||||
@@ -307,7 +334,7 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
* @author Mark Paluch
|
||||
* @since 3.1
|
||||
*/
|
||||
static class JpqlQueryParser extends JpaQueryEnhancer {
|
||||
static class JpqlQueryParser extends JpaQueryEnhancer<QueryInformation> {
|
||||
|
||||
private JpqlQueryParser(String query) {
|
||||
super(parse(query, JpqlLexer::new, JpqlParser::new, JpqlParser::start), new JpqlQueryIntrospector(),
|
||||
@@ -317,25 +344,13 @@ class JpaQueryEnhancer implements QueryEnhancer {
|
||||
/**
|
||||
* Parse a JPQL query.
|
||||
*
|
||||
* @param query
|
||||
* @param query the query to parse.
|
||||
* @return the query parser.
|
||||
* @throws BadJpqlGrammarException
|
||||
* @throws BadJpqlGrammarException in case of malformed query.
|
||||
*/
|
||||
public static JpqlQueryParser parseQuery(String query) throws BadJpqlGrammarException {
|
||||
return new JpqlQueryParser(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Functional interface to rewrite a query considering {@link Sort} and {@link ReturnedType}. The function returns a
|
||||
* visitor object that can visit the parsed query tree.
|
||||
*
|
||||
* @since 3.5
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface SortedQueryRewriteFunction {
|
||||
|
||||
ParseTreeVisitor<? extends Object> apply(Sort sort, String primaryAlias, @Nullable ReturnedType returnedType);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ class JpqlCountQueryTransformer extends JpqlQueryRenderer {
|
||||
private final @Nullable String countProjection;
|
||||
private final @Nullable String primaryFromAlias;
|
||||
|
||||
JpqlCountQueryTransformer(@Nullable String countProjection, @Nullable String primaryFromAlias) {
|
||||
JpqlCountQueryTransformer(@Nullable String countProjection, QueryInformation queryInformation) {
|
||||
this.countProjection = countProjection;
|
||||
this.primaryFromAlias = primaryFromAlias;
|
||||
this.primaryFromAlias = queryInformation.getAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.springframework.data.jpa.repository.query.QueryTokens.TOKEN_COMMA;
|
||||
import static org.springframework.data.jpa.repository.query.QueryTokens.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -30,7 +30,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@SuppressWarnings({ "UnreachableCode", "ConstantValue" })
|
||||
class JpqlQueryIntrospector extends JpqlBaseVisitor<Void> implements ParsedQueryIntrospector {
|
||||
class JpqlQueryIntrospector extends JpqlBaseVisitor<Void> implements ParsedQueryIntrospector<QueryInformation> {
|
||||
|
||||
private final JpqlQueryRenderer renderer = new JpqlQueryRenderer();
|
||||
|
||||
@@ -39,17 +39,10 @@ class JpqlQueryIntrospector extends JpqlBaseVisitor<Void> implements ParsedQuery
|
||||
private boolean projectionProcessed;
|
||||
private boolean hasConstructorExpression = false;
|
||||
|
||||
@Nullable
|
||||
public String getAlias() {
|
||||
return primaryFromAlias;
|
||||
}
|
||||
|
||||
public List<QueryToken> getProjection() {
|
||||
return projection == null ? Collections.emptyList() : projection;
|
||||
}
|
||||
|
||||
public boolean hasConstructorExpression() {
|
||||
return hasConstructorExpression;
|
||||
@Override
|
||||
public QueryInformation getParsedQueryInformation() {
|
||||
return new QueryInformation(primaryFromAlias, projection == null ? Collections.emptyList() : projection,
|
||||
hasConstructorExpression);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -41,21 +41,13 @@ class JpqlSortedQueryTransformer extends JpqlQueryRenderer {
|
||||
private final @Nullable String primaryFromAlias;
|
||||
private final @Nullable DtoProjectionTransformerDelegate dtoDelegate;
|
||||
|
||||
JpqlSortedQueryTransformer(Sort sort, @Nullable String primaryFromAlias) {
|
||||
JpqlSortedQueryTransformer(Sort sort, QueryInformation queryInformation, @Nullable ReturnedType returnedType) {
|
||||
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
Assert.notNull(queryInformation, "ParsedHqlQueryInformation must not be null");
|
||||
|
||||
this.sort = sort;
|
||||
this.primaryFromAlias = primaryFromAlias;
|
||||
this.dtoDelegate = null;
|
||||
}
|
||||
|
||||
JpqlSortedQueryTransformer(Sort sort, @Nullable String primaryFromAlias, @Nullable ReturnedType returnedType) {
|
||||
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
|
||||
this.sort = sort;
|
||||
this.primaryFromAlias = primaryFromAlias;
|
||||
this.primaryFromAlias = queryInformation.getAlias();
|
||||
this.dtoDelegate = returnedType == null ? null : new DtoProjectionTransformerDelegate(returnedType);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,19 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Interface defining an introspector for String-queries providing details about the primary table alias, the primary
|
||||
* selection projection and whether the query makes use of constructor expressions.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.5
|
||||
*/
|
||||
interface ParsedQueryIntrospector {
|
||||
interface ParsedQueryIntrospector<I extends QueryInformation> {
|
||||
|
||||
/**
|
||||
* Visit the parsed tree to introspect the AST tree.
|
||||
@@ -38,20 +35,10 @@ interface ParsedQueryIntrospector {
|
||||
Void visit(ParseTree tree);
|
||||
|
||||
/**
|
||||
* Primary table alias. Contains the first table name/table alias in case multiple tables are specified in the query.
|
||||
* Returns the parsed query information. The information is available after the {@link #visit(ParseTree)
|
||||
* introspection} has been completed.
|
||||
*
|
||||
* @return
|
||||
* @return parsed query information.
|
||||
*/
|
||||
@Nullable
|
||||
String getAlias();
|
||||
|
||||
/**
|
||||
* @return the primary selection.
|
||||
*/
|
||||
List<QueryToken> getProjection();
|
||||
|
||||
/**
|
||||
* @return {@code true} if the query uses a constructor expression.
|
||||
*/
|
||||
boolean hasConstructorExpression();
|
||||
I getParsedQueryInformation();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Value object capturing introspection details of a parsed query.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.5
|
||||
*/
|
||||
class QueryInformation {
|
||||
|
||||
private final @Nullable String alias;
|
||||
|
||||
private final List<QueryToken> projection;
|
||||
|
||||
private final boolean hasConstructorExpression;
|
||||
|
||||
QueryInformation(@Nullable String alias, List<QueryToken> projection, boolean hasConstructorExpression) {
|
||||
this.alias = alias;
|
||||
this.projection = projection;
|
||||
this.hasConstructorExpression = hasConstructorExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary table alias. Contains the first table name/table alias in case multiple tables are specified in the query.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the primary selection.
|
||||
*/
|
||||
public List<QueryToken> getProjection() {
|
||||
return projection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the query uses a constructor expression.
|
||||
*/
|
||||
public boolean hasConstructorExpression() {
|
||||
return hasConstructorExpression;
|
||||
}
|
||||
}
|
||||
@@ -35,15 +35,13 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
|
||||
class EqlDtoQueryTransformerUnitTests {
|
||||
|
||||
JpaQueryMethod method = getMethod("dtoProjection");
|
||||
EqlSortedQueryTransformer transformer = new EqlSortedQueryTransformer(Sort.unsorted(), null,
|
||||
method.getResultProcessor().getReturnedType());
|
||||
|
||||
@Test // GH-3076
|
||||
void shouldTranslateSingleProjectionToDto() {
|
||||
|
||||
JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser.parseQuery("SELECT p from Person p");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo(
|
||||
"SELECT new org.springframework.data.jpa.repository.query.EqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar) from Person p");
|
||||
@@ -55,7 +53,7 @@ class EqlDtoQueryTransformerUnitTests {
|
||||
JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser
|
||||
.parseQuery("select u from User u left outer join u.roles r where r in (select r from Role r)");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo(
|
||||
"select new org.springframework.data.jpa.repository.query.EqlDtoQueryTransformerUnitTests$MyRecord(u.foo, u.bar) from User u left outer join u.roles r where r in (select r from Role r)");
|
||||
@@ -67,7 +65,7 @@ class EqlDtoQueryTransformerUnitTests {
|
||||
JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser
|
||||
.parseQuery("SELECT NEW String(p) from Person p");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo("SELECT NEW String(p) from Person p");
|
||||
}
|
||||
@@ -78,6 +76,7 @@ class EqlDtoQueryTransformerUnitTests {
|
||||
JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser
|
||||
.parseQuery("SELECT p.foo, p.bar, sum(p.age) from Person p");
|
||||
|
||||
EqlSortedQueryTransformer transformer = getTransformer(parser);
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo(
|
||||
@@ -97,6 +96,11 @@ class EqlDtoQueryTransformerUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
private EqlSortedQueryTransformer getTransformer(JpaQueryEnhancer.EqlQueryParser parser) {
|
||||
return new EqlSortedQueryTransformer(Sort.unsorted(), parser.getQueryInformation(),
|
||||
method.getResultProcessor().getReturnedType());
|
||||
}
|
||||
|
||||
interface MyRepo extends Repository<Person, String> {
|
||||
|
||||
MyRecord dtoProjection();
|
||||
|
||||
@@ -35,15 +35,13 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
|
||||
class HqlDtoQueryTransformerUnitTests {
|
||||
|
||||
JpaQueryMethod method = getMethod("dtoProjection");
|
||||
HqlSortedQueryTransformer transformer = new HqlSortedQueryTransformer(Sort.unsorted(), null,
|
||||
method.getResultProcessor().getReturnedType());
|
||||
|
||||
@Test // GH-3076
|
||||
void shouldTranslateSingleProjectionToDto() {
|
||||
|
||||
JpaQueryEnhancer.HqlQueryParser parser = JpaQueryEnhancer.HqlQueryParser.parseQuery("SELECT p from Person p");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo(
|
||||
"SELECT new org.springframework.data.jpa.repository.query.HqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar) from Person p");
|
||||
@@ -55,7 +53,7 @@ class HqlDtoQueryTransformerUnitTests {
|
||||
JpaQueryEnhancer.HqlQueryParser parser = JpaQueryEnhancer.HqlQueryParser
|
||||
.parseQuery("select u from User u left outer join u.roles r where r in (select r from Role r)");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo(
|
||||
"select new org.springframework.data.jpa.repository.query.HqlDtoQueryTransformerUnitTests$MyRecord(u.foo, u.bar) from User u left outer join u.roles r where r in (select r from Role r)");
|
||||
@@ -67,7 +65,7 @@ class HqlDtoQueryTransformerUnitTests {
|
||||
JpaQueryEnhancer.HqlQueryParser parser = JpaQueryEnhancer.HqlQueryParser
|
||||
.parseQuery("SELECT NEW String(p) from Person p");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo("SELECT NEW String(p) from Person p");
|
||||
}
|
||||
@@ -78,7 +76,7 @@ class HqlDtoQueryTransformerUnitTests {
|
||||
JpaQueryEnhancer.HqlQueryParser parser = JpaQueryEnhancer.HqlQueryParser
|
||||
.parseQuery("SELECT p.foo, p.bar, sum(p.age) from Person p");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo(
|
||||
"SELECT new org.springframework.data.jpa.repository.query.HqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar, sum(p.age)) from Person p");
|
||||
@@ -97,6 +95,11 @@ class HqlDtoQueryTransformerUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
private HqlSortedQueryTransformer getTransformer(JpaQueryEnhancer.HqlQueryParser parser) {
|
||||
return new HqlSortedQueryTransformer(Sort.unsorted(), parser.getQueryInformation(),
|
||||
method.getResultProcessor().getReturnedType());
|
||||
}
|
||||
|
||||
interface MyRepo extends Repository<Person, String> {
|
||||
|
||||
MyRecord dtoProjection();
|
||||
|
||||
@@ -35,15 +35,13 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
|
||||
class JpqlDtoQueryTransformerUnitTests {
|
||||
|
||||
JpaQueryMethod method = getMethod("dtoProjection");
|
||||
JpqlSortedQueryTransformer transformer = new JpqlSortedQueryTransformer(Sort.unsorted(), null,
|
||||
method.getResultProcessor().getReturnedType());
|
||||
|
||||
@Test // GH-3076
|
||||
void shouldTranslateSingleProjectionToDto() {
|
||||
|
||||
JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser.parseQuery("SELECT p from Person p");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo(
|
||||
"SELECT new org.springframework.data.jpa.repository.query.JpqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar) from Person p");
|
||||
@@ -55,7 +53,7 @@ class JpqlDtoQueryTransformerUnitTests {
|
||||
JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser
|
||||
.parseQuery("select u from User u left outer join u.roles r where r in (select r from Role r)");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo(
|
||||
"select new org.springframework.data.jpa.repository.query.JpqlDtoQueryTransformerUnitTests$MyRecord(u.foo, u.bar) from User u left outer join u.roles r where r in (select r from Role r)");
|
||||
@@ -67,7 +65,7 @@ class JpqlDtoQueryTransformerUnitTests {
|
||||
JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser
|
||||
.parseQuery("SELECT NEW String(p) from Person p");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo("SELECT NEW String(p) from Person p");
|
||||
}
|
||||
@@ -78,7 +76,7 @@ class JpqlDtoQueryTransformerUnitTests {
|
||||
JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser
|
||||
.parseQuery("SELECT p.foo, p.bar, sum(p.age) from Person p");
|
||||
|
||||
QueryTokenStream visit = transformer.visit(parser.getContext());
|
||||
QueryTokenStream visit = getTransformer(parser).visit(parser.getContext());
|
||||
|
||||
assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo(
|
||||
"SELECT new org.springframework.data.jpa.repository.query.JpqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar, sum(p.age)) from Person p");
|
||||
@@ -97,6 +95,11 @@ class JpqlDtoQueryTransformerUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
private JpqlSortedQueryTransformer getTransformer(JpaQueryEnhancer.JpqlQueryParser parser) {
|
||||
return new JpqlSortedQueryTransformer(Sort.unsorted(), parser.getQueryInformation(),
|
||||
method.getResultProcessor().getReturnedType());
|
||||
}
|
||||
|
||||
interface MyRepo extends Repository<Person, String> {
|
||||
|
||||
MyRecord dtoProjection();
|
||||
|
||||
Reference in New Issue
Block a user