Refactoring.

See #3622
Original pull request: #3527
This commit is contained in:
Christoph Strobl
2025-03-04 13:41:10 +01:00
committed by Mark Paluch
parent 878cd8cd4d
commit 5da86f1bf0
27 changed files with 305 additions and 207 deletions

View File

@@ -55,7 +55,7 @@ public class HqlParserBenchmarks {
OR p.description LIKE "cost overrun"
""";
query = DeclaredQuery.ofJpql(s);
query = DeclaredQuery.jpqlQuery(s);
enhancer = QueryEnhancerFactory.forQuery(query).create(query);
}
}

View File

@@ -56,7 +56,7 @@ public class JSqlParserQueryEnhancerBenchmarks {
select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE
union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE""";
enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.ofNative(s));
enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.nativeQuery(s));
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.Collections;
import java.util.List;
/**
* @author Christoph Strobl
*/
final class BindableQuery implements DeclaredQuery {
private final DeclaredQuery source;
private final String bindableQueryString;
private final List<ParameterBinding> bindings;
private final boolean usesJdbcStyleParameters;
public BindableQuery(DeclaredQuery source, String bindableQueryString, List<ParameterBinding> bindings, boolean usesJdbcStyleParameters) {
this.source = source;
this.bindableQueryString = bindableQueryString;
this.bindings = bindings;
this.usesJdbcStyleParameters = usesJdbcStyleParameters;
}
@Override
public boolean isNativeQuery() {
return source.isNativeQuery();
}
boolean hasBindings() {
return !bindings.isEmpty();
}
boolean usesJdbcStyleParameters() {
return usesJdbcStyleParameters;
}
@Override
public String getQueryString() {
return bindableQueryString;
}
public BindableQuery unifyBindings(BindableQuery comparisonQuery) {
if (comparisonQuery.hasBindings() && !comparisonQuery.bindings.equals(this.bindings)) {
return new BindableQuery(source, bindableQueryString, comparisonQuery.bindings, usesJdbcStyleParameters);
}
return this;
}
public List<ParameterBinding> getBindings() {
return Collections.unmodifiableList(bindings);
}
}

View File

