Introduce JSqlParser as option for parsing native queries.
Introduce a library that makes it possible to parse native SQL statements. See #2409.
This commit is contained in:
committed by
Greg L. Turnquist
parent
8c127f66b4
commit
1fe4cf598d
1
pom.xml
1
pom.xml
@@ -28,6 +28,7 @@
|
||||
<postgresql>42.2.19</postgresql>
|
||||
<springdata.commons>3.0.0-SNAPSHOT</springdata.commons>
|
||||
<vavr>0.10.3</vavr>
|
||||
<jsqlparser.version>4.3</jsqlparser.version>
|
||||
|
||||
<hibernate.groupId>org.hibernate</hibernate.groupId>
|
||||
|
||||
|
||||
@@ -226,6 +226,14 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.jsqlparser</groupId>
|
||||
<artifactId>jsqlparser</artifactId>
|
||||
<version>${jsqlparser.version}</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -15,14 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.LockModeType;
|
||||
import jakarta.persistence.Query;
|
||||
@@ -31,6 +23,14 @@ import jakarta.persistence.Tuple;
|
||||
import jakarta.persistence.TupleElement;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.util.Assert;
|
||||
* @author Tom Hombergs
|
||||
* @author David Madden
|
||||
* @author Mark Paluch
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
@@ -55,8 +56,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
* @param parser must not be {@literal null}.
|
||||
*/
|
||||
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
|
||||
@Nullable String countQueryString,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
|
||||
@Nullable String countQueryString, QueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
SpelExpressionParser parser) {
|
||||
|
||||
super(method, em);
|
||||
|
||||
@@ -65,10 +66,12 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
Assert.notNull(parser, "Parser must not be null!");
|
||||
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser);
|
||||
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser,
|
||||
method.isNativeQuery());
|
||||
|
||||
DeclaredQuery countQuery = query.deriveCountQuery(countQueryString, method.getCountQueryProjection());
|
||||
this.countQuery = ExpressionBasedStringQuery.from(countQuery, method.getEntityInformation(), parser);
|
||||
this.countQuery = ExpressionBasedStringQuery.from(countQuery, method.getEntityInformation(), parser,
|
||||
method.isNativeQuery());
|
||||
|
||||
this.parser = parser;
|
||||
|
||||
@@ -79,7 +82,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
@Override
|
||||
public Query doCreateQuery(JpaParametersParameterAccessor accessor) {
|
||||
|
||||
String sortedQueryString = QueryUtils.applySorting(query.getQueryString(), accessor.getSort(), query.getAlias());
|
||||
String sortedQueryString = QueryEnhancerFactory.forQuery(query) //
|
||||
.applySorting(accessor.getSort(), query.getAlias());
|
||||
ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
|
||||
|
||||
Query query = createJpaQuery(sortedQueryString, processor.getReturnedType());
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* A wrapper for a String representation of a query offering information about the query.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Diego Krupitza
|
||||
* @since 2.0.3
|
||||
*/
|
||||
interface DeclaredQuery {
|
||||
@@ -32,10 +33,11 @@ interface DeclaredQuery {
|
||||
* Creates a {@literal DeclaredQuery} from a query {@literal String}.
|
||||
*
|
||||
* @param query might be {@literal null} or empty.
|
||||
* @param nativeQuery is a given query is native or not
|
||||
* @return a {@literal DeclaredQuery} instance even for a {@literal null} or empty argument.
|
||||
*/
|
||||
static DeclaredQuery of(@Nullable String query) {
|
||||
return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query);
|
||||
static DeclaredQuery of(@Nullable String query, boolean nativeQuery) {
|
||||
return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query, nativeQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,4 +102,13 @@ interface DeclaredQuery {
|
||||
* @since 2.0.6
|
||||
*/
|
||||
boolean usesJdbcStyleParameters();
|
||||
|
||||
/**
|
||||
* Return whether the query is a native query of not.
|
||||
*
|
||||
* @return <code>true</code> if native query otherwise <code>false</code>
|
||||
*/
|
||||
default boolean isNativeQuery() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2022 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.data.domain.Sort;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The implementation of {@link QueryEnhancer} using {@link QueryUtils}.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
public class DefaultQueryEnhancer implements QueryEnhancer {
|
||||
|
||||
private final DeclaredQuery query;
|
||||
|
||||
public DefaultQueryEnhancer(DeclaredQuery query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExistsQueryString(String entityName, String countQueryPlaceHolder, Iterable<String> idAttributes) {
|
||||
return QueryUtils.getExistsQueryString(entityName, countQueryPlaceHolder, idAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString(String template, String entityName) {
|
||||
return QueryUtils.getQueryString(template, entityName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String applySorting(Sort sort, String alias) {
|
||||
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String detectAlias() {
|
||||
return QueryUtils.detectAlias(this.query.getQueryString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createCountQueryFor(String countProjection) {
|
||||
return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProjection() {
|
||||
return QueryUtils.getProjection(this.query.getQueryString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getJoinAliases() {
|
||||
return QueryUtils.getOuterJoinAliases(this.query.getQueryString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeclaredQuery getQuery() {
|
||||
return this.query;
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ class EmptyDeclaredQuery implements DeclaredQuery {
|
||||
|
||||
Assert.hasText(countQuery, "CountQuery must not be empty!");
|
||||
|
||||
return DeclaredQuery.of(countQuery);
|
||||
return DeclaredQuery.of(countQuery, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.util.Assert;
|
||||
* @author Oliver Gierke
|
||||
* @author Tom Hombergs
|
||||
* @author Michael J. Simons
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
class ExpressionBasedStringQuery extends StringQuery {
|
||||
|
||||
@@ -55,9 +56,11 @@ class ExpressionBasedStringQuery extends StringQuery {
|
||||
* @param query must not be {@literal null} or empty.
|
||||
* @param metadata must not be {@literal null}.
|
||||
* @param parser must not be {@literal null}.
|
||||
* @param nativeQuery is a given query is native or not
|
||||
*/
|
||||
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser) {
|
||||
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser));
|
||||
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser,
|
||||
boolean nativeQuery) {
|
||||
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,11 +69,12 @@ class ExpressionBasedStringQuery extends StringQuery {
|
||||
* @param query the original query. Must not be {@literal null}.
|
||||
* @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}.
|
||||
* @param parser Parser for resolving SpEL expressions. Must not be {@literal null}.
|
||||
* @param nativeQuery
|
||||
* @return A query supporting SpEL expressions.
|
||||
*/
|
||||
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata metadata,
|
||||
SpelExpressionParser parser) {
|
||||
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser);
|
||||
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata metadata, SpelExpressionParser parser,
|
||||
boolean nativeQuery) {
|
||||
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* Copyright 2022 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 net.sf.jsqlparser.JSQLParserException;
|
||||
import net.sf.jsqlparser.expression.Alias;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.Function;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.select.*;
|
||||
import net.sf.jsqlparser.util.SelectUtils;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.springframework.data.jpa.repository.query.JSqlParserUtils.*;
|
||||
import static org.springframework.data.jpa.repository.query.QueryUtils.checkSortExpression;
|
||||
|
||||
/**
|
||||
* The implementation of {@link QueryEnhancer} using JSqlParser.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
public class JSqlParserQueryEnhancer implements QueryEnhancer {
|
||||
|
||||
private static final String DEFAULT_TABLE_ALIAS = "x";
|
||||
|
||||
private final DeclaredQuery query;
|
||||
|
||||
/**
|
||||
* @param query the query we want to enhance. Must not be {@literal null}.
|
||||
*/
|
||||
public JSqlParserQueryEnhancer(DeclaredQuery query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExistsQueryString(String entityName, String countQueryPlaceHolder, Iterable<String> idAttributes) {
|
||||
final Table tableNameWithAlias = getTableWithAlias(entityName, DEFAULT_TABLE_ALIAS);
|
||||
Function jSqlCount = getJSqlCount(Collections.singletonList(countQueryPlaceHolder), false);
|
||||
|
||||
Select select = SelectUtils.buildSelectFromTableAndSelectItems(tableNameWithAlias,
|
||||
new SelectExpressionItem(jSqlCount));
|
||||
|
||||
PlainSelect selectBody = (PlainSelect) select.getSelectBody();
|
||||
|
||||
List<Expression> equalityExpressions = Streamable.of(idAttributes).stream() //
|
||||
.map(field -> {
|
||||
Expression tableNameField = new Column().withTable(tableNameWithAlias).withColumnName(field);
|
||||
Expression inputField = new Column(":".concat(field));
|
||||
return new EqualsTo(tableNameField, inputField);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
if (equalityExpressions.size() > 1) {
|
||||
AndExpression rootOfWhereClause = concatenateWithAndExpression(equalityExpressions);
|
||||
selectBody.setWhere(rootOfWhereClause);
|
||||
} else if (equalityExpressions.size() == 1) {
|
||||
selectBody.setWhere(equalityExpressions.get(0));
|
||||
}
|
||||
|
||||
return selectBody.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString(String template, String entityName) {
|
||||
Assert.hasText(entityName, "Entity name must not be null or empty!");
|
||||
return String.format(template, entityName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String applySorting(Sort sort, String alias) {
|
||||
String queryString = query.getQueryString();
|
||||
Assert.hasText(queryString, "Query must not be null or empty!");
|
||||
|
||||
if (sort.isUnsorted()) {
|
||||
return queryString;
|
||||
}
|
||||
|
||||
Select selectStatement = parseSelectStatement(queryString);
|
||||
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
|
||||
|
||||
final Set<String> joinAliases = getJoinAliases(selectBody);
|
||||
|
||||
final Set<String> selectionAliases = getSelectionAliases(selectBody);
|
||||
|
||||
List<OrderByElement> orderByElements = sort.stream() //
|
||||
.map(order -> getOrderClause(joinAliases, selectionAliases, alias, order)) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtils.isEmpty(selectBody.getOrderByElements())) {
|
||||
selectBody.setOrderByElements(new ArrayList<>());
|
||||
}
|
||||
|
||||
selectBody.getOrderByElements().addAll(orderByElements);
|
||||
|
||||
return selectBody.toString();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the aliases used inside the selection part in the query.
|
||||
*
|
||||
* @param selectBody a {@link PlainSelect} containing a query. Must not be {@literal null}.
|
||||
* @return a {@literal Set} containing all found aliases. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
private Set<String> getSelectionAliases(PlainSelect selectBody) {
|
||||
|
||||
if (CollectionUtils.isEmpty(selectBody.getSelectItems())) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
return selectBody.getSelectItems().stream() //
|
||||
.filter(SelectExpressionItem.class::isInstance) //
|
||||
.map(item -> ((SelectExpressionItem) item).getAlias()) //
|
||||
.filter(Objects::nonNull) //
|
||||
.map(Alias::getName) //
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the aliases used inside the selection part in the query.
|
||||
*
|
||||
* @return a {@literal Set} containing all found aliases. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
Set<String> getSelectionAliases() {
|
||||
Select selectStatement = parseSelectStatement(this.query.getQueryString());
|
||||
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
|
||||
return this.getSelectionAliases(selectBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the aliases used for {@code join}s.
|
||||
*
|
||||
* @param query a query string to extract the aliases of joins from. Must not be {@literal null}.
|
||||
* @return a {@literal Set} of aliases used in the query. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
private Set<String> getJoinAliases(String query) {
|
||||
return getJoinAliases((PlainSelect) parseSelectStatement(query).getSelectBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the aliases used for {@code join}s.
|
||||
*
|
||||
* @param selectBody the selection body to extract the aliases of joins from. Must not be {@literal null}.
|
||||
* @return a {@literal Set} of aliases used in the query. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
private Set<String> getJoinAliases(PlainSelect selectBody) {
|
||||
|
||||
if (CollectionUtils.isEmpty(selectBody.getJoins())) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
return selectBody.getJoins().stream() //
|
||||
.map(join -> join.getRightItem().getAlias()) //
|
||||
.filter(Objects::nonNull) //
|
||||
.map(Alias::getName) //
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the order clause for the given {@link Sort.Order}. Will prefix the clause with the given alias if the
|
||||
* referenced property refers to a join alias, i.e. starts with {@code $alias.}.
|
||||
*
|
||||
* @param joinAliases the join aliases of the original query. Must not be {@literal null}.
|
||||
* @param alias the alias for the root entity. May be {@literal null}.
|
||||
* @param order the order object to build the clause for. Must not be {@literal null}.
|
||||
* @return a {@link OrderByElement} containing an order clause. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
private OrderByElement getOrderClause(final Set<String> joinAliases, final Set<String> selectionAliases,
|
||||
final String alias, final Sort.Order order) {
|
||||
|
||||
final OrderByElement orderByElement = new OrderByElement();
|
||||
orderByElement.setAsc(order.getDirection().isAscending());
|
||||
orderByElement.setAscDescPresent(true);
|
||||
|
||||
final String property = order.getProperty();
|
||||
|
||||
checkSortExpression(order);
|
||||
|
||||
if (selectionAliases.contains(property)) {
|
||||
Expression orderExpression = order.isIgnoreCase() ? getJSqlLower(property) : new Column(property);
|
||||
|
||||
orderByElement.setExpression(orderExpression);
|
||||
return orderByElement;
|
||||
}
|
||||
|
||||
boolean qualifyReference = joinAliases //
|
||||
.parallelStream() //
|
||||
.map(joinAlias -> joinAlias.concat(".")) //
|
||||
.noneMatch(property::startsWith);
|
||||
|
||||
boolean functionIndicator = property.contains("(");
|
||||
|
||||
String reference = qualifyReference && !functionIndicator && StringUtils.hasText(alias)
|
||||
? String.format("%s.%s", alias, property)
|
||||
: property;
|
||||
Expression orderExpression = order.isIgnoreCase() ? getJSqlLower(reference) : new Column(reference);
|
||||
orderByElement.setExpression(orderExpression);
|
||||
return orderByElement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String detectAlias() {
|
||||
return detectAlias(this.query.getQueryString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the alias for the entity to be retrieved from the given JPA query. Note that you only provide valid Query
|
||||
* strings. Things such as <code>from User u</code> will throw an {@link IllegalArgumentException}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return Might return {@literal null}.
|
||||
*/
|
||||
private String detectAlias(String query) {
|
||||
Select selectStatement = parseSelectStatement(query);
|
||||
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
|
||||
return detectAlias(selectBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the alias for the entity to be retrieved from the given {@link PlainSelect}. Note that you only provide
|
||||
* valid Query strings. Things such as <code>from User u</code> will throw an {@link IllegalArgumentException}.
|
||||
*
|
||||
* @param selectBody must not be {@literal null}.
|
||||
* @return Might return {@literal null}.
|
||||
*/
|
||||
private static String detectAlias(PlainSelect selectBody) {
|
||||
Alias alias = selectBody.getFromItem().getAlias();
|
||||
return alias == null ? null : alias.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createCountQueryFor(String countProjection) {
|
||||
|
||||
Assert.hasText(this.query.getQueryString(), "OriginalQuery must not be null or empty!");
|
||||
|
||||
Select selectStatement = parseSelectStatement(this.query.getQueryString());
|
||||
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
|
||||
|
||||
// remove order by
|
||||
selectBody.setOrderByElements(null);
|
||||
|
||||
if (StringUtils.hasText(countProjection)) {
|
||||
Function jSqlCount = getJSqlCount(Collections.singletonList(countProjection), false);
|
||||
selectBody.setSelectItems(Collections.singletonList(new SelectExpressionItem(jSqlCount)));
|
||||
return selectBody.toString();
|
||||
}
|
||||
|
||||
boolean distinct = selectBody.getDistinct() != null;
|
||||
selectBody.setDistinct(null); // reset possible distinct
|
||||
|
||||
String tableAlias = detectAlias(selectBody);
|
||||
|
||||
// is never null
|
||||
List<SelectItem> selectItems = selectBody.getSelectItems();
|
||||
|
||||
if (onlyASingleColumnProjection(selectItems)) {
|
||||
SelectExpressionItem singleProjection = (SelectExpressionItem) selectItems.get(0);
|
||||
|
||||
Column column = (Column) singleProjection.getExpression();
|
||||
String countProp = column.getFullyQualifiedName();
|
||||
|
||||
Function jSqlCount = getJSqlCount(Collections.singletonList(countProp), distinct);
|
||||
selectBody.setSelectItems(Collections.singletonList(new SelectExpressionItem(jSqlCount)));
|
||||
return selectBody.toString();
|
||||
}
|
||||
|
||||
String countProp = tableAlias == null ? "*" : tableAlias;
|
||||
|
||||
Function jSqlCount = getJSqlCount(Collections.singletonList(countProp), distinct);
|
||||
selectBody.setSelectItems(Collections.singletonList(new SelectExpressionItem(jSqlCount)));
|
||||
|
||||
return selectBody.toString();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProjection() {
|
||||
Assert.hasText(query.getQueryString(), "Query must not be null or empty!");
|
||||
|
||||
Select selectStatement = parseSelectStatement(query.getQueryString());
|
||||
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
|
||||
|
||||
return selectBody.getSelectItems() //
|
||||
.stream() //
|
||||
.map(Object::toString) //
|
||||
.collect(Collectors.joining(", ")).trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getJoinAliases() {
|
||||
return this.getJoinAliases(this.query.getQueryString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a query string with JSqlParser.
|
||||
*
|
||||
* @param query the query to parse
|
||||
* @return the parsed query
|
||||
*/
|
||||
private static Select parseSelectStatement(String query) {
|
||||
try {
|
||||
return (Select) CCJSqlParserUtil.parse(query);
|
||||
} catch (JSQLParserException e) {
|
||||
throw new IllegalArgumentException("The query you provided is not a valid SQL Query!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given projection only contains a single column definition (aka without functions, etc)
|
||||
*
|
||||
* @param projection the projection to analyse
|
||||
* @return <code>true</code> when the projection only contains a single column definition otherwise <code>false</code>
|
||||
*/
|
||||
private boolean onlyASingleColumnProjection(List<SelectItem> projection) {
|
||||
// this is unfortunately the only way to check without any hacky & hard string regex magic
|
||||
return projection.size() == 1 && projection.get(0) instanceof SelectExpressionItem
|
||||
&& (((SelectExpressionItem) projection.get(0)).getExpression()) instanceof Column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeclaredQuery getQuery() {
|
||||
return this.query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2022 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 net.sf.jsqlparser.expression.Alias;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.Function;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A utility class for JSqlParser.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
public final class JSqlParserUtils {
|
||||
|
||||
private JSqlParserUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a JSqlParser table from an entity name and an optional alias name
|
||||
*
|
||||
* @param entityName the name of the table
|
||||
* @param alias the optional alias. Might be {@literal null} or empty
|
||||
* @return the newly generated table
|
||||
*/
|
||||
public static Table getTableWithAlias(String entityName, String alias) {
|
||||
Table table = new Table(entityName);
|
||||
return StringUtils.hasText(alias) ? table.withAlias(new Alias(alias)) : table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates a list of expression with <code>AND</code>.
|
||||
*
|
||||
* @param expressions the list of expressions to concatenate. Has to be non empty and with size >= 2
|
||||
* @return the root of the concatenated expression
|
||||
*/
|
||||
public static AndExpression concatenateWithAndExpression(List<Expression> expressions) {
|
||||
|
||||
if (CollectionUtils.isEmpty(expressions) || expressions.size() == 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"The list of expression has to be at least of length 2! Otherwise it is not possible to concatinate with an");
|
||||
}
|
||||
|
||||
AndExpression rootAndExpression = new AndExpression();
|
||||
AndExpression currentLocation = rootAndExpression;
|
||||
|
||||
// traverse the list with looking 1 element ahead
|
||||
for (int i = 0; i < expressions.size(); i++) {
|
||||
Expression currentExpression = expressions.get(i);
|
||||
if (currentLocation.getLeftExpression() == null) {
|
||||
currentLocation.setLeftExpression(currentExpression);
|
||||
} else if (currentLocation.getRightExpression() == null && i == expressions.size() - 1) {
|
||||
currentLocation.setRightExpression(currentExpression);
|
||||
} else {
|
||||
AndExpression nextAndExpression = new AndExpression();
|
||||
nextAndExpression.setLeftExpression(currentExpression);
|
||||
|
||||
currentLocation.setRightExpression(nextAndExpression);
|
||||
currentLocation = (AndExpression) currentLocation.getRightExpression();
|
||||
}
|
||||
}
|
||||
|
||||
return rootAndExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a count function call, based on the {@code countFields}.
|
||||
*
|
||||
* @param countFields the non-empty list of fields that are used for counting
|
||||
* @param distinct if it should be a distinct count
|
||||
* @return the generated count function call
|
||||
*/
|
||||
public static Function getJSqlCount(final List<String> countFields, final boolean distinct) {
|
||||
List<Expression> countColumns = countFields //
|
||||
.stream() //
|
||||
.map(Column::new) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
ExpressionList countExpression = new ExpressionList(countColumns);
|
||||
return new Function() //
|
||||
.withName("count") //
|
||||
.withParameters(countExpression) //
|
||||
.withDistinct(distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a lower function call, based on the {@code column}.
|
||||
*
|
||||
* @param column the non-empty column to use as param for lower
|
||||
* @return the generated lower function call
|
||||
*/
|
||||
public static Function getJSqlLower(String column) {
|
||||
List<Expression> expressions = Collections.singletonList(new Column(column));
|
||||
ExpressionList lowerParamExpression = new ExpressionList(expressions);
|
||||
return new Function() //
|
||||
.withName("lower") //
|
||||
.withParameters(lowerParamExpression);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -166,10 +166,8 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
|
||||
|
||||
Class<?> typeToRead = returnedType.getReturnedType();
|
||||
|
||||
query = typeToRead.isInterface()
|
||||
? query.multiselect(selections)
|
||||
: query.select((Selection) builder.construct(typeToRead,
|
||||
selections.toArray(new Selection[0])));
|
||||
query = typeToRead.isInterface() ? query.multiselect(selections)
|
||||
: query.select((Selection) builder.construct(typeToRead, selections.toArray(new Selection[0])));
|
||||
|
||||
} else if (tree.isExistsProjection()) {
|
||||
|
||||
@@ -241,83 +239,83 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
|
||||
Type type = part.getType();
|
||||
|
||||
switch (type) {
|
||||
case BETWEEN:
|
||||
ParameterMetadata<Comparable> first = provider.next(part);
|
||||
ParameterMetadata<Comparable> second = provider.next(part);
|
||||
return builder.between(getComparablePath(root, part), first.getExpression(), second.getExpression());
|
||||
case AFTER:
|
||||
case GREATER_THAN:
|
||||
return builder.greaterThan(getComparablePath(root, part),
|
||||
provider.next(part, Comparable.class).getExpression());
|
||||
case GREATER_THAN_EQUAL:
|
||||
return builder.greaterThanOrEqualTo(getComparablePath(root, part),
|
||||
provider.next(part, Comparable.class).getExpression());
|
||||
case BEFORE:
|
||||
case LESS_THAN:
|
||||
return builder.lessThan(getComparablePath(root, part), provider.next(part, Comparable.class).getExpression());
|
||||
case LESS_THAN_EQUAL:
|
||||
return builder.lessThanOrEqualTo(getComparablePath(root, part),
|
||||
provider.next(part, Comparable.class).getExpression());
|
||||
case IS_NULL:
|
||||
return getTypedPath(root, part).isNull();
|
||||
case IS_NOT_NULL:
|
||||
return getTypedPath(root, part).isNotNull();
|
||||
case NOT_IN:
|
||||
// cast required for eclipselink workaround, see DATAJPA-433
|
||||
return upperIfIgnoreCase(getTypedPath(root, part))
|
||||
.in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression()).not();
|
||||
case IN:
|
||||
// cast required for eclipselink workaround, see DATAJPA-433
|
||||
return upperIfIgnoreCase(getTypedPath(root, part))
|
||||
.in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression());
|
||||
case STARTING_WITH:
|
||||
case ENDING_WITH:
|
||||
case CONTAINING:
|
||||
case NOT_CONTAINING:
|
||||
case BETWEEN:
|
||||
ParameterMetadata<Comparable> first = provider.next(part);
|
||||
ParameterMetadata<Comparable> second = provider.next(part);
|
||||
return builder.between(getComparablePath(root, part), first.getExpression(), second.getExpression());
|
||||
case AFTER:
|
||||
case GREATER_THAN:
|
||||
return builder.greaterThan(getComparablePath(root, part),
|
||||
provider.next(part, Comparable.class).getExpression());
|
||||
case GREATER_THAN_EQUAL:
|
||||
return builder.greaterThanOrEqualTo(getComparablePath(root, part),
|
||||
provider.next(part, Comparable.class).getExpression());
|
||||
case BEFORE:
|
||||
case LESS_THAN:
|
||||
return builder.lessThan(getComparablePath(root, part), provider.next(part, Comparable.class).getExpression());
|
||||
case LESS_THAN_EQUAL:
|
||||
return builder.lessThanOrEqualTo(getComparablePath(root, part),
|
||||
provider.next(part, Comparable.class).getExpression());
|
||||
case IS_NULL:
|
||||
return getTypedPath(root, part).isNull();
|
||||
case IS_NOT_NULL:
|
||||
return getTypedPath(root, part).isNotNull();
|
||||
case NOT_IN:
|
||||
// cast required for eclipselink workaround, see DATAJPA-433
|
||||
return upperIfIgnoreCase(getTypedPath(root, part))
|
||||
.in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression()).not();
|
||||
case IN:
|
||||
// cast required for eclipselink workaround, see DATAJPA-433
|
||||
return upperIfIgnoreCase(getTypedPath(root, part))
|
||||
.in((Expression<Collection<?>>) provider.next(part, Collection.class).getExpression());
|
||||
case STARTING_WITH:
|
||||
case ENDING_WITH:
|
||||
case CONTAINING:
|
||||
case NOT_CONTAINING:
|
||||
|
||||
if (property.getLeafProperty().isCollection()) {
|
||||
if (property.getLeafProperty().isCollection()) {
|
||||
|
||||
Expression<Collection<Object>> propertyExpression = traversePath(root, property);
|
||||
ParameterExpression<Object> parameterExpression = provider.next(part).getExpression();
|
||||
Expression<Collection<Object>> propertyExpression = traversePath(root, property);
|
||||
ParameterExpression<Object> parameterExpression = provider.next(part).getExpression();
|
||||
|
||||
// Can't just call .not() in case of negation as EclipseLink chokes on that.
|
||||
return type.equals(NOT_CONTAINING) ? isNotMember(builder, parameterExpression, propertyExpression)
|
||||
: isMember(builder, parameterExpression, propertyExpression);
|
||||
}
|
||||
// Can't just call .not() in case of negation as EclipseLink chokes on that.
|
||||
return type.equals(NOT_CONTAINING) ? isNotMember(builder, parameterExpression, propertyExpression)
|
||||
: isMember(builder, parameterExpression, propertyExpression);
|
||||
}
|
||||
|
||||
case LIKE:
|
||||
case NOT_LIKE:
|
||||
Expression<String> stringPath = getTypedPath(root, part);
|
||||
Expression<String> propertyExpression = upperIfIgnoreCase(stringPath);
|
||||
Expression<String> parameterExpression = upperIfIgnoreCase(provider.next(part, String.class).getExpression());
|
||||
Predicate like = builder.like(propertyExpression, parameterExpression, escape.getEscapeCharacter());
|
||||
return type.equals(NOT_LIKE) || type.equals(NOT_CONTAINING) ? like.not() : like;
|
||||
case TRUE:
|
||||
Expression<Boolean> truePath = getTypedPath(root, part);
|
||||
return builder.isTrue(truePath);
|
||||
case FALSE:
|
||||
Expression<Boolean> falsePath = getTypedPath(root, part);
|
||||
return builder.isFalse(falsePath);
|
||||
case SIMPLE_PROPERTY:
|
||||
ParameterMetadata<Object> expression = provider.next(part);
|
||||
Expression<Object> path = getTypedPath(root, part);
|
||||
return expression.isIsNullParameter() ? path.isNull()
|
||||
: builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression()));
|
||||
case NEGATING_SIMPLE_PROPERTY:
|
||||
return builder.notEqual(upperIfIgnoreCase(getTypedPath(root, part)),
|
||||
upperIfIgnoreCase(provider.next(part).getExpression()));
|
||||
case IS_EMPTY:
|
||||
case IS_NOT_EMPTY:
|
||||
case LIKE:
|
||||
case NOT_LIKE:
|
||||
Expression<String> stringPath = getTypedPath(root, part);
|
||||
Expression<String> propertyExpression = upperIfIgnoreCase(stringPath);
|
||||
Expression<String> parameterExpression = upperIfIgnoreCase(provider.next(part, String.class).getExpression());
|
||||
Predicate like = builder.like(propertyExpression, parameterExpression, escape.getEscapeCharacter());
|
||||
return type.equals(NOT_LIKE) || type.equals(NOT_CONTAINING) ? like.not() : like;
|
||||
case TRUE:
|
||||
Expression<Boolean> truePath = getTypedPath(root, part);
|
||||
return builder.isTrue(truePath);
|
||||
case FALSE:
|
||||
Expression<Boolean> falsePath = getTypedPath(root, part);
|
||||
return builder.isFalse(falsePath);
|
||||
case SIMPLE_PROPERTY:
|
||||
ParameterMetadata<Object> expression = provider.next(part);
|
||||
Expression<Object> path = getTypedPath(root, part);
|
||||
return expression.isIsNullParameter() ? path.isNull()
|
||||
: builder.equal(upperIfIgnoreCase(path), upperIfIgnoreCase(expression.getExpression()));
|
||||
case NEGATING_SIMPLE_PROPERTY:
|
||||
return builder.notEqual(upperIfIgnoreCase(getTypedPath(root, part)),
|
||||
upperIfIgnoreCase(provider.next(part).getExpression()));
|
||||
case IS_EMPTY:
|
||||
case IS_NOT_EMPTY:
|
||||
|
||||
if (!property.getLeafProperty().isCollection()) {
|
||||
throw new IllegalArgumentException("IsEmpty / IsNotEmpty can only be used on collection properties!");
|
||||
}
|
||||
if (!property.getLeafProperty().isCollection()) {
|
||||
throw new IllegalArgumentException("IsEmpty / IsNotEmpty can only be used on collection properties!");
|
||||
}
|
||||
|
||||
Expression<Collection<Object>> collectionPath = traversePath(root, property);
|
||||
return type.equals(IS_NOT_EMPTY) ? builder.isNotEmpty(collectionPath) : builder.isEmpty(collectionPath);
|
||||
Expression<Collection<Object>> collectionPath = traversePath(root, property);
|
||||
return type.equals(IS_NOT_EMPTY) ? builder.isNotEmpty(collectionPath) : builder.isEmpty(collectionPath);
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported keyword " + type);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported keyword " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,22 +340,22 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
|
||||
|
||||
switch (part.shouldIgnoreCase()) {
|
||||
|
||||
case ALWAYS:
|
||||
case ALWAYS:
|
||||
|
||||
Assert.state(canUpperCase(expression), "Unable to ignore case of " + expression.getJavaType().getName()
|
||||
+ " types, the property '" + part.getProperty().getSegment() + "' must reference a String");
|
||||
Assert.state(canUpperCase(expression), "Unable to ignore case of " + expression.getJavaType().getName()
|
||||
+ " types, the property '" + part.getProperty().getSegment() + "' must reference a String");
|
||||
return (Expression<T>) builder.upper((Expression<String>) expression);
|
||||
|
||||
case WHEN_POSSIBLE:
|
||||
|
||||
if (canUpperCase(expression)) {
|
||||
return (Expression<T>) builder.upper((Expression<String>) expression);
|
||||
}
|
||||
|
||||
case WHEN_POSSIBLE:
|
||||
case NEVER:
|
||||
default:
|
||||
|
||||
if (canUpperCase(expression)) {
|
||||
return (Expression<T>) builder.upper((Expression<String>) expression);
|
||||
}
|
||||
|
||||
case NEVER:
|
||||
default:
|
||||
|
||||
return (Expression<T>) expression;
|
||||
return (Expression<T>) expression;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
|
||||
String annotatedQuery = getAnnotatedQuery();
|
||||
|
||||
if (!DeclaredQuery.of(annotatedQuery).hasNamedParameter()) {
|
||||
if (!DeclaredQuery.of(annotatedQuery, this.isNativeQuery.get()).hasNamedParameter()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ final class NamedQuery extends AbstractJpaQuery {
|
||||
Query query = em.createNamedQuery(queryName);
|
||||
String queryString = extractor.extractQueryString(query);
|
||||
|
||||
this.declaredQuery = DeclaredQuery.of(queryString);
|
||||
this.declaredQuery = DeclaredQuery.of(queryString, false);
|
||||
|
||||
boolean weNeedToCreateCountQuery = !namedCountQueryIsPresent && method.getParameters().hasPageableParameter();
|
||||
boolean cantExtractQuery = !this.extractor.canExtractQuery();
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2022 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.Set;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* This interface describes the API for enhancing a given Query.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
public interface QueryEnhancer {
|
||||
|
||||
/**
|
||||
* Returns the query string to execute an exists query for the given id attributes.
|
||||
*
|
||||
* @param entityName the name of the entity to create the query for, must not be {@literal null}.
|
||||
* @param countQueryPlaceHolder the placeholder for the count clause, must not be {@literal null}.
|
||||
* @param idAttributes the id attributes for the entity, must not be {@literal null}.
|
||||
*/
|
||||
String getExistsQueryString(String entityName, String countQueryPlaceHolder, Iterable<String> idAttributes);
|
||||
|
||||
/**
|
||||
* Returns the query string for the given class name.
|
||||
*
|
||||
* @param template must not be {@literal null}.
|
||||
* @param entityName must not be {@literal null}.
|
||||
* @return the template with placeholders replaced by the {@literal entityName}. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
String getQueryString(String template, String entityName);
|
||||
|
||||
/**
|
||||
* Adds {@literal order by} clause to the JPQL query. Uses the first alias to bind the sorting property to.
|
||||
*
|
||||
* @param sort the sort specification to apply.
|
||||
* @return the modified query string.
|
||||
*/
|
||||
default String applySorting(Sort sort) {
|
||||
return applySorting(sort, detectAlias());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@literal order by} clause to the JPQL query.
|
||||
*
|
||||
* @param sort the sort specification to apply.
|
||||
* @param alias the alias to be used in the order by clause. May be {@literal null} or empty.
|
||||
* @return the modified query string.
|
||||
*/
|
||||
String applySorting(Sort sort, @Nullable String alias);
|
||||
|
||||
/**
|
||||
* Resolves the alias for the entity to be retrieved from the given JPA query.
|
||||
*
|
||||
* @return Might return {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
String detectAlias();
|
||||
|
||||
/**
|
||||
* Creates a count projected query from the given original query.
|
||||
*
|
||||
* @return Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
default String createCountQueryFor() {
|
||||
return createCountQueryFor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a count projected query from the given original query using the provided <code>countProjection</code>.
|
||||
*
|
||||
* @param countProjection may be {@literal null}.
|
||||
* @return a query String to be used a count query for pagination. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
String createCountQueryFor(@Nullable String countProjection);
|
||||
|
||||
/**
|
||||
* Returns whether the given JPQL query contains a constructor expression.
|
||||
*
|
||||
* @return whether the given JPQL query contains a constructor expression.
|
||||
*/
|
||||
default boolean hasConstructorExpression() {
|
||||
return QueryUtils.hasConstructorExpression(getQuery().getQueryString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the projection part of the query, i.e. everything between {@code select} and {@code from}.
|
||||
*
|
||||
* @return the projection part of the query.
|
||||
*/
|
||||
String getProjection();
|
||||
|
||||
Set<String> getJoinAliases();
|
||||
|
||||
/**
|
||||
* Gets the query we want to use for enhancements.
|
||||
*
|
||||
* @return non null {@link DeclaredQuery} that wraps the query
|
||||
*/
|
||||
DeclaredQuery getQuery();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2022 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.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Encapsulates different strategies for the creation of a {@link QueryEnhancer} from a {@link DeclaredQuery}.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
public final class QueryEnhancerFactory {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(QueryEnhancerFactory.class);
|
||||
|
||||
private static final boolean JSQLPARSER_IN_CLASSPATH = isJSqlParserInClassPath();
|
||||
|
||||
private QueryEnhancerFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link QueryEnhancer} for the given {@link DeclaredQuery}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return an implementation of {@link QueryEnhancer} that suits the query the most
|
||||
*/
|
||||
public static QueryEnhancer forQuery(DeclaredQuery query) {
|
||||
if (qualifiesForJSqlParserUsage(query)) {
|
||||
return new JSqlParserQueryEnhancer(query);
|
||||
} else {
|
||||
return new DefaultQueryEnhancer(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given query can be process with the JSqlParser under the condition that the parser is in the classpath.
|
||||
*
|
||||
* @param query the query we want to check
|
||||
* @return <code>true</code> if JSqlParser is in the classpath and the query is classified as a native query otherwise
|
||||
* <code>false</code>
|
||||
*/
|
||||
private static boolean qualifiesForJSqlParserUsage(DeclaredQuery query) {
|
||||
return JSQLPARSER_IN_CLASSPATH && query.isNativeQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether JSqlParser is in classpath or not.
|
||||
*
|
||||
* @return <code>true</code> when in classpath otherwise <code>false</code>
|
||||
*/
|
||||
private static boolean isJSqlParserInClassPath() {
|
||||
try {
|
||||
Class.forName("net.sf.jsqlparser.parser.JSqlParser", false, QueryEnhancerFactory.class.getClassLoader());
|
||||
LOG.info("JSqlParser is in classpath. If applicable JSqlParser will be used.");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -49,7 +49,8 @@ interface QueryParameterSetter {
|
||||
void setParameter(BindableQuery query, JpaParametersParameterAccessor accessor, ErrorHandling errorHandling);
|
||||
|
||||
/** Noop implementation */
|
||||
QueryParameterSetter NOOP = (query, values, errorHandling) -> {};
|
||||
QueryParameterSetter NOOP = (query, values, errorHandling) -> {
|
||||
};
|
||||
|
||||
/**
|
||||
* {@link QueryParameterSetter} for named or indexed parameters that might have a {@link TemporalType} specified.
|
||||
|
||||
@@ -56,7 +56,7 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Simple utility class to create JPA queries.
|
||||
* Simple utility class to create JPA queries using the default implementation of a custom parser.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Kevin Raymond
|
||||
@@ -74,6 +74,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Andriy Redko
|
||||
* @author Peter Großmann
|
||||
* @author Greg Turnquist
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
public abstract class QueryUtils {
|
||||
|
||||
@@ -282,7 +283,7 @@ public abstract class QueryUtils {
|
||||
* @param joinAliases the join aliases of the original query. Must not be {@literal null}.
|
||||
* @param alias the alias for the root entity. May be {@literal null}.
|
||||
* @param order the order object to build the clause for. Must not be {@literal null}.
|
||||
* @return a String containing a order clause. Guaranteed to be not {@literal null}.
|
||||
* @return a String containing an order clause. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
private static String getOrderClause(Set<String> joinAliases, Set<String> selectionAlias, @Nullable String alias,
|
||||
Order order) {
|
||||
@@ -811,7 +812,7 @@ public abstract class QueryUtils {
|
||||
*
|
||||
* @param order
|
||||
*/
|
||||
private static void checkSortExpression(Order order) {
|
||||
static void checkSortExpression(Order order) {
|
||||
|
||||
if (order instanceof JpaOrder && ((JpaOrder) order).isUnsafe()) {
|
||||
return;
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Oliver Wehrens
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
class StringQuery implements DeclaredQuery {
|
||||
|
||||
@@ -55,6 +56,8 @@ class StringQuery implements DeclaredQuery {
|
||||
private final boolean hasConstructorExpression;
|
||||
private final boolean containsPageableInSpel;
|
||||
private final boolean usesJdbcStyleParameters;
|
||||
private final boolean isNative;
|
||||
private final QueryEnhancer queryEnhancer;
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringQuery} from the given JPQL query.
|
||||
@@ -62,10 +65,11 @@ class StringQuery implements DeclaredQuery {
|
||||
* @param query must not be {@literal null} or empty.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
StringQuery(String query) {
|
||||
StringQuery(String query, boolean isNative) {
|
||||
|
||||
Assert.hasText(query, "Query must not be null or empty!");
|
||||
|
||||
this.isNative = isNative;
|
||||
this.bindings = new ArrayList<>();
|
||||
this.containsPageableInSpel = query.contains("#pageable");
|
||||
|
||||
@@ -74,8 +78,10 @@ class StringQuery implements DeclaredQuery {
|
||||
this.bindings, queryMeta);
|
||||
|
||||
this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters;
|
||||
this.alias = QueryUtils.detectAlias(query);
|
||||
this.hasConstructorExpression = QueryUtils.hasConstructorExpression(query);
|
||||
|
||||
this.queryEnhancer = QueryEnhancerFactory.forQuery(this);
|
||||
this.alias = this.queryEnhancer.detectAlias();
|
||||
this.hasConstructorExpression = this.queryEnhancer.hasConstructorExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +92,7 @@ class StringQuery implements DeclaredQuery {
|
||||
}
|
||||
|
||||
String getProjection() {
|
||||
return QueryUtils.getProjection(query);
|
||||
return this.queryEnhancer.getProjection();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,8 +104,9 @@ class StringQuery implements DeclaredQuery {
|
||||
@SuppressWarnings("deprecation")
|
||||
public DeclaredQuery deriveCountQuery(@Nullable String countQuery, @Nullable String countQueryProjection) {
|
||||
|
||||
return DeclaredQuery
|
||||
.of(countQuery != null ? countQuery : QueryUtils.createCountQueryFor(query, countQueryProjection));
|
||||
return DeclaredQuery.of(
|
||||
countQuery != null ? countQuery : this.queryEnhancer.createCountQueryFor(countQueryProjection), //
|
||||
this.isNative);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,6 +145,11 @@ class StringQuery implements DeclaredQuery {
|
||||
return containsPageableInSpel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNativeQuery() {
|
||||
return isNative;
|
||||
}
|
||||
|
||||
/**
|
||||
* A parser that extracts the parameter bindings from a given query string.
|
||||
*
|
||||
@@ -227,7 +239,8 @@ class StringQuery implements DeclaredQuery {
|
||||
Integer parameterIndex = getParameterIndex(parameterIndexString);
|
||||
|
||||
String typeSource = matcher.group(COMPARISION_TYPE_GROUP);
|
||||
Assert.isTrue(parameterIndexString != null || parameterName != null, () -> String.format("We need either a name or an index! Offending query string: %s", query));
|
||||
Assert.isTrue(parameterIndexString != null || parameterName != null,
|
||||
() -> String.format("We need either a name or an index! Offending query string: %s", query));
|
||||
String expression = spelExtractor.getParameter(parameterName == null ? parameterIndexString : parameterName);
|
||||
String replacement = null;
|
||||
|
||||
@@ -246,36 +259,36 @@ class StringQuery implements DeclaredQuery {
|
||||
|
||||
switch (ParameterBindingType.of(typeSource)) {
|
||||
|
||||
case LIKE:
|
||||
case LIKE:
|
||||
|
||||
Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2));
|
||||
replacement = matcher.group(3);
|
||||
Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2));
|
||||
replacement = matcher.group(3);
|
||||
|
||||
if (parameterIndex != null) {
|
||||
checkAndRegister(new LikeParameterBinding(parameterIndex, likeType, expression), bindings);
|
||||
} else {
|
||||
checkAndRegister(new LikeParameterBinding(parameterName, likeType, expression), bindings);
|
||||
if (parameterIndex != null) {
|
||||
checkAndRegister(new LikeParameterBinding(parameterIndex, likeType, expression), bindings);
|
||||
} else {
|
||||
checkAndRegister(new LikeParameterBinding(parameterName, likeType, expression), bindings);
|
||||
|
||||
replacement = ":" + parameterName;
|
||||
}
|
||||
replacement = ":" + parameterName;
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
case IN:
|
||||
case IN:
|
||||
|
||||
if (parameterIndex != null) {
|
||||
checkAndRegister(new InParameterBinding(parameterIndex, expression), bindings);
|
||||
} else {
|
||||
checkAndRegister(new InParameterBinding(parameterName, expression), bindings);
|
||||
}
|
||||
if (parameterIndex != null) {
|
||||
checkAndRegister(new InParameterBinding(parameterIndex, expression), bindings);
|
||||
} else {
|
||||
checkAndRegister(new InParameterBinding(parameterName, expression), bindings);
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
case AS_IS: // fall-through we don't need a special parameter binding for the given parameter.
|
||||
default:
|
||||
case AS_IS: // fall-through we don't need a special parameter binding for the given parameter.
|
||||
default:
|
||||
|
||||
bindings.add(parameterIndex != null ? new ParameterBinding(null, parameterIndex, expression)
|
||||
: new ParameterBinding(parameterName, null, expression));
|
||||
bindings.add(parameterIndex != null ? new ParameterBinding(null, parameterIndex, expression)
|
||||
: new ParameterBinding(parameterName, null, expression));
|
||||
}
|
||||
|
||||
if (replacement != null) {
|
||||
|
||||
@@ -103,10 +103,12 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Transactional
|
||||
public class UserRepositoryTests {
|
||||
|
||||
@PersistenceContext EntityManager em;
|
||||
@PersistenceContext
|
||||
EntityManager em;
|
||||
|
||||
// CUT
|
||||
@Autowired UserRepository repository;
|
||||
@Autowired
|
||||
UserRepository repository;
|
||||
|
||||
// Test fixture
|
||||
private User firstUser;
|
||||
|
||||
@@ -0,0 +1,530 @@
|
||||
/*
|
||||
* Copyright 2008-2022 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 static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.jpa.domain.JpaSort;
|
||||
|
||||
/**
|
||||
* Unit test for {@link QueryUtils}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Komi Innocent
|
||||
* @author Christoph Strobl
|
||||
* @author Jens Schauder
|
||||
* @author Florian Lüdiger
|
||||
* @author Grégoire Druant
|
||||
* @author Mohammad Hewedy
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
class DefaultQueryUtilsUnitTests {
|
||||
|
||||
private static final String QUERY = "select u from User u";
|
||||
private static final String FQ_QUERY = "select u from org.acme.domain.User$Foo_Bar u";
|
||||
private static final String SIMPLE_QUERY = "from User u";
|
||||
private static final String COUNT_QUERY = "select count(u) from User u";
|
||||
|
||||
private static final String QUERY_WITH_AS = "select u from User as u where u.username = ?";
|
||||
|
||||
@Test
|
||||
void createsCountQueryCorrectly() {
|
||||
assertCountQuery(QUERY, COUNT_QUERY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueriesCorrectlyForCapitalLetterJPQL() {
|
||||
|
||||
assertCountQuery("FROM User u WHERE u.foo.bar = ?", "select count(u) FROM User u WHERE u.foo.bar = ?");
|
||||
|
||||
assertCountQuery("SELECT u FROM User u where u.foo.bar = ?", "select count(u) FROM User u where u.foo.bar = ?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForDistinctQueries() {
|
||||
|
||||
assertCountQuery("select distinct u from User u where u.foo = ?",
|
||||
"select count(distinct u) from User u where u.foo = ?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForConstructorQueries() {
|
||||
|
||||
assertCountQuery("select distinct new User(u.name) from User u where u.foo = ?",
|
||||
"select count(distinct u) from User u where u.foo = ?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForJoins() {
|
||||
|
||||
assertCountQuery("select distinct new User(u.name) from User u left outer join u.roles r WHERE r = ?",
|
||||
"select count(distinct u) from User u left outer join u.roles r WHERE r = ?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForQueriesWithSubSelects() {
|
||||
|
||||
assertCountQuery("select u from User u left outer join u.roles r where r in (select r from Role)",
|
||||
"select count(u) from User u left outer join u.roles r where r in (select r from Role)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForAliasesCorrectly() {
|
||||
|
||||
assertCountQuery("select u from User as u", "select count(u) from User as u");
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsShortJpaSyntax() {
|
||||
|
||||
assertCountQuery(SIMPLE_QUERY, COUNT_QUERY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsAliasCorrectly() {
|
||||
|
||||
assertThat(detectAlias(QUERY)).isEqualTo("u");
|
||||
assertThat(detectAlias(SIMPLE_QUERY)).isEqualTo("u");
|
||||
assertThat(detectAlias(COUNT_QUERY)).isEqualTo("u");
|
||||
assertThat(detectAlias(QUERY_WITH_AS)).isEqualTo("u");
|
||||
assertThat(detectAlias("SELECT FROM USER U")).isEqualTo("U");
|
||||
assertThat(detectAlias("select u from User u")).isEqualTo("u");
|
||||
assertThat(detectAlias("select u from com.acme.User u")).isEqualTo("u");
|
||||
assertThat(detectAlias("select u from T05User u")).isEqualTo("u");
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsFullyQualifiedEntityNamesInQuery() {
|
||||
|
||||
assertThat(detectAlias(FQ_QUERY)).isEqualTo("u");
|
||||
assertCountQuery(FQ_QUERY, "select count(u) from org.acme.domain.User$Foo_Bar u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-252
|
||||
void detectsJoinAliasesCorrectly() {
|
||||
|
||||
Set<String> aliases = getOuterJoinAliases("select p from Person p left outer join x.foo b2_$ar where …");
|
||||
assertThat(aliases).hasSize(1);
|
||||
assertThat(aliases).contains("b2_$ar");
|
||||
|
||||
aliases = getOuterJoinAliases("select p from Person p left join x.foo b2_$ar where …");
|
||||
assertThat(aliases).hasSize(1);
|
||||
assertThat(aliases).contains("b2_$ar");
|
||||
|
||||
aliases = getOuterJoinAliases(
|
||||
"select p from Person p left outer join x.foo as b2_$ar, left join x.bar as foo where …");
|
||||
assertThat(aliases).hasSize(2);
|
||||
assertThat(aliases).contains("b2_$ar", "foo");
|
||||
|
||||
aliases = getOuterJoinAliases(
|
||||
"select p from Person p left join x.foo as b2_$ar, left outer join x.bar foo where …");
|
||||
assertThat(aliases).hasSize(2);
|
||||
assertThat(aliases).contains("b2_$ar", "foo");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-252
|
||||
void doesNotPrefixOrderReferenceIfOuterJoinAliasDetected() {
|
||||
|
||||
String query = "select p from Person p left join p.address address";
|
||||
assertThat(applySorting(query, Sort.by("address.city"))).endsWith("order by address.city asc");
|
||||
assertThat(applySorting(query, Sort.by("address.city", "lastname"), "p"))
|
||||
.endsWith("order by address.city asc, p.lastname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-252
|
||||
void extendsExistingOrderByClausesCorrectly() {
|
||||
|
||||
String query = "select p from Person p order by p.lastname asc";
|
||||
assertThat(applySorting(query, Sort.by("firstname"), "p")).endsWith("order by p.lastname asc, p.firstname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-296
|
||||
void appliesIgnoreCaseOrderingCorrectly() {
|
||||
|
||||
Sort sort = Sort.by(Order.by("firstname").ignoreCase());
|
||||
|
||||
String query = "select p from Person p";
|
||||
assertThat(applySorting(query, sort, "p")).endsWith("order by lower(p.firstname) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-296
|
||||
void appendsIgnoreCaseOrderingCorrectly() {
|
||||
|
||||
Sort sort = Sort.by(Order.by("firstname").ignoreCase());
|
||||
|
||||
String query = "select p from Person p order by p.lastname asc";
|
||||
assertThat(applySorting(query, sort, "p")).endsWith("order by p.lastname asc, lower(p.firstname) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-342
|
||||
void usesReturnedVariableInCOuntProjectionIfSet() {
|
||||
|
||||
assertCountQuery("select distinct m.genre from Media m where m.user = ?1 order by m.genre asc",
|
||||
"select count(distinct m.genre) from Media m where m.user = ?1");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-343
|
||||
void projectsCOuntQueriesForQueriesWithSubselects() {
|
||||
|
||||
assertCountQuery("select o from Foo o where cb.id in (select b from Bar b)",
|
||||
"select count(o) from Foo o where cb.id in (select b from Bar b)");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-148
|
||||
void doesNotPrefixSortsIfFunction() {
|
||||
|
||||
Sort sort = Sort.by("sum(foo)");
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> applySorting("select p from Person p", sort, "p"));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-377
|
||||
void removesOrderByInGeneratedCountQueryFromOriginalQueryIfPresent() {
|
||||
|
||||
assertCountQuery("select distinct m.genre from Media m where m.user = ?1 OrDer By m.genre ASC",
|
||||
"select count(distinct m.genre) from Media m where m.user = ?1");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-375
|
||||
void findsExistingOrderByIndependentOfCase() {
|
||||
|
||||
Sort sort = Sort.by("lastname");
|
||||
String query = applySorting("select p from Person p ORDER BY p.firstname", sort, "p");
|
||||
assertThat(query).endsWith("ORDER BY p.firstname, p.lastname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-409
|
||||
void createsCountQueryForNestedReferenceCorrectly() {
|
||||
assertCountQuery("select a.b from A a", "select count(a.b) from A a");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-420
|
||||
void createsCountQueryForScalarSelects() {
|
||||
assertCountQuery("select p.lastname,p.firstname from Person p", "select count(p) from Person p");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-456
|
||||
void createCountQueryFromTheGivenCountProjection() {
|
||||
assertThat(createCountQueryFor("select p.lastname,p.firstname from Person p", "p.lastname"))
|
||||
.isEqualTo("select count(p.lastname) from Person p");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-726
|
||||
void detectsAliassesInPlainJoins() {
|
||||
|
||||
String query = "select p from Customer c join c.productOrder p where p.delayed = true";
|
||||
Sort sort = Sort.by("p.lineItems");
|
||||
|
||||
assertThat(applySorting(query, sort, "c")).endsWith("order by p.lineItems asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-736
|
||||
void supportsNonAsciiCharactersInEntityNames() {
|
||||
assertThat(createCountQueryFor("select u from Usèr u")).isEqualTo("select count(u) from Usèr u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-798
|
||||
void detectsAliasInQueryContainingLineBreaks() {
|
||||
assertThat(detectAlias("select \n u \n from \n User \nu")).isEqualTo("u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-815
|
||||
void doesPrefixPropertyWith() {
|
||||
|
||||
String query = "from Cat c join Dog d";
|
||||
Sort sort = Sort.by("dPropertyStartingWithJoinAlias");
|
||||
|
||||
assertThat(applySorting(query, sort, "c")).endsWith("order by c.dPropertyStartingWithJoinAlias asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-938
|
||||
void detectsConstructorExpressionInDistinctQuery() {
|
||||
assertThat(hasConstructorExpression("select distinct new Foo() from Bar b")).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-938
|
||||
void detectsComplexConstructorExpression() {
|
||||
|
||||
assertThat(hasConstructorExpression("select new foo.bar.Foo(ip.id, ip.name, sum(lp.amount)) " //
|
||||
+ "from Bar lp join lp.investmentProduct ip " //
|
||||
+ "where (lp.toDate is null and lp.fromDate <= :now and lp.fromDate is not null) and lp.accountId = :accountId " //
|
||||
+ "group by ip.id, ip.name, lp.accountId " //
|
||||
+ "order by ip.name ASC")).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-938
|
||||
void detectsConstructorExpressionWithLineBreaks() {
|
||||
assertThat(hasConstructorExpression("select new foo.bar.FooBar(\na.id) from DtoA a ")).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-960
|
||||
void doesNotQualifySortIfNoAliasDetected() {
|
||||
assertThat(applySorting("from mytable where ?1 is null", Sort.by("firstname"))).endsWith("order by firstname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotAllowWhitespaceInSort() {
|
||||
|
||||
Sort sort = Sort.by("case when foo then bar");
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> applySorting("select p from Person p", sort, "p"));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixUnsageJpaSortFunctionCalls() {
|
||||
|
||||
JpaSort sort = JpaSort.unsafe("sum(foo)");
|
||||
assertThat(applySorting("select p from Person p", sort, "p")).endsWith("order by sum(foo) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixMultipleAliasedFunctionCalls() {
|
||||
|
||||
String query = "SELECT AVG(m.price) AS avgPrice, SUM(m.stocks) AS sumStocks FROM Magazine m";
|
||||
Sort sort = Sort.by("avgPrice", "sumStocks");
|
||||
|
||||
assertThat(applySorting(query, sort, "m")).endsWith("order by avgPrice asc, sumStocks asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixSingleAliasedFunctionCalls() {
|
||||
|
||||
String query = "SELECT AVG(m.price) AS avgPrice FROM Magazine m";
|
||||
Sort sort = Sort.by("avgPrice");
|
||||
|
||||
assertThat(applySorting(query, sort, "m")).endsWith("order by avgPrice asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void prefixesSingleNonAliasedFunctionCallRelatedSortProperty() {
|
||||
|
||||
String query = "SELECT AVG(m.price) AS avgPrice FROM Magazine m";
|
||||
Sort sort = Sort.by("someOtherProperty");
|
||||
|
||||
assertThat(applySorting(query, sort, "m")).endsWith("order by m.someOtherProperty asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainesAliasedFunctionForDifferentProperty() {
|
||||
|
||||
String query = "SELECT m.name, AVG(m.price) AS avgPrice FROM Magazine m";
|
||||
Sort sort = Sort.by("name", "avgPrice");
|
||||
|
||||
assertThat(applySorting(query, sort, "m")).endsWith("order by m.name asc, avgPrice asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWithMultipleNumericParameters() {
|
||||
|
||||
String query = "SELECT SUBSTRING(m.name, 2, 5) AS trimmedName FROM Magazine m";
|
||||
Sort sort = Sort.by("trimmedName");
|
||||
|
||||
assertThat(applySorting(query, sort, "m")).endsWith("order by trimmedName asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWithMultipleStringParameters() {
|
||||
|
||||
String query = "SELECT CONCAT(m.name, 'foo') AS extendedName FROM Magazine m";
|
||||
Sort sort = Sort.by("extendedName");
|
||||
|
||||
assertThat(applySorting(query, sort, "m")).endsWith("order by extendedName asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWithUnderscores() {
|
||||
|
||||
String query = "SELECT AVG(m.price) AS avg_price FROM Magazine m";
|
||||
Sort sort = Sort.by("avg_price");
|
||||
|
||||
assertThat(applySorting(query, sort, "m")).endsWith("order by avg_price asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWithDots() {
|
||||
|
||||
String query = "SELECT AVG(m.price) AS m.avg FROM Magazine m";
|
||||
Sort sort = Sort.by("m.avg");
|
||||
|
||||
assertThat(applySorting(query, sort, "m")).endsWith("order by m.avg asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWhenQueryStringContainsMultipleWhiteSpaces() {
|
||||
|
||||
String query = "SELECT AVG( m.price ) AS avgPrice FROM Magazine m";
|
||||
Sort sort = Sort.by("avgPrice");
|
||||
|
||||
assertThat(applySorting(query, sort, "m")).endsWith("order by avgPrice asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1000
|
||||
void discoversCorrectAliasForJoinFetch() {
|
||||
|
||||
Set<String> aliases = QueryUtils
|
||||
.getOuterJoinAliases("SELECT DISTINCT user FROM User user LEFT JOIN FETCH user.authorities AS authority");
|
||||
|
||||
assertThat(aliases).containsExactly("authority");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1171
|
||||
void doesNotContainStaticClauseInExistsQuery() {
|
||||
|
||||
assertThat(QueryUtils.getExistsQueryString("entity", "x", Collections.singleton("id"))) //
|
||||
.endsWith("WHERE x.id = :id");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1363
|
||||
void discoversAliasWithComplexFunction() {
|
||||
|
||||
assertThat(QueryUtils
|
||||
.getFunctionAliases("select new MyDto(sum(case when myEntity.prop3=0 then 1 else 0 end) as myAlias")) //
|
||||
.contains("myAlias");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1506
|
||||
void detectsAliasWithGroupAndOrderBy() {
|
||||
|
||||
assertThat(detectAlias("select * from User group by name")).isNull();
|
||||
assertThat(detectAlias("select * from User order by name")).isNull();
|
||||
assertThat(detectAlias("select * from User u group by name")).isEqualTo("u");
|
||||
assertThat(detectAlias("select * from User u order by name")).isEqualTo("u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1500
|
||||
void createCountQuerySupportsWhitespaceCharacters() {
|
||||
|
||||
assertThat(createCountQueryFor("select * from User user\n" + //
|
||||
" where user.age = 18\n" + //
|
||||
" order by user.name\n ")).isEqualTo("select count(user) from User user\n" + //
|
||||
" where user.age = 18\n ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createCountQuerySupportsLineBreaksInSelectClause() {
|
||||
|
||||
assertThat(createCountQueryFor("select user.age,\n" + //
|
||||
" user.name\n" + //
|
||||
" from User user\n" + //
|
||||
" where user.age = 18\n" + //
|
||||
" order\nby\nuser.name\n ")).isEqualTo("select count(user) from User user\n" + //
|
||||
" where user.age = 18\n ");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1061
|
||||
void appliesSortCorrectlyForFieldAliases() {
|
||||
|
||||
String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a";
|
||||
Sort sort = Sort.by("authorName");
|
||||
|
||||
String fullQuery = applySorting(query, sort);
|
||||
|
||||
assertThat(fullQuery).endsWith("order by authorName asc");
|
||||
}
|
||||
|
||||
@Test // GH-2280
|
||||
void appliesOrderingCorrectlyForFieldAliasWithIgnoreCase() {
|
||||
|
||||
String query = "SELECT customer.id as id, customer.name as name FROM CustomerEntity customer";
|
||||
Sort sort = Sort.by(Order.by("name").ignoreCase());
|
||||
|
||||
String fullQuery = applySorting(query, sort);
|
||||
|
||||
assertThat(fullQuery).isEqualTo(
|
||||
"SELECT customer.id as id, customer.name as name FROM CustomerEntity customer order by lower(name) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1061
|
||||
void appliesSortCorrectlyForFunctionAliases() {
|
||||
|
||||
String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a";
|
||||
Sort sort = Sort.by("title");
|
||||
|
||||
String fullQuery = applySorting(query, sort);
|
||||
|
||||
assertThat(fullQuery).endsWith("order by title asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1061
|
||||
void appliesSortCorrectlyForSimpleField() {
|
||||
|
||||
String query = "SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a";
|
||||
Sort sort = Sort.by("price");
|
||||
|
||||
String fullQuery = applySorting(query, sort);
|
||||
|
||||
assertThat(fullQuery).endsWith("order by m.price asc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createCountQuerySupportsLineBreakRightAfterDistinct() {
|
||||
|
||||
assertThat(createCountQueryFor("select\ndistinct\nuser.age,\n" + //
|
||||
"user.name\n" + //
|
||||
"from\nUser\nuser")).isEqualTo(createCountQueryFor("select\ndistinct user.age,\n" + //
|
||||
"user.name\n" + //
|
||||
"from\nUser\nuser"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsAliasWithGroupAndOrderByWithLineBreaks() {
|
||||
|
||||
assertThat(detectAlias("select * from User group\nby name")).isNull();
|
||||
assertThat(detectAlias("select * from User order\nby name")).isNull();
|
||||
assertThat(detectAlias("select * from User u group\nby name")).isEqualTo("u");
|
||||
assertThat(detectAlias("select * from User u order\nby name")).isEqualTo("u");
|
||||
assertThat(detectAlias("select * from User\nu\norder \n by name")).isEqualTo("u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1679
|
||||
void findProjectionClauseWithDistinct() {
|
||||
|
||||
SoftAssertions.assertSoftly(sofly -> {
|
||||
sofly.assertThat(QueryUtils.getProjection("select * from x")).isEqualTo("*");
|
||||
sofly.assertThat(QueryUtils.getProjection("select a, b, c from x")).isEqualTo("a, b, c");
|
||||
sofly.assertThat(QueryUtils.getProjection("select distinct a, b, c from x")).isEqualTo("a, b, c");
|
||||
sofly.assertThat(QueryUtils.getProjection("select DISTINCT a, b, c from x")).isEqualTo("a, b, c");
|
||||
});
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1696
|
||||
void findProjectionClauseWithSubselect() {
|
||||
|
||||
// This is not a required behavior, in fact the opposite is,
|
||||
// but it documents a current limitation.
|
||||
// to fix this without breaking findProjectionClauseWithIncludedFrom we need a more sophisticated parser.
|
||||
assertThat(QueryUtils.getProjection("select * from (select x from y)")).isNotEqualTo("*");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1696
|
||||
void findProjectionClauseWithIncludedFrom() {
|
||||
assertThat(QueryUtils.getProjection("select x, frommage, y from t")).isEqualTo("x, frommage, y");
|
||||
}
|
||||
|
||||
private static void assertCountQuery(String originalQuery, String countQuery) {
|
||||
assertThat(createCountQueryFor(originalQuery)).isEqualTo(countQuery);
|
||||
}
|
||||
}
|
||||
@@ -35,13 +35,15 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @author Michael J. Simons
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class ExpressionBasedStringQueryUnitTests {
|
||||
|
||||
private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser();
|
||||
@Mock JpaEntityMetadata<?> metadata;
|
||||
@Mock
|
||||
JpaEntityMetadata<?> metadata;
|
||||
|
||||
@Test // DATAJPA-170
|
||||
void shouldReturnQueryWithDomainTypeExpressionReplacedWithSimpleDomainTypeName() {
|
||||
@@ -49,7 +51,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
when(metadata.getEntityName()).thenReturn("User");
|
||||
|
||||
String source = "select from #{#entityName} u where u.firstname like :firstname";
|
||||
StringQuery query = new ExpressionBasedStringQuery(source, metadata, SPEL_PARSER);
|
||||
StringQuery query = new ExpressionBasedStringQuery(source, metadata, SPEL_PARSER, false);
|
||||
assertThat(query.getQueryString()).isEqualTo("select from User u where u.firstname like :firstname");
|
||||
}
|
||||
|
||||
@@ -58,7 +60,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
|
||||
when(metadata.getEntityName()).thenReturn("User");
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from #{#entityName} u", metadata, SPEL_PARSER);
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from #{#entityName} u", metadata, SPEL_PARSER, true);
|
||||
assertThat(query.getAlias()).isEqualTo("u");
|
||||
assertThat(query.getQueryString()).isEqualTo("select u from User u");
|
||||
}
|
||||
@@ -71,7 +73,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
+ "+ \"AND (LOWER(n.server) LIKE LOWER(NULLIF(text(concat('%',:#{#networkRequest.server},'%')), '')) OR :#{#networkRequest.server} IS NULL)\"\n"
|
||||
+ "+ \"AND (n.createdAt >= :#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=:#{#networkRequest.createdTime.endDateTime})\"\n"
|
||||
+ "+ \"AND (n.updatedAt >= :#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=:#{#networkRequest.updatedTime.endDateTime})",
|
||||
metadata, SPEL_PARSER);
|
||||
metadata, SPEL_PARSER, false);
|
||||
|
||||
assertThat(query.getParameterBindings()).hasSize(8);
|
||||
}
|
||||
@@ -80,13 +82,39 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
void shouldDetectBindParameterCountCorrectlyWithJDBCStyleParameters() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery(
|
||||
"select n from #{#entityName} n where (LOWER(n.name) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.name},'%')), '')) OR ?#{#networkRequest.name} IS NULL )\"\n"
|
||||
+ "+ \"AND (LOWER(n.server) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.server},'%')), '')) OR ?#{#networkRequest.server} IS NULL)\"\n"
|
||||
+ "+ \"AND (n.createdAt >= ?#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=?#{#networkRequest.createdTime.endDateTime})\"\n"
|
||||
+ "+ \"AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})",
|
||||
metadata, SPEL_PARSER);
|
||||
"select n from #{#entityName} n where (LOWER(n.name) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.name},'%')), '')) OR ?#{#networkRequest.name} IS NULL )\"\n"
|
||||
+ "+ \"AND (LOWER(n.server) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.server},'%')), '')) OR ?#{#networkRequest.server} IS NULL)\"\n"
|
||||
+ "+ \"AND (n.createdAt >= ?#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=?#{#networkRequest.createdTime.endDateTime})\"\n"
|
||||
+ "+ \"AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})",
|
||||
metadata, SPEL_PARSER, false);
|
||||
|
||||
assertThat(query.getParameterBindings()).hasSize(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectComplexNativeQueriesWithSpelAsNonNative() {
|
||||
StringQuery query = new ExpressionBasedStringQuery(
|
||||
"select n from #{#entityName} n where (LOWER(n.name) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.name},'%')), '')) OR ?#{#networkRequest.name} IS NULL )\"\n"
|
||||
+ "+ \"AND (LOWER(n.server) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.server},'%')), '')) OR ?#{#networkRequest.server} IS NULL)\"\n"
|
||||
+ "+ \"AND (n.createdAt >= ?#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=?#{#networkRequest.createdTime.endDateTime})\"\n"
|
||||
+ "+ \"AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})",
|
||||
metadata, SPEL_PARSER, true);
|
||||
|
||||
assertThat(query.isNativeQuery()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectSimpleNativeQueriesWithSpelAsNonNative() {
|
||||
StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata, SPEL_PARSER, true);
|
||||
|
||||
assertThat(query.isNativeQuery()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectSimpleNativeQueriesWithoutSpelAsNonNative() {
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from User u", metadata, SPEL_PARSER, true);
|
||||
|
||||
assertThat(query.isNativeQuery()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -74,8 +74,10 @@ public class JpaQueryMethodUnitTests {
|
||||
|
||||
private static final String METHOD_NAME = "findByFirstname";
|
||||
|
||||
@Mock QueryExtractor extractor;
|
||||
@Mock RepositoryMetadata metadata;
|
||||
@Mock
|
||||
QueryExtractor extractor;
|
||||
@Mock
|
||||
RepositoryMetadata metadata;
|
||||
private ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
|
||||
|
||||
private Method invalidReturnType;
|
||||
@@ -518,7 +520,7 @@ public class JpaQueryMethodUnitTests {
|
||||
|
||||
interface ValidRepository extends Repository<User, Integer> {
|
||||
|
||||
@Query(value = "query", nativeQuery = true)
|
||||
@Query(value = "Select u from User u where u.lastname = ?1", nativeQuery = true)
|
||||
List<User> findByLastname(String lastname);
|
||||
|
||||
@Query(name = "HateoasAwareSpringDataWebConfiguration.bar")
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.data.jpa.repository.query.StringQuery.ParameterBindin
|
||||
* Unit tests for the {@link ParameterBindingParser}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
class ParameterBindingParserUnitTests {
|
||||
|
||||
@@ -65,7 +66,7 @@ class ParameterBindingParserUnitTests {
|
||||
|
||||
private void checkHasParameter(SoftAssertions softly, String query, boolean containsParameter, String label) {
|
||||
|
||||
StringQuery stringQuery = new StringQuery(query);
|
||||
StringQuery stringQuery = new StringQuery(query, false);
|
||||
|
||||
softly.assertThat(stringQuery.getParameterBindings().size()) //
|
||||
.describedAs(String.format("<%s> (%s)", query, label)) //
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2022 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.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link QueryEnhancerFactory}.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
class QueryEnhancerFactoryUnitTests {
|
||||
|
||||
@Test
|
||||
void createsDefaultImplementationForNonNativeQuery() {
|
||||
StringQuery query = new StringQuery("Select new User(u.firstname) from User u", false);
|
||||
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query);
|
||||
assertThat(queryEnhancer) //
|
||||
.isInstanceOf(DefaultQueryEnhancer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsJSqlImplementationForNativeQuery() {
|
||||
StringQuery query = new StringQuery("Select * from User", true);
|
||||
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query);
|
||||
assertThat(queryEnhancer) //
|
||||
.isInstanceOf(JSqlParserQueryEnhancer.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,707 @@
|
||||
/*
|
||||
* Copyright 2022 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.assertj.core.api.SoftAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.JpaSort;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit test for {@link QueryEnhancer}.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
class QueryEnhancerUnitTests {
|
||||
|
||||
private static final String QUERY = "select u from User u";
|
||||
private static final String FQ_QUERY = "select u from org.acme.domain.User$Foo_Bar u";
|
||||
private static final String SIMPLE_QUERY = "from User u";
|
||||
private static final String COUNT_QUERY = "select count(u) from User u";
|
||||
|
||||
private static final String QUERY_WITH_AS = "select u from User as u where u.username = ?";
|
||||
|
||||
@Test
|
||||
void createsCountQueryCorrectly() {
|
||||
assertCountQuery(QUERY, COUNT_QUERY, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueriesCorrectlyForCapitalLetterJPQL() {
|
||||
|
||||
assertCountQuery("FROM User u WHERE u.foo.bar = ?", "select count(u) FROM User u WHERE u.foo.bar = ?", false);
|
||||
|
||||
assertCountQuery("SELECT u FROM User u where u.foo.bar = ?", "select count(u) FROM User u where u.foo.bar = ?",
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForDistinctQueries() {
|
||||
|
||||
assertCountQuery("select distinct u from User u where u.foo = ?",
|
||||
"select count(distinct u) from User u where u.foo = ?", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForConstructorQueries() {
|
||||
|
||||
assertCountQuery("select distinct new User(u.name) from User u where u.foo = ?",
|
||||
"select count(distinct u) from User u where u.foo = ?", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForJoinsNoneNative() {
|
||||
|
||||
assertCountQuery("select distinct new User(u.name) from User u left outer join u.roles r WHERE r = ?",
|
||||
"select count(distinct u) from User u left outer join u.roles r WHERE r = ?", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForJoinsNative() {
|
||||
|
||||
assertCountQuery("select distinct u.name from User u left outer join u.roles r WHERE r = ?",
|
||||
"select count(distinct u.name) from User u left outer join u.roles r WHERE r = ?", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForQueriesWithSubSelects() {
|
||||
|
||||
assertCountQuery("select u from User u left outer join u.roles r where r in (select r from Role)",
|
||||
"select count(u) from User u left outer join u.roles r where r in (select r from Role)", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForAliasesCorrectly() {
|
||||
|
||||
assertCountQuery("select u from User as u", "select count(u) from User as u", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsShortJpaSyntax() {
|
||||
|
||||
assertCountQuery(SIMPLE_QUERY, COUNT_QUERY, false);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("detectsAliasWithUCorrectlySource")
|
||||
void detectsAliasWithUCorrectly(DeclaredQuery query, String alias) {
|
||||
assertThat(getEnhancer(query).detectAlias()).isEqualTo(alias);
|
||||
}
|
||||
|
||||
public static Stream<Arguments> detectsAliasWithUCorrectlySource() {
|
||||
return Stream.of( //
|
||||
Arguments.of(new StringQuery(QUERY, true), "u"), //
|
||||
Arguments.of(new StringQuery(SIMPLE_QUERY, false), "u"), //
|
||||
Arguments.of(new StringQuery(COUNT_QUERY, true), "u"), //
|
||||
Arguments.of(new StringQuery(QUERY_WITH_AS, true), "u"), //
|
||||
Arguments.of(new StringQuery("SELECT FROM USER U", false), "U"), //
|
||||
Arguments.of(new StringQuery("select u from User u", true), "u"), //
|
||||
Arguments.of(new StringQuery("select u from com.acme.User u", true), "u"), //
|
||||
Arguments.of(new StringQuery("select u from T05User u", true), "u") //
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsFullyQualifiedEntityNamesInQuery() {
|
||||
|
||||
StringQuery query = new StringQuery(FQ_QUERY, true);
|
||||
assertThat(getEnhancer(query).detectAlias()).isEqualTo("u");
|
||||
assertCountQuery(FQ_QUERY, "select count(u) from org.acme.domain.User$Foo_Bar u", true);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-252
|
||||
void doesNotPrefixOrderReferenceIfOuterJoinAliasDetected() {
|
||||
|
||||
StringQuery query = new StringQuery("select p from Person p left join p.address address", true);
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(Sort.by("address.city")), "order by address.city asc");
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(Sort.by("address.city", "lastname"), "p"),
|
||||
"order by address.city asc, p.lastname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-252
|
||||
void extendsExistingOrderByClausesCorrectly() {
|
||||
|
||||
StringQuery query = new StringQuery("select p from Person p order by p.lastname asc", true);
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(Sort.by("firstname"), "p"),
|
||||
"order by p.lastname asc, p.firstname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-296
|
||||
void appliesIgnoreCaseOrderingCorrectly() {
|
||||
|
||||
Sort sort = Sort.by(Sort.Order.by("firstname").ignoreCase());
|
||||
|
||||
StringQuery query = new StringQuery("select p from Person p", true);
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "p"), "order by lower(p.firstname) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-296
|
||||
void appendsIgnoreCaseOrderingCorrectly() {
|
||||
|
||||
Sort sort = Sort.by(Sort.Order.by("firstname").ignoreCase());
|
||||
|
||||
StringQuery query = new StringQuery("select p from Person p order by p.lastname asc", true);
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "p"), "order by p.lastname asc, lower(p.firstname) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-342
|
||||
void usesReturnedVariableInCOuntProjectionIfSet() {
|
||||
|
||||
assertCountQuery("select distinct m.genre from Media m where m.user = ?1 order by m.genre asc",
|
||||
"select count(distinct m.genre) from Media m where m.user = ?1", true);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-343
|
||||
void projectsCountQueriesForQueriesWithSubSelects() {
|
||||
|
||||
assertCountQuery("select o from Foo o where cb.id in (select b from Bar b)",
|
||||
"select count(o) from Foo o where cb.id in (select b from Bar b)", true);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-148
|
||||
void doesNotPrefixSortsIfFunction() {
|
||||
StringQuery query = new StringQuery("select p from Person p", true);
|
||||
Sort sort = Sort.by("sum(foo)");
|
||||
|
||||
QueryEnhancer enhancer = getEnhancer(query);
|
||||
|
||||
assertThatThrownBy(() -> enhancer.applySorting(sort, "p")) //
|
||||
.isInstanceOf(InvalidDataAccessApiUsageException.class);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-377
|
||||
void removesOrderByInGeneratedCountQueryFromOriginalQueryIfPresent() {
|
||||
|
||||
assertCountQuery("select distinct m.genre from Media m where m.user = ?1 OrDer By m.genre ASC",
|
||||
"select count(distinct m.genre) from Media m where m.user = ?1", true);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-375
|
||||
void findsExistingOrderByIndependentOfCase() {
|
||||
|
||||
Sort sort = Sort.by("lastname");
|
||||
StringQuery originalQuery = new StringQuery("select p from Person p ORDER BY p.firstname", true);
|
||||
String query = getEnhancer(originalQuery).applySorting(sort, "p");
|
||||
endsIgnoringCase(query, "ORDER BY p.firstname, p.lastname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-409
|
||||
void createsCountQueryForNestedReferenceCorrectly() {
|
||||
assertCountQuery("select a.b from A a", "select count(a.b) from A a", true);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-420
|
||||
void createsCountQueryForScalarSelects() {
|
||||
assertCountQuery("select p.lastname,p.firstname from Person p", "select count(p) from Person p", true);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-456
|
||||
void createCountQueryFromTheGivenCountProjection() {
|
||||
StringQuery query = new StringQuery("select p.lastname,p.firstname from Person p", true);
|
||||
assertThat(getEnhancer(query).createCountQueryFor("p.lastname"))
|
||||
.isEqualToIgnoringCase("select count(p.lastname) from Person p");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-726
|
||||
void detectsAliassesInPlainJoins() {
|
||||
|
||||
StringQuery query = new StringQuery("select p from Customer c join c.productOrder p where p.delaye = true", true);
|
||||
Sort sort = Sort.by("p.lineItems");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "c"), "order by p.lineItems asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-736
|
||||
void supportsNonAsciiCharactersInEntityNames() {
|
||||
StringQuery query = new StringQuery("select u from Usèr u", true);
|
||||
assertThat(getEnhancer(query).createCountQueryFor()).isEqualToIgnoringCase("select count(u) from Usèr u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-798
|
||||
void detectsAliasInQueryContainingLineBreaks() {
|
||||
StringQuery query = new StringQuery("select \n u \n from \n User \nu", true);
|
||||
assertThat(getEnhancer(query).detectAlias()).isEqualTo("u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-815
|
||||
void doesPrefixPropertyWithNonNative() {
|
||||
|
||||
StringQuery query = new StringQuery("from Cat c join Dog d", false);
|
||||
Sort sort = Sort.by("dPropertyStartingWithJoinAlias");
|
||||
|
||||
assertThat(getEnhancer(query).applySorting(sort, "c")).endsWith("order by c.dPropertyStartingWithJoinAlias asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-815
|
||||
void doesPrefixPropertyWithNative() {
|
||||
StringQuery query = new StringQuery("Select * from Cat c join Dog d", true);
|
||||
Sort sort = Sort.by("dPropertyStartingWithJoinAlias");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "c"), "order by c.dPropertyStartingWithJoinAlias asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-938
|
||||
void detectsConstructorExpressionInDistinctQuery() {
|
||||
StringQuery query = new StringQuery("select distinct new Foo() from Bar b", false);
|
||||
assertThat(getEnhancer(query).hasConstructorExpression()).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-938
|
||||
void detectsComplexConstructorExpression() {
|
||||
|
||||
StringQuery query = new StringQuery("select new foo.bar.Foo(ip.id, ip.name, sum(lp.amount)) " //
|
||||
+ "from Bar lp join lp.investmentProduct ip " //
|
||||
+ "where (lp.toDate is null and lp.fromDate <= :now and lp.fromDate is not null) and lp.accountId = :accountId "
|
||||
//
|
||||
+ "group by ip.id, ip.name, lp.accountId " //
|
||||
+ "order by ip.name ASC", false);
|
||||
|
||||
assertThat(getEnhancer(query).hasConstructorExpression()).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-938
|
||||
void detectsConstructorExpressionWithLineBreaks() {
|
||||
StringQuery query = new StringQuery("select new foo.bar.FooBar(\na.id) from DtoA a ", false);
|
||||
assertThat(getEnhancer(query).hasConstructorExpression()).isTrue();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-960
|
||||
void doesNotQualifySortIfNoAliasDetectedNonNative() {
|
||||
StringQuery query = new StringQuery("from mytable where ?1 is null", false);
|
||||
assertThat(getEnhancer(query).applySorting(Sort.by("firstname"))).endsWith("order by firstname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-960
|
||||
void doesNotQualifySortIfNoAliasDetectedNative() {
|
||||
StringQuery query = new StringQuery("Select * from mytable where ?1 is null", true);
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(Sort.by("firstname")), "order by firstname asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotAllowWhitespaceInSort() {
|
||||
|
||||
StringQuery query = new StringQuery("select p from Person p", true);
|
||||
|
||||
Sort sort = Sort.by("case when foo then bar");
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> getEnhancer(query).applySorting(sort, "p"));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixUnsageJpaSortFunctionCalls() {
|
||||
|
||||
JpaSort sort = JpaSort.unsafe("sum(foo)");
|
||||
StringQuery query = new StringQuery("select p from Person p", true);
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "p"), "order by sum(foo) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixMultipleAliasedFunctionCalls() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT AVG(m.price) AS avgPrice, SUM(m.stocks) AS sumStocks FROM Magazine m",
|
||||
true);
|
||||
Sort sort = Sort.by("avgPrice", "sumStocks");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "m"), "order by avgPrice asc, sumStocks asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixSingleAliasedFunctionCalls() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT AVG(m.price) AS avgPrice FROM Magazine m", true);
|
||||
Sort sort = Sort.by("avgPrice");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "m"), "order by avgPrice asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void prefixesSingleNonAliasedFunctionCallRelatedSortProperty() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT AVG(m.price) AS avgPrice FROM Magazine m", true);
|
||||
Sort sort = Sort.by("someOtherProperty");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "m"), "order by m.someOtherProperty asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void prefixesNonAliasedFunctionCallRelatedSortPropertyWhenSelectClauseContainesAliasedFunctionForDifferentProperty() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT m.name, AVG(m.price) AS avgPrice FROM Magazine m", true);
|
||||
Sort sort = Sort.by("name", "avgPrice");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "m"), "order by m.name asc, avgPrice asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWithMultipleNumericParameters() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT SUBSTRING(m.name, 2, 5) AS trimmedName FROM Magazine m", true);
|
||||
Sort sort = Sort.by("trimmedName");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "m"), "order by trimmedName asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWithMultipleStringParameters() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT CONCAT(m.name, 'foo') AS extendedName FROM Magazine m", true);
|
||||
Sort sort = Sort.by("extendedName");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "m"), "order by extendedName asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWithUnderscores() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT AVG(m.price) AS avg_price FROM Magazine m", true);
|
||||
Sort sort = Sort.by("avg_price");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "m"), "order by avg_price asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWithDots() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT AVG(m.price) AS m.avg FROM Magazine m", false);
|
||||
Sort sort = Sort.by("m.avg");
|
||||
|
||||
assertThat(getEnhancer(query).applySorting(sort, "m")).endsWith("order by m.avg asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWithDotsNativeQuery() {
|
||||
|
||||
// this is invalid since the '.' character is not allowed. Not in sql nor in JPQL.
|
||||
assertThatThrownBy(() -> new StringQuery("SELECT AVG(m.price) AS m.avg FROM Magazine m", true)) //
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-965, DATAJPA-970
|
||||
void doesNotPrefixAliasedFunctionCallNameWhenQueryStringContainsMultipleWhiteSpaces() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT AVG( m.price ) AS avgPrice FROM Magazine m", true);
|
||||
Sort sort = Sort.by("avgPrice");
|
||||
|
||||
endsIgnoringCase(getEnhancer(query).applySorting(sort, "m"), "order by avgPrice asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1000
|
||||
void discoversCorrectAliasForJoinFetch() {
|
||||
|
||||
String queryString = "SELECT DISTINCT user FROM User user LEFT JOIN user.authorities AS authority";
|
||||
Set<String> aliases = QueryUtils.getOuterJoinAliases(queryString);
|
||||
|
||||
StringQuery nativeQuery = new StringQuery(queryString, true);
|
||||
Set<String> joinAliases = new JSqlParserQueryEnhancer(nativeQuery).getJoinAliases();
|
||||
|
||||
assertThat(aliases).containsExactly("authority");
|
||||
assertThat(joinAliases).containsExactly("authority");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1171
|
||||
void doesNotContainStaticClauseInExistsQuery() {
|
||||
endsIgnoringCase(QueryUtils.getExistsQueryString("entity", "x", Collections.singleton("id")), "WHERE x.id = :id");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1363
|
||||
void discoversAliasWithComplexFunction() {
|
||||
|
||||
assertThat(
|
||||
QueryUtils.getFunctionAliases("select new MyDto(sum(case when myEntity.prop3=0 then 1 else 0 end) as myAlias")) //
|
||||
.contains("myAlias");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1506
|
||||
void detectsAliasWithGroupAndOrderBy() {
|
||||
|
||||
StringQuery queryWithGroupNoAlias = new StringQuery("select * from User group by name", true);
|
||||
StringQuery queryWithGroupAlias = new StringQuery("select * from User u group by name", true);
|
||||
|
||||
StringQuery queryWithOrderNoAlias = new StringQuery("select * from User order by name", true);
|
||||
StringQuery queryWithOrderAlias = new StringQuery("select * from User u order by name", true);
|
||||
|
||||
assertThat(getEnhancer(queryWithGroupNoAlias).detectAlias()).isNull();
|
||||
assertThat(getEnhancer(queryWithOrderNoAlias).detectAlias()).isNull();
|
||||
assertThat(getEnhancer(queryWithGroupAlias).detectAlias()).isEqualTo("u");
|
||||
assertThat(getEnhancer(queryWithOrderAlias).detectAlias()).isEqualTo("u");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1500
|
||||
void createCountQuerySupportsWhitespaceCharacters() {
|
||||
|
||||
StringQuery query = new StringQuery("select * from User user\n" + //
|
||||
" where user.age = 18\n" + //
|
||||
" order by user.name\n ", true);
|
||||
|
||||
assertThat(getEnhancer(query).createCountQueryFor())
|
||||
.isEqualToIgnoringCase("select count(user) from User user where user.age = 18");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createCountQuerySupportsLineBreaksInSelectClause() {
|
||||
StringQuery query = new StringQuery("select user.age,\n" + //
|
||||
" user.name\n" + //
|
||||
" from User user\n" + //
|
||||
" where user.age = 18\n" + //
|
||||
" order\nby\nuser.name\n ", true);
|
||||
assertThat(getEnhancer(query).createCountQueryFor())
|
||||
.isEqualToIgnoringCase("select count(user) from User user where user.age = 18");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1061
|
||||
void appliesSortCorrectlyForFieldAliases() {
|
||||
|
||||
StringQuery query = new StringQuery(
|
||||
"SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a",
|
||||
true);
|
||||
Sort sort = Sort.by("authorName");
|
||||
|
||||
String fullQuery = getEnhancer(query).applySorting(sort);
|
||||
|
||||
endsIgnoringCase(fullQuery, "order by authorName asc");
|
||||
}
|
||||
|
||||
@Test // GH-2280
|
||||
void appliesOrderingCorrectlyForFieldAliasWithIgnoreCase() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT customer.id as id, customer.name as name FROM CustomerEntity customer",
|
||||
true);
|
||||
Sort sort = Sort.by(Sort.Order.by("name").ignoreCase());
|
||||
|
||||
String fullQuery = getEnhancer(query).applySorting(sort);
|
||||
|
||||
assertThat(fullQuery).isEqualToIgnoringCase(
|
||||
"SELECT customer.id as id, customer.name as name FROM CustomerEntity customer order by lower(name) asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1061
|
||||
void appliesSortCorrectlyForFunctionAliases() {
|
||||
|
||||
StringQuery query = new StringQuery(
|
||||
"SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a",
|
||||
true);
|
||||
Sort sort = Sort.by("title");
|
||||
|
||||
String fullQuery = getEnhancer(query).applySorting(sort);
|
||||
|
||||
endsIgnoringCase(fullQuery, "order by title asc");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1061
|
||||
void appliesSortCorrectlyForSimpleField() {
|
||||
|
||||
StringQuery query = new StringQuery(
|
||||
"SELECT m.price, lower(m.title) AS title, a.name as authorName FROM Magazine m INNER JOIN m.author a",
|
||||
true);
|
||||
Sort sort = Sort.by("price");
|
||||
|
||||
String fullQuery = getEnhancer(query).applySorting(sort);
|
||||
|
||||
endsIgnoringCase(fullQuery, "order by m.price asc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createCountQuerySupportsLineBreakRightAfterDistinct() {
|
||||
|
||||
StringQuery query1 = new StringQuery("select\ndistinct\nuser.age,\n" + //
|
||||
"user.name\n" + //
|
||||
"from\nUser\nuser", true);
|
||||
|
||||
StringQuery query2 = new StringQuery("select\ndistinct user.age,\n" + //
|
||||
"user.name\n" + //
|
||||
"from\nUser\nuser", true);
|
||||
|
||||
assertThat(getEnhancer(query1).createCountQueryFor()).isEqualTo(getEnhancer(query2).createCountQueryFor());
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsAliasWithGroupAndOrderByWithLineBreaks() {
|
||||
|
||||
StringQuery queryWithGroupAndLineBreak = new StringQuery("select * from User group\nby name", true);
|
||||
StringQuery queryWithGroupAndLineBreakAndAlias = new StringQuery("select * from User u group\nby name", true);
|
||||
|
||||
assertThat(getEnhancer(queryWithGroupAndLineBreak).detectAlias()).isNull();
|
||||
assertThat(getEnhancer(queryWithGroupAndLineBreakAndAlias).detectAlias()).isEqualTo("u");
|
||||
|
||||
StringQuery queryWithOrderAndLineBreak = new StringQuery("select * from User order\nby name", true);
|
||||
StringQuery queryWithOrderAndLineBreakAndAlias = new StringQuery("select * from User u order\nby name", true);
|
||||
StringQuery queryWithOrderAndMultipleLineBreakAndAlias = new StringQuery("select * from User\nu\norder \n by name",
|
||||
true);
|
||||
|
||||
assertThat(getEnhancer(queryWithOrderAndLineBreak).detectAlias()).isNull();
|
||||
assertThat(getEnhancer(queryWithOrderAndLineBreakAndAlias).detectAlias()).isEqualTo("u");
|
||||
assertThat(getEnhancer(queryWithOrderAndMultipleLineBreakAndAlias).detectAlias()).isEqualTo("u");
|
||||
}
|
||||
|
||||
@ParameterizedTest // DATAJPA-1679
|
||||
@MethodSource("findProjectionClauseWithDistinctSource")
|
||||
void findProjectionClauseWithDistinct(DeclaredQuery query, String expected) {
|
||||
|
||||
SoftAssertions.assertSoftly(sofly -> {
|
||||
sofly.assertThat(getEnhancer(query).getProjection()).isEqualTo(expected);
|
||||
});
|
||||
}
|
||||
|
||||
public static Stream<Arguments> findProjectionClauseWithDistinctSource() {
|
||||
return Stream.of( //
|
||||
Arguments.of(new StringQuery("select * from x", true), "*"), //
|
||||
Arguments.of(new StringQuery("select a, b, c from x", true), "a, b, c"), //
|
||||
Arguments.of(new StringQuery("select distinct a, b, c from x", true), "a, b, c"), //
|
||||
Arguments.of(new StringQuery("select DISTINCT a, b, c from x", true), "a, b, c") //
|
||||
);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1696
|
||||
void findProjectionClauseWithSubselect() {
|
||||
|
||||
// This is not a required behavior, in fact the opposite is,
|
||||
// but it documents a current limitation.
|
||||
// to fix this without breaking findProjectionClauseWithIncludedFrom we need a more sophisticated parser.
|
||||
assertThat(QueryUtils.getProjection("select * from (select x from y)")).isNotEqualTo("*");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1696
|
||||
void findProjectionClauseWithSubselectNative() {
|
||||
|
||||
// This is a required behavior the testcase in #findProjectionClauseWithSubselect tells why
|
||||
String queryString = "select * from (select x from y)";
|
||||
StringQuery query = new StringQuery(queryString, true);
|
||||
assertThat(getEnhancer(query).getProjection()).isEqualTo("*");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1696
|
||||
void findProjectionClauseWithIncludedFrom() {
|
||||
StringQuery query = new StringQuery("select x, frommage, y from t", true);
|
||||
assertThat(getEnhancer(query).getProjection()).isEqualTo("x, frommage, y");
|
||||
}
|
||||
|
||||
@Test
|
||||
void countProjectionDistrinctQueryIncludesNewLineAfterFromAndBeforeJoin() {
|
||||
StringQuery originalQuery = new StringQuery(
|
||||
"SELECT DISTINCT entity1\nFROM Entity1 entity1\nLEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", true);
|
||||
|
||||
assertCountQuery(originalQuery,
|
||||
"select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key");
|
||||
}
|
||||
|
||||
@Test
|
||||
void countProjectionDistinctQueryIncludesNewLineAfterEntity() {
|
||||
StringQuery originalQuery = new StringQuery(
|
||||
"SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key", true);
|
||||
assertCountQuery(originalQuery,
|
||||
"select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key");
|
||||
}
|
||||
|
||||
@Test
|
||||
void countProjectionDistinctQueryIncludesNewLineAfterEntityAndBeforeWhere() {
|
||||
StringQuery originalQuery = new StringQuery(
|
||||
"SELECT DISTINCT entity1\nFROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key\nwhere entity1.id = 1799",
|
||||
true);
|
||||
assertCountQuery(originalQuery,
|
||||
"select count(DISTINCT entity1) FROM Entity1 entity1 LEFT JOIN Entity2 entity2 ON entity1.key = entity2.key where entity1.id = 1799");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueriesCorrectlyForCapitalLetter() {
|
||||
assertCountQuery("SELECT u FROM User u where u.foo.bar = ?", "select count(u) FROM User u where u.foo.bar = ?",
|
||||
true);
|
||||
}
|
||||
|
||||
@ParameterizedTest // DATAJPA-252
|
||||
@MethodSource("detectsJoinAliasesCorrectlySource")
|
||||
void detectsJoinAliasesCorrectly(String queryString, List<String> aliases) {
|
||||
|
||||
StringQuery nativeQuery = new StringQuery(queryString, true);
|
||||
StringQuery nonNativeQuery = new StringQuery(queryString, false);
|
||||
|
||||
Set<String> nativeJoinAliases = getEnhancer(nativeQuery).getJoinAliases();
|
||||
Set<String> nonNativeJoinAliases = getEnhancer(nonNativeQuery).getJoinAliases();
|
||||
|
||||
assertThat(nonNativeJoinAliases).containsAll(nativeJoinAliases);
|
||||
assertThat(nativeJoinAliases) //
|
||||
.hasSize(aliases.size()) //
|
||||
.containsAll(aliases);
|
||||
|
||||
}
|
||||
|
||||
@Test // GH-2441
|
||||
void correctFunctionAliasWithComplexNestedFunctions() {
|
||||
String queryString = "\nSELECT \nCAST(('{' || string_agg(distinct array_to_string(c.institutes_ids, ','), ',') || '}') AS bigint[]) as institutesIds\nFROM\ncity c";
|
||||
StringQuery nativeQuery = new StringQuery(queryString, true);
|
||||
|
||||
JSqlParserQueryEnhancer queryEnhancer = (JSqlParserQueryEnhancer) getEnhancer(nativeQuery);
|
||||
|
||||
assertThat(queryEnhancer.getSelectionAliases()).contains("institutesIds");
|
||||
}
|
||||
|
||||
@Test // GH-2441
|
||||
void correctApplySortOnComplexNestedFunctionQuery() {
|
||||
String queryString = "SELECT dd.institutesIds FROM (\n" + " SELECT\n"
|
||||
+ " CAST(('{' || string_agg(distinct array_to_string(c.institutes_ids, ','), ',') || '}') AS bigint[]) as institutesIds\n"
|
||||
+ " FROM\n" + " city c\n"
|
||||
+ " ) dd";
|
||||
|
||||
StringQuery nativeQuery = new StringQuery(queryString, true);
|
||||
|
||||
QueryEnhancer queryEnhancer = getEnhancer(nativeQuery);
|
||||
|
||||
String result = queryEnhancer.applySorting(Sort.by(new Sort.Order(Sort.Direction.ASC, "institutesIds")));
|
||||
assertThat(result).containsIgnoringCase("order by dd.institutesIds");
|
||||
}
|
||||
|
||||
public static Stream<Arguments> detectsJoinAliasesCorrectlySource() {
|
||||
return Stream.of( //
|
||||
Arguments.of("select p from Person p left outer join x.foo b2_$ar", Collections.singletonList("b2_$ar")), //
|
||||
Arguments.of("select p from Person p left join x.foo b2_$ar", Collections.singletonList("b2_$ar")), //
|
||||
Arguments.of("select p from Person p left outer join x.foo as b2_$ar, left join x.bar as foo",
|
||||
Arrays.asList("b2_$ar", "foo")), //
|
||||
Arguments.of("select p from Person p left join x.foo as b2_$ar, left outer join x.bar foo",
|
||||
Arrays.asList("b2_$ar", "foo")) //
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
private static void assertCountQuery(String originalQuery, String countQuery, boolean nativeQuery) {
|
||||
assertCountQuery(new StringQuery(originalQuery, nativeQuery), countQuery);
|
||||
}
|
||||
|
||||
private static void assertCountQuery(StringQuery originalQuery, String countQuery) {
|
||||
assertThat(getEnhancer(originalQuery).createCountQueryFor()).isEqualToIgnoringCase(countQuery);
|
||||
}
|
||||
|
||||
private static void endsIgnoringCase(String original, String endWithIgnoreCase) {
|
||||
// https://github.com/assertj/assertj-core/pull/2451
|
||||
// can be removed when upgrading to version 3.23.0 assertJ
|
||||
assertThat(original.toUpperCase()).endsWith(endWithIgnoreCase.toUpperCase());
|
||||
}
|
||||
|
||||
private static QueryEnhancer getEnhancer(DeclaredQuery query) {
|
||||
return QueryEnhancerFactory.forQuery(query);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -53,14 +53,14 @@ class QueryParameterSetterFactoryUnitTests {
|
||||
|
||||
@Test // DATAJPA-1058
|
||||
void noExceptionWhenQueryDoesNotContainNamedParameters() {
|
||||
setterFactory.create(binding, DeclaredQuery.of("QueryStringWithOutNamedParameter"));
|
||||
setterFactory.create(binding, DeclaredQuery.of("QueryStringWithOutNamedParameter", false));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1058
|
||||
void exceptionWhenQueryContainNamedParametersAndMethodParametersAreNotNamed() {
|
||||
|
||||
assertThatExceptionOfType(IllegalStateException.class) //
|
||||
.isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith :NamedParameter"))) //
|
||||
.isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith :NamedParameter", false))) //
|
||||
.withMessageContaining("Java 8") //
|
||||
.withMessageContaining("@Param") //
|
||||
.withMessageContaining("-parameters");
|
||||
@@ -77,7 +77,7 @@ class QueryParameterSetterFactoryUnitTests {
|
||||
when(binding.getRequiredPosition()).thenReturn(1);
|
||||
|
||||
assertThatExceptionOfType(IllegalArgumentException.class) //
|
||||
.isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith :NamedParameter"))) //
|
||||
.isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith :NamedParameter", false))) //
|
||||
.withMessage("At least 1 parameter(s) provided but only 0 parameter(s) present in query.");
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ class QueryParameterSetterFactoryUnitTests {
|
||||
when(binding.getRequiredPosition()).thenReturn(1);
|
||||
|
||||
assertThatExceptionOfType(IllegalArgumentException.class) //
|
||||
.isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith ?1"))) //
|
||||
.isThrownBy(() -> setterFactory.create(binding, DeclaredQuery.of("QueryStringWith ?1", false))) //
|
||||
.withMessage("At least 1 parameter(s) provided but only 0 parameter(s) present in query.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
@ContextConfiguration("classpath:infrastructure.xml")
|
||||
public class QueryUtilsIntegrationTests {
|
||||
|
||||
@PersistenceContext EntityManager em;
|
||||
@PersistenceContext
|
||||
EntityManager em;
|
||||
|
||||
@Test // DATAJPA-403
|
||||
void reusesExistingJoinForExpression() {
|
||||
@@ -139,8 +140,8 @@ public class QueryUtilsIntegrationTests {
|
||||
CriteriaQuery<InvoiceItem> query = builder.createQuery(InvoiceItem.class);
|
||||
Root<InvoiceItem> root = query.from(InvoiceItem.class);
|
||||
|
||||
QueryUtils
|
||||
.toExpressionRecursively(root, PropertyPath.from("invoice.order.customer.name", InvoiceItem.class), false);
|
||||
QueryUtils.toExpressionRecursively(root, PropertyPath.from("invoice.order.customer.name", InvoiceItem.class),
|
||||
false);
|
||||
|
||||
assertThat(getInnerJoins(root)).hasSize(1); // join invoice
|
||||
Join<?, ?> rootInnerJoin = getInnerJoins(root).iterator().next();
|
||||
@@ -162,8 +163,8 @@ public class QueryUtilsIntegrationTests {
|
||||
root.join("invoice", JoinType.LEFT).join("order", JoinType.LEFT);
|
||||
|
||||
// when navigating through a path with nested optionals
|
||||
QueryUtils
|
||||
.toExpressionRecursively(root, PropertyPath.from("invoice.order.customer.name", InvoiceItem.class), false);
|
||||
QueryUtils.toExpressionRecursively(root, PropertyPath.from("invoice.order.customer.name", InvoiceItem.class),
|
||||
false);
|
||||
|
||||
// assert that existing joins are reused and no additional joins are created
|
||||
assertThat(getInnerJoins(root)).isEmpty(); // no inner join invoice
|
||||
@@ -185,8 +186,8 @@ public class QueryUtilsIntegrationTests {
|
||||
// given an existing inner join an nested optional
|
||||
root.join("invoice").join("order");
|
||||
|
||||
QueryUtils
|
||||
.toExpressionRecursively(root, PropertyPath.from("invoice.order.customer.name", InvoiceItem.class), false);
|
||||
QueryUtils.toExpressionRecursively(root, PropertyPath.from("invoice.order.customer.name", InvoiceItem.class),
|
||||
false);
|
||||
|
||||
// assert that no useless left joins are created
|
||||
assertThat(getInnerJoins(root)).hasSize(1); // join invoice
|
||||
@@ -347,32 +348,40 @@ public class QueryUtilsIntegrationTests {
|
||||
@SuppressWarnings("unused")
|
||||
static class Merchant {
|
||||
|
||||
@Id String id;
|
||||
@OneToMany Set<Employee> employees;
|
||||
@Id
|
||||
String id;
|
||||
@OneToMany
|
||||
Set<Employee> employees;
|
||||
|
||||
@OneToOne Address address;
|
||||
@OneToOne
|
||||
Address address;
|
||||
}
|
||||
|
||||
@Entity
|
||||
@SuppressWarnings("unused")
|
||||
static class Address {
|
||||
@Id String id;
|
||||
@OneToOne(mappedBy = "address") Merchant merchant;
|
||||
@Id
|
||||
String id;
|
||||
@OneToOne(mappedBy = "address")
|
||||
Merchant merchant;
|
||||
}
|
||||
|
||||
@Entity
|
||||
@SuppressWarnings("unused")
|
||||
static class Employee {
|
||||
|
||||
@Id String id;
|
||||
@OneToMany Set<Credential> credentials;
|
||||
@Id
|
||||
String id;
|
||||
@OneToMany
|
||||
Set<Credential> credentials;
|
||||
}
|
||||
|
||||
@Entity
|
||||
@SuppressWarnings("unused")
|
||||
static class Credential {
|
||||
|
||||
@Id String id;
|
||||
@Id
|
||||
String id;
|
||||
String uid;
|
||||
}
|
||||
|
||||
@@ -390,7 +399,8 @@ public class QueryUtilsIntegrationTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearCachedProviders() {}
|
||||
public void clearCachedProviders() {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class QueryUtilsUnitTests {
|
||||
private static final String QUERY_WITH_AS = "select u from User as u where u.username = ?";
|
||||
|
||||
@Test
|
||||
void createsCountQueryCorrectly() {
|
||||
void createsCountQueryCorrectly() throws Exception {
|
||||
assertCountQuery(QUERY, COUNT_QUERY);
|
||||
}
|
||||
|
||||
@@ -64,47 +64,47 @@ class QueryUtilsUnitTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForDistinctQueries() {
|
||||
void createsCountQueryForDistinctQueries() throws Exception {
|
||||
|
||||
assertCountQuery("select distinct u from User u where u.foo = ?",
|
||||
"select count(distinct u) from User u where u.foo = ?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForConstructorQueries() {
|
||||
void createsCountQueryForConstructorQueries() throws Exception {
|
||||
|
||||
assertCountQuery("select distinct new User(u.name) from User u where u.foo = ?",
|
||||
"select count(distinct u) from User u where u.foo = ?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForJoins() {
|
||||
void createsCountQueryForJoins() throws Exception {
|
||||
|
||||
assertCountQuery("select distinct new User(u.name) from User u left outer join u.roles r WHERE r = ?",
|
||||
"select count(distinct u) from User u left outer join u.roles r WHERE r = ?");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForQueriesWithSubSelects() {
|
||||
void createsCountQueryForQueriesWithSubSelects() throws Exception {
|
||||
|
||||
assertCountQuery("select u from User u left outer join u.roles r where r in (select r from Role)",
|
||||
"select count(u) from User u left outer join u.roles r where r in (select r from Role)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsCountQueryForAliasesCorrectly() {
|
||||
void createsCountQueryForAliasesCorrectly() throws Exception {
|
||||
|
||||
assertCountQuery("select u from User as u", "select count(u) from User as u");
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsShortJpaSyntax() {
|
||||
void allowsShortJpaSyntax() throws Exception {
|
||||
|
||||
assertCountQuery(SIMPLE_QUERY, COUNT_QUERY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsAliasCorrectly() {
|
||||
void detectsAliasCorrectly() throws Exception {
|
||||
|
||||
assertThat(detectAlias(QUERY)).isEqualTo("u");
|
||||
assertThat(detectAlias(SIMPLE_QUERY)).isEqualTo("u");
|
||||
@@ -400,8 +400,8 @@ class QueryUtilsUnitTests {
|
||||
@Test // DATAJPA-1363
|
||||
void discoversAliasWithComplexFunction() {
|
||||
|
||||
assertThat(
|
||||
QueryUtils.getFunctionAliases("select new MyDto(sum(case when myEntity.prop3=0 then 1 else 0 end) as myAlias")) //
|
||||
assertThat(QueryUtils
|
||||
.getFunctionAliases("select new MyDto(sum(case when myEntity.prop3=0 then 1 else 0 end) as myAlias")) //
|
||||
.contains("myAlias");
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.springframework.data.repository.query.parser.Part.Type;
|
||||
* @author Jens Schauder
|
||||
* @author Nils Borrmann
|
||||
* @author Andriy Redko
|
||||
* @author Diego Krupitza
|
||||
*/
|
||||
class StringQueryUnitTests {
|
||||
|
||||
@@ -46,7 +47,7 @@ class StringQueryUnitTests {
|
||||
void doesNotConsiderPlainLikeABinding() {
|
||||
|
||||
String source = "select from User u where u.firstname like :firstname";
|
||||
StringQuery query = new StringQuery(source);
|
||||
StringQuery query = new StringQuery(source, false);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString()).isEqualTo(source);
|
||||
@@ -63,7 +64,8 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-292
|
||||
void detectsPositionalLikeBindings() {
|
||||
|
||||
StringQuery query = new StringQuery("select u from User u where u.firstname like %?1% or u.lastname like %?2");
|
||||
StringQuery query = new StringQuery("select u from User u where u.firstname like %?1% or u.lastname like %?2",
|
||||
true);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString())
|
||||
@@ -86,7 +88,7 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-292
|
||||
void detectsNamedLikeBindings() {
|
||||
|
||||
StringQuery query = new StringQuery("select u from User u where u.firstname like %:firstname");
|
||||
StringQuery query = new StringQuery("select u from User u where u.firstname like %:firstname", true);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString()).isEqualTo("select u from User u where u.firstname like :firstname");
|
||||
@@ -104,7 +106,7 @@ class StringQueryUnitTests {
|
||||
void detectsNamedInParameterBindings() {
|
||||
|
||||
String queryString = "select u from User u where u.id in :ids";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
StringQuery query = new StringQuery(queryString, true);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString()).isEqualTo(queryString);
|
||||
@@ -121,7 +123,7 @@ class StringQueryUnitTests {
|
||||
void detectsMultipleNamedInParameterBindings() {
|
||||
|
||||
String queryString = "select u from User u where u.id in :ids and u.name in :names and foo = :bar";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
StringQuery query = new StringQuery(queryString, true);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString()).isEqualTo(queryString);
|
||||
@@ -140,7 +142,7 @@ class StringQueryUnitTests {
|
||||
void detectsPositionalInParameterBindings() {
|
||||
|
||||
String queryString = "select u from User u where u.id in ?1";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
StringQuery query = new StringQuery(queryString, true);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString()).isEqualTo(queryString);
|
||||
@@ -157,7 +159,7 @@ class StringQueryUnitTests {
|
||||
void detectsMultiplePositionalInParameterBindings() {
|
||||
|
||||
String queryString = "select u from User u where u.id in ?1 and u.names in ?2 and foo = ?3";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
StringQuery query = new StringQuery(queryString, true);
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString()).isEqualTo(queryString);
|
||||
@@ -174,19 +176,19 @@ class StringQueryUnitTests {
|
||||
|
||||
@Test // DATAJPA-373
|
||||
void handlesMultipleNamedLikeBindingsCorrectly() {
|
||||
new StringQuery("select u from User u where u.firstname like %:firstname or foo like :bar");
|
||||
new StringQuery("select u from User u where u.firstname like %:firstname or foo like :bar", true);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-292, DATAJPA-362
|
||||
void rejectsDifferentBindingsForRepeatedParameter() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new StringQuery("select u from User u where u.firstname like %?1 and u.lastname like ?1%"));
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new StringQuery("select u from User u where u.firstname like %?1 and u.lastname like ?1%", true));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-461
|
||||
void treatsGreaterThanBindingAsSimpleBinding() {
|
||||
|
||||
StringQuery query = new StringQuery("select u from User u where u.createdDate > ?1");
|
||||
StringQuery query = new StringQuery("select u from User u where u.createdDate > ?1", true);
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
|
||||
assertThat(bindings).hasSize(1);
|
||||
@@ -199,7 +201,7 @@ class StringQueryUnitTests {
|
||||
void removesLikeBindingsFromQueryIfQueryContainsSimpleBinding() {
|
||||
|
||||
StringQuery query = new StringQuery("SELECT a FROM Article a WHERE a.overview LIKE %:escapedWord% ESCAPE '~'"
|
||||
+ " OR a.content LIKE %:escapedWord% ESCAPE '~' OR a.title = :word ORDER BY a.articleId DESC");
|
||||
+ " OR a.content LIKE %:escapedWord% ESCAPE '~' OR a.title = :word ORDER BY a.articleId DESC", true);
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
|
||||
@@ -217,7 +219,7 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-483
|
||||
void detectsInBindingWithParentheses() {
|
||||
|
||||
StringQuery query = new StringQuery("select count(we) from MyEntity we where we.status in (:statuses)");
|
||||
StringQuery query = new StringQuery("select count(we) from MyEntity we where we.status in (:statuses)", true);
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
|
||||
@@ -230,7 +232,7 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-545
|
||||
void detectsInBindingWithSpecialFrenchCharactersInParentheses() {
|
||||
|
||||
StringQuery query = new StringQuery("select * from MyEntity where abonnés in (:abonnés)");
|
||||
StringQuery query = new StringQuery("select * from MyEntity where abonnés in (:abonnés)", true);
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
|
||||
@@ -243,7 +245,7 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-545
|
||||
void detectsInBindingWithSpecialCharactersInParentheses() {
|
||||
|
||||
StringQuery query = new StringQuery("select * from MyEntity where øre in (:øre)");
|
||||
StringQuery query = new StringQuery("select * from MyEntity where øre in (:øre)", true);
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
|
||||
@@ -256,7 +258,7 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-545
|
||||
void detectsInBindingWithSpecialAsianCharactersInParentheses() {
|
||||
|
||||
StringQuery query = new StringQuery("select * from MyEntity where 생일 in (:생일)");
|
||||
StringQuery query = new StringQuery("select * from MyEntity where 생일 in (:생일)", true);
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
|
||||
@@ -269,7 +271,7 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-545
|
||||
void detectsInBindingWithSpecialCharactersAndWordCharactersMixedInParentheses() {
|
||||
|
||||
StringQuery query = new StringQuery("select * from MyEntity where foo in (:ab1babc생일233)");
|
||||
StringQuery query = new StringQuery("select * from MyEntity where foo in (:ab1babc생일233)", true);
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
|
||||
@@ -281,14 +283,14 @@ class StringQueryUnitTests {
|
||||
|
||||
@Test // DATAJPA-362
|
||||
void rejectsDifferentBindingsForRepeatedParameter2() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new StringQuery("select u from User u where u.firstname like ?1 and u.lastname like %?1"));
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new StringQuery("select u from User u where u.firstname like ?1 and u.lastname like %?1", true));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-712
|
||||
void shouldReplaceAllNamedExpressionParametersWithInClause() {
|
||||
|
||||
StringQuery query = new StringQuery("select a from A a where a.b in :#{#bs} and a.c in :#{#cs}");
|
||||
StringQuery query = new StringQuery("select a from A a where a.b in :#{#bs} and a.c in :#{#cs}", true);
|
||||
String queryString = query.getQueryString();
|
||||
|
||||
assertThat(queryString).isEqualTo("select a from A a where a.b in :__$synthetic$__1 and a.c in :__$synthetic$__2");
|
||||
@@ -297,7 +299,7 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-712
|
||||
void shouldReplaceAllPositionExpressionParametersWithInClause() {
|
||||
|
||||
StringQuery query = new StringQuery("select a from A a where a.b in ?#{#bs} and a.c in ?#{#cs}");
|
||||
StringQuery query = new StringQuery("select a from A a where a.b in ?#{#bs} and a.c in ?#{#cs}", true);
|
||||
String queryString = query.getQueryString();
|
||||
|
||||
softly.assertThat(queryString).isEqualTo("select a from A a where a.b in ?1 and a.c in ?2");
|
||||
@@ -310,9 +312,11 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-864
|
||||
void detectsConstructorExpressions() {
|
||||
|
||||
softly.assertThat(new StringQuery("select new Dto(a.foo, a.bar) from A a").hasConstructorExpression()).isTrue();
|
||||
softly.assertThat(new StringQuery("select new Dto (a.foo, a.bar) from A a").hasConstructorExpression()).isTrue();
|
||||
softly.assertThat(new StringQuery("select a from A a").hasConstructorExpression()).isFalse();
|
||||
softly.assertThat(new StringQuery("select new Dto(a.foo, a.bar) from A a", false).hasConstructorExpression())
|
||||
.isTrue();
|
||||
softly.assertThat(new StringQuery("select new Dto (a.foo, a.bar) from A a", false).hasConstructorExpression())
|
||||
.isTrue();
|
||||
softly.assertThat(new StringQuery("select a from A a", true).hasConstructorExpression()).isFalse();
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
@@ -325,8 +329,8 @@ class StringQueryUnitTests {
|
||||
void detectsConstructorExpressionForDefaultConstructor() {
|
||||
|
||||
// Parentheses required
|
||||
softly.assertThat(new StringQuery("select new Dto() from A a").hasConstructorExpression()).isTrue();
|
||||
softly.assertThat(new StringQuery("select new Dto from A a").hasConstructorExpression()).isFalse();
|
||||
softly.assertThat(new StringQuery("select new Dto() from A a", false).hasConstructorExpression()).isTrue();
|
||||
softly.assertThat(new StringQuery("select new Dto from A a", false).hasConstructorExpression()).isFalse();
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
@@ -334,7 +338,7 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-1179
|
||||
void bindingsMatchQueryForIdenticalSpelExpressions() {
|
||||
|
||||
StringQuery query = new StringQuery("select a from A a where a.first = :#{#exp} or a.second = :#{#exp}");
|
||||
StringQuery query = new StringQuery("select a from A a where a.first = :#{#exp} or a.second = :#{#exp}", true);
|
||||
|
||||
List<ParameterBinding> bindings = query.getParameterBindings();
|
||||
softly.assertThat(bindings).isNotEmpty();
|
||||
@@ -351,18 +355,18 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-1235
|
||||
void getProjection() {
|
||||
|
||||
checkProjection("SELECT something FROM", "something", "uppercase is supported");
|
||||
checkProjection("select something from", "something", "single expression");
|
||||
checkProjection("select x, y, z from", "x, y, z", "tuple");
|
||||
checkProjection("sect x, y, z from", "", "missing select");
|
||||
checkProjection("select x, y, z fron", "", "missing from");
|
||||
checkProjection("SELECT something FROM", "something", "uppercase is supported", false);
|
||||
checkProjection("select something from", "something", "single expression", false);
|
||||
checkProjection("select x, y, z from", "x, y, z", "tuple", false);
|
||||
checkProjection("sect x, y, z from", "", "missing select", false);
|
||||
checkProjection("select x, y, z fron", "", "missing from", false);
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
void checkProjection(String query, String expected, String description) {
|
||||
void checkProjection(String query, String expected, String description, boolean nativeQuery) {
|
||||
|
||||
softly.assertThat(new StringQuery(query).getProjection()) //
|
||||
softly.assertThat(new StringQuery(query, nativeQuery).getProjection()) //
|
||||
.as("%s (%s)", description, query) //
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
@@ -370,25 +374,25 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-1235
|
||||
void getAlias() {
|
||||
|
||||
checkAlias("from User u", "u", "simple query");
|
||||
checkAlias("select count(u) from User u", "u", "count query");
|
||||
checkAlias("select u from User as u where u.username = ?", "u", "with as");
|
||||
checkAlias("SELECT FROM USER U", "U", "uppercase");
|
||||
checkAlias("select u from User u", "u", "simple query");
|
||||
checkAlias("select u from com.acme.User u", "u", "fully qualified package name");
|
||||
checkAlias("select u from T05User u", "u", "interesting entity name");
|
||||
checkAlias("from User ", null, "trailing space");
|
||||
checkAlias("from User", null, "no trailing space");
|
||||
checkAlias("from User as bs", "bs", "ignored as");
|
||||
checkAlias("from User as AS", "AS", "ignored as using the second");
|
||||
checkAlias("from User asas", "asas", "asas is weird but legal");
|
||||
checkAlias("from User u", "u", "simple query", false);
|
||||
checkAlias("select count(u) from User u", "u", "count query", true);
|
||||
checkAlias("select u from User as u where u.username = ?", "u", "with as", true);
|
||||
checkAlias("SELECT FROM USER U", "U", "uppercase", false);
|
||||
checkAlias("select u from User u", "u", "simple query", true);
|
||||
checkAlias("select u from com.acme.User u", "u", "fully qualified package name", true);
|
||||
checkAlias("select u from T05User u", "u", "interesting entity name", true);
|
||||
checkAlias("from User ", null, "trailing space", false);
|
||||
checkAlias("from User", null, "no trailing space", false);
|
||||
checkAlias("from User as bs", "bs", "ignored as", false);
|
||||
checkAlias("from User as AS", "AS", "ignored as using the second", false);
|
||||
checkAlias("from User asas", "asas", "asas is weird but legal", false);
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
private void checkAlias(String query, String expected, String description) {
|
||||
private void checkAlias(String query, String expected, String description, boolean nativeQuery) {
|
||||
|
||||
softly.assertThat(new StringQuery(query).getAlias()) //
|
||||
softly.assertThat(new StringQuery(query, nativeQuery).getAlias()) //
|
||||
.as("%s (%s)", description, query) //
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
@@ -396,32 +400,32 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-1200
|
||||
void testHasNamedParameter() {
|
||||
|
||||
checkHasNamedParameter("select something from x where id = :id", true, "named parameter");
|
||||
checkHasNamedParameter("in the :id middle", true, "middle");
|
||||
checkHasNamedParameter(":id start", true, "beginning");
|
||||
checkHasNamedParameter(":id", true, "alone");
|
||||
checkHasNamedParameter("select something from x where id = :id", true, "named parameter");
|
||||
checkHasNamedParameter(":UPPERCASE", true, "uppercase");
|
||||
checkHasNamedParameter(":lowercase", true, "lowercase");
|
||||
checkHasNamedParameter(":2something", true, "beginning digit");
|
||||
checkHasNamedParameter(":2", true, "only digit");
|
||||
checkHasNamedParameter(":.something", true, "dot");
|
||||
checkHasNamedParameter(":_something", true, "underscore");
|
||||
checkHasNamedParameter(":$something", true, "dollar");
|
||||
checkHasNamedParameter(":\uFE0F", true, "non basic latin emoji"); //
|
||||
checkHasNamedParameter(":\u4E01", true, "chinese japanese korean");
|
||||
checkHasNamedParameter("select something from x where id = :id", true, "named parameter", true);
|
||||
checkHasNamedParameter("in the :id middle", true, "middle", false);
|
||||
checkHasNamedParameter(":id start", true, "beginning", false);
|
||||
checkHasNamedParameter(":id", true, "alone", false);
|
||||
checkHasNamedParameter("select something from x where id = :id", true, "named parameter", true);
|
||||
checkHasNamedParameter(":UPPERCASE", true, "uppercase", false);
|
||||
checkHasNamedParameter(":lowercase", true, "lowercase", false);
|
||||
checkHasNamedParameter(":2something", true, "beginning digit", false);
|
||||
checkHasNamedParameter(":2", true, "only digit", false);
|
||||
checkHasNamedParameter(":.something", true, "dot", false);
|
||||
checkHasNamedParameter(":_something", true, "underscore", false);
|
||||
checkHasNamedParameter(":$something", true, "dollar", false);
|
||||
checkHasNamedParameter(":\uFE0F", true, "non basic latin emoji", false); //
|
||||
checkHasNamedParameter(":\u4E01", true, "chinese japanese korean", false);
|
||||
|
||||
checkHasNamedParameter("no bind variable", false, "no bind variable");
|
||||
checkHasNamedParameter(":\u2004whitespace", false, "non basic latin whitespace");
|
||||
checkHasNamedParameter("select something from x where id = ?1", false, "indexed parameter");
|
||||
checkHasNamedParameter("::", false, "double colon");
|
||||
checkHasNamedParameter(":", false, "end of query");
|
||||
checkHasNamedParameter(":\u0003", false, "non-printable");
|
||||
checkHasNamedParameter(":*", false, "basic latin emoji");
|
||||
checkHasNamedParameter("\\:", false, "escaped colon");
|
||||
checkHasNamedParameter("::id", false, "double colon with identifier");
|
||||
checkHasNamedParameter("\\:id", false, "escaped colon with identifier");
|
||||
checkHasNamedParameter("select something from x where id = #something", false, "hash");
|
||||
checkHasNamedParameter("no bind variable", false, "no bind variable", false);
|
||||
checkHasNamedParameter(":\u2004whitespace", false, "non basic latin whitespace", false);
|
||||
checkHasNamedParameter("select something from x where id = ?1", false, "indexed parameter", true);
|
||||
checkHasNamedParameter("::", false, "double colon", false);
|
||||
checkHasNamedParameter(":", false, "end of query", false);
|
||||
checkHasNamedParameter(":\u0003", false, "non-printable", false);
|
||||
checkHasNamedParameter(":*", false, "basic latin emoji", false);
|
||||
checkHasNamedParameter("\\:", false, "escaped colon", false);
|
||||
checkHasNamedParameter("::id", false, "double colon with identifier", false);
|
||||
checkHasNamedParameter("\\:id", false, "escaped colon with identifier", false);
|
||||
checkHasNamedParameter("select something from x where id = #something", false, "hash", true);
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
@@ -429,11 +433,12 @@ class StringQueryUnitTests {
|
||||
@Test // DATAJPA-1235
|
||||
void ignoresQuotedNamedParameterLookAlike() {
|
||||
|
||||
checkNumberOfNamedParameters("select something from blah where x = '0:name'", 0, "single quoted");
|
||||
checkNumberOfNamedParameters("select something from blah where x = \"0:name\"", 0, "double quoted");
|
||||
checkNumberOfNamedParameters("select something from blah where x = '\"0':name", 1, "double quote in single quotes");
|
||||
checkNumberOfNamedParameters("select something from blah where x = \"'0\":name", 1,
|
||||
"single quote in double quotes");
|
||||
checkNumberOfNamedParameters("select something from blah where x = '0:name'", 0, "single quoted", false);
|
||||
checkNumberOfNamedParameters("select something from blah where x = \"0:name\"", 0, "double quoted", false);
|
||||
checkNumberOfNamedParameters("select something from blah where x = '\"0':name", 1, "double quote in single quotes",
|
||||
false);
|
||||
checkNumberOfNamedParameters("select something from blah where x = \"'0\":name", 1, "single quote in double quotes",
|
||||
false);
|
||||
|
||||
softly.assertAll();
|
||||
}
|
||||
@@ -442,7 +447,7 @@ class StringQueryUnitTests {
|
||||
void detectsMultiplePositionalParameterBindingsWithoutIndex() {
|
||||
|
||||
String queryString = "select u from User u where u.id in ? and u.names in ? and foo = ?";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
StringQuery query = new StringQuery(queryString, false);
|
||||
|
||||
softly.assertThat(query.getQueryString()).isEqualTo(queryString);
|
||||
softly.assertThat(query.hasParameterBindings()).isTrue();
|
||||
@@ -464,14 +469,14 @@ class StringQueryUnitTests {
|
||||
for (String testQuery : testQueries) {
|
||||
|
||||
Assertions.assertThatExceptionOfType(IllegalArgumentException.class) //
|
||||
.describedAs(testQuery).isThrownBy(() -> new StringQuery(testQuery));
|
||||
.describedAs(testQuery).isThrownBy(() -> new StringQuery(testQuery, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1307
|
||||
void makesUsageOfJdbcStyleParameterAvailable() {
|
||||
|
||||
softly.assertThat(new StringQuery("something = ?").usesJdbcStyleParameters()).isTrue();
|
||||
softly.assertThat(new StringQuery("something = ?", false).usesJdbcStyleParameters()).isTrue();
|
||||
|
||||
List<String> testQueries = Arrays.asList( //
|
||||
"something = ?1", //
|
||||
@@ -481,7 +486,7 @@ class StringQueryUnitTests {
|
||||
|
||||
for (String testQuery : testQueries) {
|
||||
|
||||
softly.assertThat(new StringQuery(testQuery) //
|
||||
softly.assertThat(new StringQuery(testQuery, false) //
|
||||
.usesJdbcStyleParameters()) //
|
||||
.describedAs(testQuery) //
|
||||
.isFalse();
|
||||
@@ -494,7 +499,7 @@ class StringQueryUnitTests {
|
||||
void questionMarkInStringLiteral() {
|
||||
|
||||
String queryString = "select '? ' from dual";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
StringQuery query = new StringQuery(queryString, false);
|
||||
|
||||
softly.assertThat(query.getQueryString()).isEqualTo(queryString);
|
||||
softly.assertThat(query.hasParameterBindings()).isFalse();
|
||||
@@ -515,7 +520,7 @@ class StringQueryUnitTests {
|
||||
"select a, b from C");
|
||||
|
||||
for (String queryString : queriesWithoutDefaultProjection) {
|
||||
softly.assertThat(new StringQuery(queryString).isDefaultProjection()) //
|
||||
softly.assertThat(new StringQuery(queryString, true).isDefaultProjection()) //
|
||||
.describedAs(queryString) //
|
||||
.isFalse();
|
||||
}
|
||||
@@ -532,7 +537,7 @@ class StringQueryUnitTests {
|
||||
);
|
||||
|
||||
for (String queryString : queriesWithDefaultProjection) {
|
||||
softly.assertThat(new StringQuery(queryString).isDefaultProjection()) //
|
||||
softly.assertThat(new StringQuery(queryString, true).isDefaultProjection()) //
|
||||
.describedAs(queryString) //
|
||||
.isTrue();
|
||||
}
|
||||
@@ -544,7 +549,7 @@ class StringQueryUnitTests {
|
||||
void usingPipesWithNamedParameter() {
|
||||
|
||||
String queryString = "SELECT u FROM User u WHERE u.lastname LIKE '%'||:name||'%'";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
StringQuery query = new StringQuery(queryString, true);
|
||||
|
||||
assertThat(query.getParameterBindings()) //
|
||||
.extracting(ParameterBinding::getName) //
|
||||
@@ -555,16 +560,16 @@ class StringQueryUnitTests {
|
||||
void usingGreaterThanWithNamedParameter() {
|
||||
|
||||
String queryString = "SELECT u FROM User u WHERE :age>u.age";
|
||||
StringQuery query = new StringQuery(queryString);
|
||||
StringQuery query = new StringQuery(queryString, true);
|
||||
|
||||
assertThat(query.getParameterBindings()) //
|
||||
.extracting(ParameterBinding::getName) //
|
||||
.containsExactly("age");
|
||||
}
|
||||
|
||||
void checkNumberOfNamedParameters(String query, int expectedSize, String label) {
|
||||
void checkNumberOfNamedParameters(String query, int expectedSize, String label, boolean nativeQuery) {
|
||||
|
||||
DeclaredQuery declaredQuery = DeclaredQuery.of(query);
|
||||
DeclaredQuery declaredQuery = DeclaredQuery.of(query, nativeQuery);
|
||||
|
||||
softly.assertThat(declaredQuery.hasNamedParameter()) //
|
||||
.describedAs("hasNamed Parameter " + label) //
|
||||
@@ -574,9 +579,9 @@ class StringQueryUnitTests {
|
||||
.hasSize(expectedSize);
|
||||
}
|
||||
|
||||
private void checkHasNamedParameter(String query, boolean expected, String label) {
|
||||
private void checkHasNamedParameter(String query, boolean expected, String label, boolean nativeQuery) {
|
||||
|
||||
softly.assertThat(new StringQuery(query).hasNamedParameter()) //
|
||||
softly.assertThat(new StringQuery(query, nativeQuery).hasNamedParameter()) //
|
||||
.describedAs(String.format("<%s> (%s)", query, label)) //
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user