@@ -23,33 +23,28 @@ package org.springframework.data.jpa.repository.query;
* @author Mark Paluch
* @since 2.0.3
*/
public interface DeclaredQuery {
public interface DeclaredQuery extends StructuredQuery {
/**
* Creates a DeclaredQuery for a JPQL query.
*
* @param query the JPQL query string.
* @return
* @param jpql the JPQL query string.
* @return new instance of {@link DeclaredQuery}.
*/
static DeclaredQuery ofJpql(String query) {
return new DefaultDeclaredQuery(query, false);
static DeclaredQuery jpqlQuery(String jpql) {
return new JpqlQuery(jpql);
}
/**
* Creates a DeclaredQuery for a native query.
*
* @param query the native query string.
* @return
* @param sql the native query string.
* @return new instance of {@link DeclaredQuery}.
*/
static DeclaredQuery ofNative(String query) {
return new DefaultDeclaredQuery(query, true);
static DeclaredQuery nativeQuery(String sql) {
return new NativeQuery(sql);
}
/**
* Returns the query string.
*/
String getQueryString();
/**
* Return whether the query is a native query of not.
*

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2024 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 org.springframework.util.ObjectUtils;
/**
* @author Mark Paluch
*/
class DefaultDeclaredQuery implements DeclaredQuery {
private final String query;
private final boolean nativeQuery;
DefaultDeclaredQuery(String query, boolean nativeQuery) {
this.query = query;
this.nativeQuery = nativeQuery;
}
@Override
public String getQueryString() {
return query;
}
@Override
public boolean isNativeQuery() {
return nativeQuery;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof DefaultDeclaredQuery that)) {
return false;
}
if (nativeQuery != that.nativeQuery) {
return false;
}
return ObjectUtils.nullSafeEquals(query, that.query);
}
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(query);
result = 31 * result + (nativeQuery ? 1 : 0);
return result;
}
@Override
public String toString() {
return (isNativeQuery() ? "[native] " : "[JPQL] ") + getQueryString();
}
}

View File

@@ -29,13 +29,13 @@ import org.jspecify.annotations.Nullable;
*/
public class DefaultQueryEnhancer implements QueryEnhancer {
private final DeclaredQuery query;
private final StructuredQuery query;
private final boolean hasConstructorExpression;
private final @Nullable String alias;
private final String projection;
private final Set<String> joinAliases;
public DefaultQueryEnhancer(DeclaredQuery query) {
public DefaultQueryEnhancer(StructuredQuery query) {
this.query = query;
this.hasConstructorExpression = QueryUtils.hasConstructorExpression(query.getQueryString());
this.alias = QueryUtils.detectAlias(query.getQueryString());
@@ -60,7 +60,9 @@ public class DefaultQueryEnhancer implements QueryEnhancer {
@Override
public String createCountQueryFor(@Nullable String countProjection) {
return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection, this.query.isNativeQuery());
boolean nativeQuery = this.query instanceof DeclaredQuery dc ? dc.isNativeQuery() : true;
return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection, nativeQuery);
}
@Override
@@ -84,7 +86,7 @@ public class DefaultQueryEnhancer implements QueryEnhancer {
}
@Override
public DeclaredQuery getQuery() {
public StructuredQuery getQuery() {
return this.query;
}
}

View File

@@ -63,6 +63,11 @@ class EmptyIntrospectedQuery implements EntityQuery {
return false;
}
@Override
public String getQueryString() {
return "";
}
@Override
public List<ParameterBinding> getParameterBindings() {
return Collections.emptyList();
@@ -82,4 +87,9 @@ class EmptyIntrospectedQuery implements EntityQuery {
public boolean usesJdbcStyleParameters() {
return false;
}
@Override
public DeclaredQuery getDeclaredQuery() {
return DeclaredQuery.nativeQuery("");
}
}

View File

@@ -24,7 +24,13 @@ import java.util.List;
* @author Diego Krupitza
* @since 2.0.3
*/
interface IntrospectedQuery extends DeclaredQuery {
interface IntrospectedQuery extends StructuredQuery {
DeclaredQuery getDeclaredQuery();
default String getQueryString() {
return getDeclaredQuery().getQueryString();
}
/**
* @return whether the underlying query has at least one named parameter.

View File

@@ -74,7 +74,7 @@ import org.springframework.util.StringUtils;
*/
public class JSqlParserQueryEnhancer implements QueryEnhancer {
private final DeclaredQuery query;
private final StructuredQuery query;
private final Statement statement;
private final ParsedType parsedType;
private final boolean hasConstructorExpression;
@@ -87,7 +87,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
/**
* @param query the query we want to enhance. Must not be {@literal null}.
*/
public JSqlParserQueryEnhancer(DeclaredQuery query) {
public JSqlParserQueryEnhancer(StructuredQuery query) {
this.query = query;
this.statement = parseStatement(query.getQueryString(), Statement.class);
@@ -339,7 +339,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
}
@Override
public DeclaredQuery getQuery() {
public StructuredQuery getQuery() {
return this.query;
}
@@ -410,8 +410,8 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer {
this.query::getQueryString);
}
private static String createCountQueryFor(PlainSelect selectBody, @Nullable String countProjection,
@Nullable String primaryAlias) {
private static String createCountQueryFor(StructuredQuery query, PlainSelect selectBody,
@Nullable String countProjection, @Nullable String primaryAlias) {
// remove order by
selectBody.setOrderByElements(null);

View File

@@ -0,0 +1,38 @@
/*
* 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;
/**
* @author Christoph Strobl
*/
final class JpqlQuery implements DeclaredQuery {
private final String jpql;
JpqlQuery(String jpql) {
this.jpql = jpql;
}
@Override
public boolean isNativeQuery() {
return false;
}
@Override
public String getQueryString() {
return jpql;
}
}

View File

@@ -185,16 +185,11 @@ final class NamedQuery extends AbstractJpaQuery {
EntityManager em = getEntityManager();
TypedQuery<Long> countQuery;
String cacheKey;
if (namedCountQueryIsPresent) {
cacheKey = countQueryName;
countQuery = em.createNamedQuery(countQueryName, Long.class);
} else {
String countQueryString = entityQuery.get().deriveCountQuery(countProjection).getQueryString();
countQueryString = potentiallyRewriteQuery(countQueryString, accessor.getSort(), accessor.getPageable());
cacheKey = countQueryString;
countQuery = em.createQuery(countQueryString, Long.class);
}

View File

@@ -0,0 +1,38 @@
/*
* 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;
/**
* @author Christoph Strobl
*/
final class NativeQuery implements DeclaredQuery {
private final String sql;
NativeQuery(String sql) {
this.sql = sql;
}
@Override
public boolean isNativeQuery() {
return true;
}
@Override
public String getQueryString() {
return sql;
}
}

View File

@@ -92,7 +92,6 @@ class ParameterBinderFactory {
Assert.notNull(parser, "SpelExpressionParser must not be null");
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null");
List<ParameterBinding> bindings = query.getParameterBindings();
QueryParameterSetterFactory expressionSetterFactory = QueryParameterSetterFactory.parsing(parser,
evaluationContextProvider);
@@ -101,7 +100,8 @@ class ParameterBinderFactory {
boolean usesPaging = query instanceof EntityQuery eq && eq.usesPaging();
return new ParameterBinder(parameters, createSetters(bindings, query, expressionSetterFactory, basicSetterFactory),
// TODO: lets maybe obtain the bindable query and pass that on to create the setters?
return new ParameterBinder(parameters, createSetters(query.getParameterBindings(), query, expressionSetterFactory, basicSetterFactory),
!usesPaging);
}

View File

@@ -66,7 +66,7 @@ public interface QueryEnhancer {
*
* @return non-null {@link DeclaredQuery} that wraps the query.
*/
DeclaredQuery getQuery();
StructuredQuery getQuery();
/**
* Adds {@literal order by} clause to the JPQL query. Uses the first alias to bind the sorting property to.

View File

@@ -57,7 +57,7 @@ public class QueryEnhancerFactories {
}
@Override
public QueryEnhancer create(DeclaredQuery query) {
public QueryEnhancer create(StructuredQuery query) {
return new DefaultQueryEnhancer(query);
}
},
@@ -69,7 +69,7 @@ public class QueryEnhancerFactories {
}
@Override
public QueryEnhancer create(DeclaredQuery query) {
public QueryEnhancer create(StructuredQuery query) {
if (jSqlParserPresent) {
return new JSqlParserQueryEnhancer(query);
}
@@ -85,7 +85,7 @@ public class QueryEnhancerFactories {
}
@Override
public QueryEnhancer create(DeclaredQuery query) {
public QueryEnhancer create(StructuredQuery query) {
return JpaQueryEnhancer.forHql(query.getQueryString());
}
},
@@ -96,7 +96,7 @@ public class QueryEnhancerFactories {
}
@Override
public QueryEnhancer create(DeclaredQuery query) {
public QueryEnhancer create(StructuredQuery query) {
return JpaQueryEnhancer.forEql(query.getQueryString());
}
},
@@ -107,7 +107,7 @@ public class QueryEnhancerFactories {
}
@Override
public QueryEnhancer create(DeclaredQuery query) {
public QueryEnhancer create(StructuredQuery query) {
return JpaQueryEnhancer.forJpql(query.getQueryString());
}
}

View File

@@ -40,7 +40,7 @@ public interface QueryEnhancerFactory {
* @param query the query to be enhanced and introspected.
* @return
*/
QueryEnhancer create(DeclaredQuery query);
QueryEnhancer create(StructuredQuery query);
/**
* Creates a new {@link QueryEnhancerFactory} for the given {@link DeclaredQuery}.

View File

@@ -66,7 +66,7 @@ public interface QueryEnhancerSelector {
private final QueryEnhancerFactory nativeQuery;
private final QueryEnhancerFactory jpql;
public DefaultQueryEnhancerSelector() {
DefaultQueryEnhancerSelector() {
this(DEFAULT_NATIVE, DEFAULT_JPQL);
}

View File

@@ -64,11 +64,8 @@ import org.springframework.util.StringUtils;
*/
class StringQuery implements EntityQuery {
private final String query;
private final List<ParameterBinding> bindings;
private final BindableQuery bindableQuery;
private final boolean containsPageableInSpel;
private final boolean usesJdbcStyleParameters;
private final boolean isNative;
private final QueryEnhancerFactory queryEnhancerFactory;
private final QueryEnhancer queryEnhancer;
private final boolean hasNamedParameters;
@@ -78,7 +75,7 @@ class StringQuery implements EntityQuery {
*
* @param query must not be {@literal null} or empty.
*/
public StringQuery(String query, boolean isNative) {
StringQuery(String query, boolean isNative) {
this(query, isNative, QueryEnhancerSelector.DEFAULT_SELECTOR, it -> {});
}
@@ -91,29 +88,15 @@ class StringQuery implements EntityQuery {
Assert.hasText(query, "Query must not be null or empty");
this.isNative = isNative;
this.bindings = new ArrayList<>();
this.containsPageableInSpel = query.contains("#pageable");
this.queryEnhancerFactory = factory;
Metadata queryMeta = new Metadata();
this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
this.bindings, queryMeta);
DeclaredQuery source = isNative ? DeclaredQuery.nativeQuery(query) : DeclaredQuery.jpqlQuery(query);
this.bindableQuery = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(source);
this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters;
this.queryEnhancer = factory.create(this);
parameterPostProcessor.accept(this.bindings);
boolean hasNamedParameters = false;
for (ParameterBinding parameterBinding : getParameterBindings()) {
if (parameterBinding.getIdentifier().hasName() && parameterBinding.getOrigin().isMethodArgument()) {
hasNamedParameters = true;
break;
}
}
this.hasNamedParameters = hasNamedParameters;
parameterPostProcessor.accept(this.bindableQuery.getBindings());
this.queryEnhancer = factory.create(this.bindableQuery);
this.hasNamedParameters = containsNamedParameter(this.bindableQuery.getBindings());
}
/**
@@ -125,29 +108,32 @@ class StringQuery implements EntityQuery {
Assert.hasText(query, "Query must not be null or empty");
this.isNative = isNative;
this.bindings = new ArrayList<>();
this.containsPageableInSpel = query.contains("#pageable");
DeclaredQuery source = isNative ? DeclaredQuery.nativeQuery(query) : DeclaredQuery.jpqlQuery(query);
Metadata queryMeta = new Metadata();
this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
this.bindings, queryMeta);
this.bindableQuery = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(source);
this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters;
this.queryEnhancerFactory = selector.select(this);
this.queryEnhancer = queryEnhancerFactory.create(this);
parameterPostProcessor.accept(this.bindings);
boolean hasNamedParameters = false;
for (ParameterBinding parameterBinding : getParameterBindings()) {
if (parameterBinding.getIdentifier().hasName() && parameterBinding.getOrigin().isMethodArgument()) {
hasNamedParameters = true;
break;
}
}
this.queryEnhancerFactory = selector.select(source);
this.queryEnhancer = queryEnhancerFactory.create(this.bindableQuery);
parameterPostProcessor.accept(this.bindableQuery.getBindings());
this.hasNamedParameters = containsNamedParameter(this.bindableQuery.getBindings());
}
/**
* internal copy constructor
*
* @param bindableQuery
* @param factory
* @param enhancer
* @param hasNamedParameters
* @param containsPageableInSpel
*/
private StringQuery(BindableQuery bindableQuery, QueryEnhancerFactory factory, QueryEnhancer enhancer, boolean hasNamedParameters, boolean containsPageableInSpel) {
this.bindableQuery = bindableQuery;
this.queryEnhancerFactory = factory;
this.queryEnhancer = enhancer;
this.hasNamedParameters = hasNamedParameters;
this.containsPageableInSpel = containsPageableInSpel;
}
QueryEnhancer getQueryEnhancer() {
@@ -158,16 +144,21 @@ class StringQuery implements EntityQuery {
* Returns whether we have found some like bindings.
*/
boolean hasParameterBindings() {
return !bindings.isEmpty();
return this.bindableQuery.hasBindings();
}
String getProjection() {
return this.queryEnhancer.getProjection();
}
@Override
public String getQueryString() {
return bindableQuery.getQueryString();
}
@Override
public List<ParameterBinding> getParameterBindings() {
return bindings;
return this.bindableQuery.getBindings();
}
@Override
@@ -177,14 +168,14 @@ class StringQuery implements EntityQuery {
// JPA parameter markers and not the original expressions anymore.
return new StringQuery(this.queryEnhancer.createCountQueryFor(countQueryProjection), //
this.isNative, queryEnhancerFactory, derivedBindings -> {
this.bindableQuery.isNativeQuery(), queryEnhancerFactory, derivedBindings -> {
// need to copy expression bindings from the declared to the derived query as JPQL query derivation only sees
// JPA
// parameter markers and not the original expressions anymore.
if (this.hasParameterBindings() && !this.getParameterBindings().equals(derivedBindings)) {
for (ParameterBinding binding : bindings) {
for (ParameterBinding binding : getParameterBindings()) {
Predicate<ParameterBinding> identifier = binding::bindsTo;
Predicate<ParameterBinding> notCompatible = Predicate.not(binding::isCompatibleWith);
@@ -206,12 +197,7 @@ class StringQuery implements EntityQuery {
@Override
public boolean usesJdbcStyleParameters() {
return usesJdbcStyleParameters;
}
@Override
public String getQueryString() {
return query;
return bindableQuery.usesJdbcStyleParameters();
}
public @Nullable String getAlias() {
@@ -239,8 +225,17 @@ class StringQuery implements EntityQuery {
}
@Override
public boolean isNativeQuery() {
return isNative;
public DeclaredQuery getDeclaredQuery() {
return bindableQuery;
}
private static boolean containsNamedParameter(List<ParameterBinding> bindings) {
for (ParameterBinding parameterBinding : bindings) {
if (parameterBinding.getIdentifier().hasName() && parameterBinding.getOrigin().isMethodArgument()) {
return true;
}
}
return false;
}
/**
@@ -376,8 +371,7 @@ class StringQuery implements EntityQuery {
* Parses {@link ParameterBinding} instances from the given query and adds them to the registered bindings. Returns
* the cleaned up query.
*/
String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String query, List<ParameterBinding> bindings,
Metadata queryMeta) {
BindableQuery parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(DeclaredQuery query) {
IndexedParameterLabels parameterLabels = new IndexedParameterLabels(findParameterIndices(query));
boolean parametersShouldBeAccessedByIndex = parameterLabels.hasLabels();
@@ -385,11 +379,11 @@ class StringQuery implements EntityQuery {
/*
* Prefer indexed access over named parameters if only SpEL Expression parameters are present.
*/
if (!parametersShouldBeAccessedByIndex && query.contains("?#{")) {
if (!parametersShouldBeAccessedByIndex && query.getQueryString().contains("?#{")) {
parametersShouldBeAccessedByIndex = true;
}
ValueExpressionQueryRewriter.ParsedQuery parsedQuery = createSpelExtractor(query,
ValueExpressionQueryRewriter.ParsedQuery parsedQuery = createSpelExtractor(query.getQueryString(),
parametersShouldBeAccessedByIndex, parameterLabels);
String resultingQuery = parsedQuery.getQueryString();
@@ -412,14 +406,14 @@ class StringQuery implements EntityQuery {
String match = matcher.group(0);
if (JDBC_STYLE_PARAM.matcher(match).find()) {
queryMeta.usesJdbcStyleParameters = true;
jdbcStyle = true;
}
if (NUMBERED_STYLE_PARAM.matcher(match).find() || NAMED_STYLE_PARAM.matcher(match).find()) {
usesJpaStyleParameters = true;
}
if (usesJpaStyleParameters && queryMeta.usesJdbcStyleParameters) {
if (usesJpaStyleParameters && jdbcStyle) {
throw new IllegalArgumentException("Mixing of ? parameters and other forms like ?1 is not supported");
}
@@ -467,7 +461,7 @@ class StringQuery implements EntityQuery {
}
replacement = targetBinding.hasName() ? ":" + targetBinding.getName()
: ((!usesJpaStyleParameters && queryMeta.usesJdbcStyleParameters) ? "?"
: ((!usesJpaStyleParameters && jdbcStyle) ? "?"
: "?" + targetBinding.getPosition());
String result;
String substring = matcher.group(2);
@@ -484,7 +478,7 @@ class StringQuery implements EntityQuery {
resultingQuery = result;
}
return resultingQuery;
return new BindableQuery(query, resultingQuery, bindings, jdbcStyle);
}
private static ValueExpressionQueryRewriter.ParsedQuery createSpelExtractor(String queryWithSpel,
@@ -592,9 +586,7 @@ class StringQuery implements EntityQuery {
}
}
static class Metadata {
private boolean usesJdbcStyleParameters = false;
}
/**
* Utility to create unique parameter bindings for LIKE that refer to the same underlying method parameter but are

View File

@@ -0,0 +1,24 @@
/*
* 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;
/**
* @author Christoph Strobl
*/
public interface StructuredQuery {
String getQueryString();
}

View File

@@ -43,7 +43,7 @@ class DefaultQueryEnhancerUnitTests extends QueryEnhancerTckTests {
@Test // GH-3546
void shouldApplySorting() {
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative("SELECT e FROM Employee e"));
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.nativeQuery("SELECT e FROM Employee e"));
String sql = enhancer.applySorting(Sort.by("foo", "bar"));

View File

@@ -111,7 +111,7 @@ class ExpressionBasedStringQueryUnitTests {
+ "AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})",
metadata, CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector());
assertThat(query.isNativeQuery()).isFalse();
assertThat(query.getDeclaredQuery().isNativeQuery()).isFalse();
}
@Test
@@ -120,7 +120,7 @@ class ExpressionBasedStringQueryUnitTests {
StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata,
CONFIG.getValueExpressionDelegate().getValueExpressionParser(), true, CONFIG.getSelector());
assertThat(query.isNativeQuery()).isFalse();
assertThat(query.getDeclaredQuery().isNativeQuery()).isFalse();
}
@Test
@@ -129,7 +129,7 @@ class ExpressionBasedStringQueryUnitTests {
StringQuery query = new ExpressionBasedStringQuery("select u from User u", metadata,
CONFIG.getValueExpressionDelegate().getValueExpressionParser(), true, CONFIG.getSelector());
assertThat(query.isNativeQuery()).isTrue();
assertThat(query.getDeclaredQuery().isNativeQuery()).isTrue();
}
@Test // GH-3041

View File

@@ -46,7 +46,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
@Test // GH-3546
void shouldApplySorting() {
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofJpql("SELECT e FROM Employee e"));
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.jpqlQuery("SELECT e FROM Employee e"));
String sql = enhancer.applySorting(Sort.by("foo", "bar"));
@@ -56,7 +56,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
@Test // GH-3886
void shouldApplySortingWithNullsPrecedence() {
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.of("SELECT e FROM Employee e", true));
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.jpqlQuery("SELECT e FROM Employee e"));
String sql = enhancer.rewrite(new DefaultQueryRewriteInformation(
Sort.by(Sort.Order.asc("foo").with(Sort.NullHandling.NULLS_LAST),
@@ -69,7 +69,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
@Test // GH-3707
void countQueriesShouldConsiderPrimaryTableAlias() {
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative("""
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.nativeQuery("""
SELECT DISTINCT a.*, b.b1
FROM TableA a
JOIN TableB b ON a.b = b.b
@@ -98,7 +98,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
+ "select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE";
StringQuery stringQuery = new StringQuery(setQuery, true);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery);
assertThat(stringQuery.getAlias()).isNullOrEmpty();
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("SOME_COLUMN");
@@ -121,7 +121,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
+ "union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE";
StringQuery stringQuery = new StringQuery(setQuery, true);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery);
assertThat(stringQuery.getAlias()).isNullOrEmpty();
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("SOME_COLUMN");
@@ -148,7 +148,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
+ "\t;";
StringQuery stringQuery = new StringQuery(setQuery, true);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery);
assertThat(stringQuery.getAlias()).isNullOrEmpty();
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("CustomerID");
@@ -168,7 +168,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
String setQuery = "VALUES (1, 2, 'test')";
StringQuery stringQuery = new StringQuery(setQuery, true);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery);
assertThat(stringQuery.getAlias()).isNullOrEmpty();
assertThat(stringQuery.getProjection()).isNullOrEmpty();
@@ -189,7 +189,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
+ "select day, value from sample_data as a";
StringQuery stringQuery = new StringQuery(setQuery, true);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery);
assertThat(stringQuery.getAlias()).isEqualToIgnoringCase("a");
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("day, value");
@@ -212,7 +212,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
+ "select day, value from sample_data as a";
StringQuery stringQuery = new StringQuery(setQuery, true);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery);
assertThat(stringQuery.getAlias()).isEqualToIgnoringCase("a");
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("day, value");
@@ -232,7 +232,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
void truncateStatementShouldWork() {
StringQuery stringQuery = new StringQuery("TRUNCATE TABLE foo", true);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery);
assertThat(stringQuery.getAlias()).isNull();
assertThat(stringQuery.getProjection()).isEmpty();
@@ -250,7 +250,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
void mergeStatementWorksWithJSqlParser(String query, String alias) {
StringQuery stringQuery = new StringQuery(query, true);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery);
assertThat(queryEnhancer.detectAlias()).isEqualTo(alias);
assertThat(QueryUtils.detectAlias(query)).isNull();

View File

@@ -89,7 +89,7 @@ class NamedOrIndexedQueryParameterSetterUnitTests {
softly
.assertThatThrownBy(
() -> setter.setParameter(BindableQuery.from(query), methodArguments, STRICT)) //
() -> setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, STRICT)) //
.describedAs("p-type: %s, p-name: %s, p-position: %s, temporal: %s", //
parameter.getClass(), //
parameter.getName(), //
@@ -118,7 +118,7 @@ class NamedOrIndexedQueryParameterSetterUnitTests {
softly
.assertThatCode(
() -> setter.setParameter(BindableQuery.from(query), methodArguments, LENIENT)) //
() -> setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, LENIENT)) //
.describedAs("p-type: %s, p-name: %s, p-position: %s, temporal: %s", //
parameter.getClass(), //
parameter.getName(), //
@@ -149,7 +149,7 @@ class NamedOrIndexedQueryParameterSetterUnitTests {
temporalType //
);
setter.setParameter(BindableQuery.from(query), methodArguments, LENIENT);
setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, LENIENT);
if (temporalType == null) {
verify(query).setParameter(eq(11), any(Date.class));
@@ -179,7 +179,7 @@ class NamedOrIndexedQueryParameterSetterUnitTests {
temporalType //
);
setter.setParameter(BindableQuery.from(query), methodArguments, LENIENT);
setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, LENIENT);
if (temporalType == null) {
verify(query, never()).setParameter(anyInt(), any(Date.class));

View File

@@ -34,7 +34,7 @@ class QueryEnhancerFactoryUnitTests {
StringQuery query = new StringQuery("select new com.example.User(u.firstname) from User u", false);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query).create(query);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query.getDeclaredQuery()).create(query);
assertThat(queryEnhancer) //
.isInstanceOf(JpaQueryEnhancer.class);
@@ -49,7 +49,7 @@ class QueryEnhancerFactoryUnitTests {
StringQuery query = new StringQuery("select * from User", true);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query).create(query);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query.getDeclaredQuery()).create(query);
assertThat(queryEnhancer) //
.isInstanceOf(JSqlParserQueryEnhancer.class);

View File

@@ -35,7 +35,7 @@ abstract class QueryEnhancerTckTests {
@MethodSource("nativeCountQueries") // GH-2773
void shouldDeriveNativeCountQuery(String query, String expected) {
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative(query));
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.nativeQuery(query));
String countQueryFor = enhancer.createCountQueryFor();
// lenient cleanup to allow for rendering variance
@@ -119,7 +119,7 @@ abstract class QueryEnhancerTckTests {
@MethodSource("jpqlCountQueries")
void shouldDeriveJpqlCountQuery(String query, String expected) {
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofJpql(query));
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.jpqlQuery(query));
String countQueryFor = enhancer.createCountQueryFor(null);
assertThat(countQueryFor).isEqualToIgnoringCase(expected);
@@ -178,7 +178,7 @@ abstract class QueryEnhancerTckTests {
@MethodSource("nativeQueriesWithVariables")
void shouldDeriveNativeCountQueryWithVariable(String query, String expected) {
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative(query));
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.nativeQuery(query));
String countQueryFor = enhancer.createCountQueryFor();
assertThat(countQueryFor).isEqualToIgnoringCase(expected);
@@ -205,7 +205,7 @@ abstract class QueryEnhancerTckTests {
StringQuery query = new StringQuery("select x, frommage, y from t", true);
assertThat(createQueryEnhancer(query).getProjection()).isEqualTo("x, frommage, y");
assertThat(createQueryEnhancer(query.getDeclaredQuery()).getProjection()).isEqualTo("x, frommage, y");
}
abstract QueryEnhancer createQueryEnhancer(DeclaredQuery query);

View File

@@ -632,7 +632,7 @@ class QueryEnhancerUnitTests {
assertThat(modiQuery.hasConstructorExpression()).isEqualTo(constructorExpressionNotConsideringQueryType);
assertThat(countQueryForNotConsiderQueryType).isEqualToIgnoringCase(modifyingQuery);
assertThat(QueryEnhancerFactory.forQuery(modiQuery).create(modiQuery).createCountQueryFor())
assertThat(QueryEnhancerFactory.forQuery(modiQuery.getDeclaredQuery()).create(modiQuery.getDeclaredQuery()).createCountQueryFor())
.isEqualToIgnoringCase(modifyingQuery);
}
@@ -641,7 +641,7 @@ class QueryEnhancerUnitTests {
void insertStatementIsProcessedSameAsDefault(String insertQuery) {
StringQuery stringQuery = new StringQuery(insertQuery, true);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery.getDeclaredQuery());
Sort sorting = Sort.by("day").descending();
@@ -697,7 +697,7 @@ class QueryEnhancerUnitTests {
}
private static QueryEnhancer getEnhancer(IntrospectedQuery query) {
return QueryEnhancerFactory.forQuery(query).create(query);
return QueryEnhancerFactory.forQuery(query.getDeclaredQuery()).create(query.getDeclaredQuery());
}
}

View File

@@ -17,7 +17,6 @@ package org.springframework.data.jpa.repository.query;
import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -29,6 +28,7 @@ import org.springframework.data.jpa.repository.query.ParameterBinding.InParamete
import org.springframework.data.jpa.repository.query.ParameterBinding.LikeParameterBinding;
import org.springframework.data.jpa.repository.query.ParameterBinding.MethodInvocationArgument;
import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin;
import org.springframework.data.jpa.repository.query.StringQuery.ParameterBindingParser;
import org.springframework.data.repository.query.parser.Part.Type;
/**
@@ -926,11 +926,10 @@ class StringQueryUnitTests {
private void checkHasNamedParameter(String query, boolean expected, String label) {
List<ParameterBinding> bindings = new ArrayList<>();
StringQuery.ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
bindings, new StringQuery.Metadata());
DeclaredQuery source = nativeQuery ? DeclaredQuery.nativeQuery(query) : DeclaredQuery.jpqlQuery(query);
BindableQuery bindableQuery = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(source);
assertThat(bindings.stream().anyMatch(it -> it.getIdentifier().hasName())) //
assertThat(bindableQuery.getBindings().stream().anyMatch(it -> it.getIdentifier().hasName())) //
.describedAs(String.format("<%s> (%s)", query, label)) //
.isEqualTo(expected);
}